1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/views/extensions/extension_popup.h"
8 #include "base/message_loop/message_loop.h"
9 #include "chrome/browser/chrome_notification_types.h"
10 #include "chrome/browser/devtools/devtools_window.h"
11 #include "chrome/browser/extensions/extension_view_host.h"
12 #include "chrome/browser/extensions/extension_view_host_factory.h"
13 #include "chrome/browser/platform_util.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/ui/browser.h"
16 #include "chrome/browser/ui/browser_window.h"
17 #include "chrome/browser/ui/host_desktop.h"
18 #include "chrome/browser/ui/tabs/tab_strip_model.h"
19 #include "chrome/browser/ui/views/frame/browser_view.h"
20 #include "content/public/browser/devtools_agent_host.h"
21 #include "content/public/browser/devtools_manager.h"
22 #include "content/public/browser/notification_details.h"
23 #include "content/public/browser/notification_source.h"
24 #include "content/public/browser/render_view_host.h"
25 #include "content/public/browser/web_contents.h"
26 #include "content/public/browser/web_contents_view.h"
27 #include "ui/gfx/insets.h"
28 #include "ui/views/layout/fill_layout.h"
29 #include "ui/views/widget/widget.h"
32 #include "ui/aura/client/activation_client.h"
33 #include "ui/aura/window.h"
34 #include "ui/views/corewm/window_animations.h"
35 #include "ui/views/corewm/window_util.h"
39 #include "ui/views/win/hwnd_util.h"
42 using content::BrowserContext
;
43 using content::RenderViewHost
;
44 using content::WebContents
;
48 // Returns true if |possible_owner| is the owner of |child|.
49 bool IsOwnerOf(gfx::NativeView child
, gfx::NativeView possible_owner
) {
53 if (::GetWindow(views::HWNDForNativeView(child
), GW_OWNER
) ==
54 views::HWNDForNativeView(possible_owner
))
62 // The minimum/maximum dimensions of the popup.
63 // The minimum is just a little larger than the size of the button itself.
64 // The maximum is an arbitrary number that should be smaller than most screens.
65 const int ExtensionPopup::kMinWidth
= 25;
66 const int ExtensionPopup::kMinHeight
= 25;
67 const int ExtensionPopup::kMaxWidth
= 800;
68 const int ExtensionPopup::kMaxHeight
= 600;
70 ExtensionPopup::ExtensionPopup(extensions::ExtensionViewHost
* host
,
71 views::View
* anchor_view
,
72 views::BubbleBorder::Arrow arrow
,
73 ShowAction show_action
)
74 : BubbleDelegateView(anchor_view
, arrow
),
76 devtools_callback_(base::Bind(
77 &ExtensionPopup::OnDevToolsStateChanged
, base::Unretained(this))) {
78 inspect_with_devtools_
= show_action
== SHOW_AND_INSPECT
;
79 // Adjust the margin so that contents fit better.
80 const int margin
= views::BubbleBorder::GetCornerRadius() / 2;
81 set_margins(gfx::Insets(margin
, margin
, margin
, margin
));
82 SetLayoutManager(new views::FillLayout());
83 AddChildView(host
->view());
84 host
->view()->set_container(this);
85 // Use OnNativeFocusChange to check for child window activation on deactivate.
86 set_close_on_deactivate(false);
87 // Make the bubble move with its anchor (during inspection, etc.).
88 set_move_with_anchor(true);
90 // Wait to show the popup until the contained host finishes loading.
91 registrar_
.Add(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME
,
92 content::Source
<WebContents
>(host
->host_contents()));
94 // Listen for the containing view calling window.close();
95 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE
,
96 content::Source
<BrowserContext
>(host
->browser_context()));
97 content::DevToolsManager::GetInstance()->AddAgentStateCallback(
100 host_
->view()->browser()->tab_strip_model()->AddObserver(this);
103 ExtensionPopup::~ExtensionPopup() {
104 content::DevToolsManager::GetInstance()->RemoveAgentStateCallback(
107 host_
->view()->browser()->tab_strip_model()->RemoveObserver(this);
110 void ExtensionPopup::Observe(int type
,
111 const content::NotificationSource
& source
,
112 const content::NotificationDetails
& details
) {
114 case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME
:
115 DCHECK(content::Source
<WebContents
>(host()->host_contents()) == source
);
116 // Show when the content finishes loading and its width is computed.
119 case chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE
:
120 // If we aren't the host of the popup, then disregard the notification.
121 if (content::Details
<extensions::ExtensionHost
>(host()) == details
)
122 GetWidget()->Close();
125 NOTREACHED() << L
"Received unexpected notification";
129 void ExtensionPopup::OnDevToolsStateChanged(
130 content::DevToolsAgentHost
* agent_host
, bool attached
) {
131 // First check that the devtools are being opened on this popup.
132 if (host()->render_view_host() != agent_host
->GetRenderViewHost())
136 // Set inspect_with_devtools_ so the popup will be kept open while
137 // the devtools are open.
138 inspect_with_devtools_
= true;
140 // Widget::Close posts a task, which should give the devtools window a
141 // chance to finish detaching from the inspected RenderViewHost.
142 GetWidget()->Close();
146 void ExtensionPopup::OnExtensionSizeChanged(ExtensionViewViews
* view
) {
150 gfx::Size
ExtensionPopup::GetPreferredSize() {
151 // Constrain the size to popup min/max.
152 gfx::Size sz
= views::View::GetPreferredSize();
153 sz
.set_width(std::max(kMinWidth
, std::min(kMaxWidth
, sz
.width())));
154 sz
.set_height(std::max(kMinHeight
, std::min(kMaxHeight
, sz
.height())));
158 void ExtensionPopup::OnWidgetDestroying(views::Widget
* widget
) {
159 BubbleDelegateView::OnWidgetDestroying(widget
);
160 #if defined(USE_AURA)
161 aura::Window
* bubble_window
= GetWidget()->GetNativeWindow();
162 aura::client::ActivationClient
* activation_client
=
163 aura::client::GetActivationClient(bubble_window
->GetRootWindow());
164 activation_client
->RemoveObserver(this);
168 void ExtensionPopup::OnWidgetActivationChanged(views::Widget
* widget
,
170 // Dismiss only if the window being activated is not owned by this popup's
171 // window. In particular, don't dismiss when we lose activation to a child
172 // dialog box. Possibly relevant: http://crbug.com/106723 and
173 // http://crbug.com/179786
174 views::Widget
* this_widget
= GetWidget();
176 // TODO(msw): Resolve crashes and remove checks. See: http://crbug.com/327776
177 CHECK(!close_on_deactivate());
181 gfx::NativeView activated_view
= widget
->GetNativeView();
182 gfx::NativeView this_view
= this_widget
->GetNativeView();
183 if (active
&& !inspect_with_devtools_
&& activated_view
!= this_view
&&
184 !IsOwnerOf(activated_view
, this_view
))
185 this_widget
->Close();
188 #if defined(USE_AURA)
189 void ExtensionPopup::OnWindowActivated(aura::Window
* gained_active
,
190 aura::Window
* lost_active
) {
191 // DesktopNativeWidgetAura does not trigger the expected browser widget
192 // [de]activation events when activating widgets in its own root window.
193 // This additional check handles those cases. See: http://crbug.com/320889
194 aura::Window
* this_window
= GetWidget()->GetNativeWindow();
195 aura::Window
* anchor_window
= anchor_widget()->GetNativeWindow();
196 chrome::HostDesktopType host_desktop_type
=
197 chrome::GetHostDesktopTypeForNativeWindow(this_window
);
198 if (!inspect_with_devtools_
&& anchor_window
== gained_active
&&
199 host_desktop_type
!= chrome::HOST_DESKTOP_TYPE_ASH
&&
200 this_window
->GetRootWindow() == anchor_window
->GetRootWindow() &&
201 views::corewm::GetTransientParent(gained_active
) != this_window
)
202 GetWidget()->Close();
206 void ExtensionPopup::ActiveTabChanged(content::WebContents
* old_contents
,
207 content::WebContents
* new_contents
,
210 GetWidget()->Close();
214 ExtensionPopup
* ExtensionPopup::ShowPopup(const GURL
& url
,
216 views::View
* anchor_view
,
217 views::BubbleBorder::Arrow arrow
,
218 ShowAction show_action
) {
219 extensions::ExtensionViewHost
* host
=
220 extensions::ExtensionViewHostFactory::CreatePopupHost(url
, browser
);
221 ExtensionPopup
* popup
= new ExtensionPopup(host
, anchor_view
, arrow
,
223 views::BubbleDelegateView::CreateBubble(popup
);
225 #if defined(USE_AURA)
226 gfx::NativeView native_view
= popup
->GetWidget()->GetNativeView();
227 views::corewm::SetWindowVisibilityAnimationType(
229 views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL
);
230 views::corewm::SetWindowVisibilityAnimationVerticalPosition(
235 // If the host had somehow finished loading, then we'd miss the notification
236 // and not show. This seems to happen in single-process mode.
237 if (host
->did_stop_loading())
240 #if defined(USE_AURA)
241 aura::Window
* bubble_window
= popup
->GetWidget()->GetNativeWindow();
242 aura::client::ActivationClient
* activation_client
=
243 aura::client::GetActivationClient(bubble_window
->GetRootWindow());
244 activation_client
->AddObserver(popup
);
250 void ExtensionPopup::ShowBubble() {
253 // Focus on the host contents when the bubble is first shown.
254 host()->host_contents()->GetView()->Focus();
256 if (inspect_with_devtools_
) {
257 DevToolsWindow::OpenDevToolsWindow(host()->render_view_host(),
258 DevToolsToggleAction::ShowConsole());