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 "ui/gfx/insets.h"
27 #include "ui/views/layout/fill_layout.h"
28 #include "ui/views/widget/widget.h"
31 #include "ui/aura/window.h"
32 #include "ui/wm/core/window_animations.h"
33 #include "ui/wm/core/window_util.h"
34 #include "ui/wm/public/activation_client.h"
38 #include "ui/views/win/hwnd_util.h"
41 using content::BrowserContext
;
42 using content::RenderViewHost
;
43 using content::WebContents
;
47 // Returns true if |possible_owner| is the owner of |child|.
48 bool IsOwnerOf(gfx::NativeView child
, gfx::NativeView possible_owner
) {
52 if (::GetWindow(views::HWNDForNativeView(child
), GW_OWNER
) ==
53 views::HWNDForNativeView(possible_owner
))
61 // The minimum/maximum dimensions of the popup.
62 // The minimum is just a little larger than the size of the button itself.
63 // The maximum is an arbitrary number that should be smaller than most screens.
64 const int ExtensionPopup::kMinWidth
= 25;
65 const int ExtensionPopup::kMinHeight
= 25;
66 const int ExtensionPopup::kMaxWidth
= 800;
67 const int ExtensionPopup::kMaxHeight
= 600;
69 ExtensionPopup::ExtensionPopup(extensions::ExtensionViewHost
* host
,
70 views::View
* anchor_view
,
71 views::BubbleBorder::Arrow arrow
,
72 ShowAction show_action
)
73 : BubbleDelegateView(anchor_view
, arrow
),
75 devtools_callback_(base::Bind(
76 &ExtensionPopup::OnDevToolsStateChanged
, base::Unretained(this))) {
77 inspect_with_devtools_
= show_action
== SHOW_AND_INSPECT
;
78 // Adjust the margin so that contents fit better.
79 const int margin
= views::BubbleBorder::GetCornerRadius() / 2;
80 set_margins(gfx::Insets(margin
, margin
, margin
, margin
));
81 SetLayoutManager(new views::FillLayout());
82 AddChildView(host
->view());
83 host
->view()->set_container(this);
84 // Use OnNativeFocusChange to check for child window activation on deactivate.
85 set_close_on_deactivate(false);
87 // Wait to show the popup until the contained host finishes loading.
88 registrar_
.Add(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME
,
89 content::Source
<WebContents
>(host
->host_contents()));
91 // Listen for the containing view calling window.close();
92 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE
,
93 content::Source
<BrowserContext
>(host
->browser_context()));
94 content::DevToolsManager::GetInstance()->AddAgentStateCallback(
97 host_
->view()->browser()->tab_strip_model()->AddObserver(this);
100 ExtensionPopup::~ExtensionPopup() {
101 content::DevToolsManager::GetInstance()->RemoveAgentStateCallback(
104 host_
->view()->browser()->tab_strip_model()->RemoveObserver(this);
107 void ExtensionPopup::Observe(int type
,
108 const content::NotificationSource
& source
,
109 const content::NotificationDetails
& details
) {
111 case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME
:
112 DCHECK(content::Source
<WebContents
>(host()->host_contents()) == source
);
113 // Show when the content finishes loading and its width is computed.
116 case chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE
:
117 // If we aren't the host of the popup, then disregard the notification.
118 if (content::Details
<extensions::ExtensionHost
>(host()) == details
)
119 GetWidget()->Close();
122 NOTREACHED() << L
"Received unexpected notification";
126 void ExtensionPopup::OnDevToolsStateChanged(
127 content::DevToolsAgentHost
* agent_host
, bool attached
) {
128 // First check that the devtools are being opened on this popup.
129 if (host()->render_view_host() != agent_host
->GetRenderViewHost())
133 // Set inspect_with_devtools_ so the popup will be kept open while
134 // the devtools are open.
135 inspect_with_devtools_
= true;
137 // Widget::Close posts a task, which should give the devtools window a
138 // chance to finish detaching from the inspected RenderViewHost.
139 GetWidget()->Close();
143 void ExtensionPopup::OnExtensionSizeChanged(ExtensionViewViews
* view
) {
147 gfx::Size
ExtensionPopup::GetPreferredSize() {
148 // Constrain the size to popup min/max.
149 gfx::Size sz
= views::View::GetPreferredSize();
150 sz
.set_width(std::max(kMinWidth
, std::min(kMaxWidth
, sz
.width())));
151 sz
.set_height(std::max(kMinHeight
, std::min(kMaxHeight
, sz
.height())));
155 void ExtensionPopup::OnWidgetDestroying(views::Widget
* widget
) {
156 BubbleDelegateView::OnWidgetDestroying(widget
);
157 #if defined(USE_AURA)
158 aura::Window
* bubble_window
= GetWidget()->GetNativeWindow();
159 aura::client::ActivationClient
* activation_client
=
160 aura::client::GetActivationClient(bubble_window
->GetRootWindow());
161 activation_client
->RemoveObserver(this);
165 void ExtensionPopup::OnWidgetActivationChanged(views::Widget
* widget
,
167 // Dismiss only if the window being activated is not owned by this popup's
168 // window. In particular, don't dismiss when we lose activation to a child
169 // dialog box. Possibly relevant: http://crbug.com/106723 and
170 // http://crbug.com/179786
171 views::Widget
* this_widget
= GetWidget();
173 // TODO(msw): Resolve crashes and remove checks. See: http://crbug.com/327776
174 CHECK(!close_on_deactivate());
178 gfx::NativeView activated_view
= widget
->GetNativeView();
179 gfx::NativeView this_view
= this_widget
->GetNativeView();
180 if (active
&& !inspect_with_devtools_
&& activated_view
!= this_view
&&
181 !IsOwnerOf(activated_view
, this_view
))
182 this_widget
->Close();
185 #if defined(USE_AURA)
186 void ExtensionPopup::OnWindowActivated(aura::Window
* gained_active
,
187 aura::Window
* lost_active
) {
188 // DesktopNativeWidgetAura does not trigger the expected browser widget
189 // [de]activation events when activating widgets in its own root window.
190 // This additional check handles those cases. See: http://crbug.com/320889
191 aura::Window
* this_window
= GetWidget()->GetNativeWindow();
192 aura::Window
* anchor_window
= anchor_widget()->GetNativeWindow();
193 chrome::HostDesktopType host_desktop_type
=
194 chrome::GetHostDesktopTypeForNativeWindow(this_window
);
195 if (!inspect_with_devtools_
&& anchor_window
== gained_active
&&
196 host_desktop_type
!= chrome::HOST_DESKTOP_TYPE_ASH
&&
197 this_window
->GetRootWindow() == anchor_window
->GetRootWindow() &&
198 wm::GetTransientParent(gained_active
) != this_window
)
199 GetWidget()->Close();
203 void ExtensionPopup::ActiveTabChanged(content::WebContents
* old_contents
,
204 content::WebContents
* new_contents
,
207 GetWidget()->Close();
211 ExtensionPopup
* ExtensionPopup::ShowPopup(const GURL
& url
,
213 views::View
* anchor_view
,
214 views::BubbleBorder::Arrow arrow
,
215 ShowAction show_action
) {
216 extensions::ExtensionViewHost
* host
=
217 extensions::ExtensionViewHostFactory::CreatePopupHost(url
, browser
);
218 ExtensionPopup
* popup
= new ExtensionPopup(host
, anchor_view
, arrow
,
220 views::BubbleDelegateView::CreateBubble(popup
);
222 #if defined(USE_AURA)
223 gfx::NativeView native_view
= popup
->GetWidget()->GetNativeView();
224 wm::SetWindowVisibilityAnimationType(
226 wm::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL
);
227 wm::SetWindowVisibilityAnimationVerticalPosition(
232 // If the host had somehow finished loading, then we'd miss the notification
233 // and not show. This seems to happen in single-process mode.
234 if (host
->did_stop_loading())
237 #if defined(USE_AURA)
238 aura::Window
* bubble_window
= popup
->GetWidget()->GetNativeWindow();
239 aura::client::ActivationClient
* activation_client
=
240 aura::client::GetActivationClient(bubble_window
->GetRootWindow());
241 activation_client
->AddObserver(popup
);
247 void ExtensionPopup::ShowBubble() {
250 // Focus on the host contents when the bubble is first shown.
251 host()->host_contents()->Focus();
253 if (inspect_with_devtools_
) {
254 DevToolsWindow::OpenDevToolsWindow(host()->render_view_host(),
255 DevToolsToggleAction::ShowConsole());