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 "chrome/browser/chrome_notification_types.h"
9 #include "chrome/browser/devtools/devtools_window.h"
10 #include "chrome/browser/extensions/extension_view_host.h"
11 #include "chrome/browser/extensions/extension_view_host_factory.h"
12 #include "chrome/browser/ui/browser.h"
13 #include "chrome/browser/ui/tabs/tab_strip_model.h"
14 #include "content/public/browser/devtools_agent_host.h"
15 #include "content/public/browser/devtools_manager.h"
16 #include "content/public/browser/notification_details.h"
17 #include "content/public/browser/notification_source.h"
18 #include "content/public/browser/render_view_host.h"
19 #include "content/public/browser/web_contents.h"
20 #include "ui/aura/window.h"
21 #include "ui/gfx/insets.h"
22 #include "ui/views/layout/fill_layout.h"
23 #include "ui/views/widget/widget.h"
24 #include "ui/wm/core/window_animations.h"
25 #include "ui/wm/core/window_util.h"
26 #include "ui/wm/public/activation_client.h"
29 #include "ui/views/win/hwnd_util.h"
32 // The minimum/maximum dimensions of the popup.
33 // The minimum is just a little larger than the size of the button itself.
34 // The maximum is an arbitrary number that should be smaller than most screens.
35 const int ExtensionPopup::kMinWidth
= 25;
36 const int ExtensionPopup::kMinHeight
= 25;
37 const int ExtensionPopup::kMaxWidth
= 800;
38 const int ExtensionPopup::kMaxHeight
= 600;
40 ExtensionPopup::ExtensionPopup(extensions::ExtensionViewHost
* host
,
41 views::View
* anchor_view
,
42 views::BubbleBorder::Arrow arrow
,
43 ShowAction show_action
)
44 : BubbleDelegateView(anchor_view
, arrow
),
46 devtools_callback_(base::Bind(
47 &ExtensionPopup::OnDevToolsStateChanged
, base::Unretained(this))),
48 widget_initialized_(false) {
49 inspect_with_devtools_
= show_action
== SHOW_AND_INSPECT
;
50 // Adjust the margin so that contents fit better.
51 const int margin
= views::BubbleBorder::GetCornerRadius() / 2;
52 set_margins(gfx::Insets(margin
, margin
, margin
, margin
));
53 SetLayoutManager(new views::FillLayout());
54 AddChildView(host
->view());
55 host
->view()->set_container(this);
56 // ExtensionPopup closes itself on very specific de-activation conditions.
57 set_close_on_deactivate(false);
59 // Wait to show the popup until the contained host finishes loading.
60 registrar_
.Add(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME
,
61 content::Source
<content::WebContents
>(host
->host_contents()));
63 // Listen for the containing view calling window.close();
64 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE
,
65 content::Source
<content::BrowserContext
>(host
->browser_context()));
66 content::DevToolsManager::GetInstance()->AddAgentStateCallback(
69 host_
->view()->browser()->tab_strip_model()->AddObserver(this);
72 ExtensionPopup::~ExtensionPopup() {
73 content::DevToolsManager::GetInstance()->RemoveAgentStateCallback(
76 host_
->view()->browser()->tab_strip_model()->RemoveObserver(this);
79 void ExtensionPopup::Observe(int type
,
80 const content::NotificationSource
& source
,
81 const content::NotificationDetails
& details
) {
83 case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME
:
84 DCHECK_EQ(host()->host_contents(),
85 content::Source
<content::WebContents
>(source
).ptr());
86 // Show when the content finishes loading and its width is computed.
89 case chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE
:
90 // If we aren't the host of the popup, then disregard the notification.
91 if (content::Details
<extensions::ExtensionHost
>(host()) == details
)
95 NOTREACHED() << L
"Received unexpected notification";
99 void ExtensionPopup::OnDevToolsStateChanged(
100 content::DevToolsAgentHost
* agent_host
,
102 // First check that the devtools are being opened on this popup.
103 if (host()->render_view_host() != agent_host
->GetRenderViewHost())
107 // Set inspect_with_devtools_ so the popup will be kept open while
108 // the devtools are open.
109 inspect_with_devtools_
= true;
111 // Widget::Close posts a task, which should give the devtools window a
112 // chance to finish detaching from the inspected RenderViewHost.
113 GetWidget()->Close();
117 void ExtensionPopup::OnExtensionSizeChanged(ExtensionViewViews
* view
) {
121 gfx::Size
ExtensionPopup::GetPreferredSize() const {
122 // Constrain the size to popup min/max.
123 gfx::Size sz
= views::View::GetPreferredSize();
124 sz
.set_width(std::max(kMinWidth
, std::min(kMaxWidth
, sz
.width())));
125 sz
.set_height(std::max(kMinHeight
, std::min(kMaxHeight
, sz
.height())));
129 void ExtensionPopup::ViewHierarchyChanged(
130 const ViewHierarchyChangedDetails
& details
) {
131 // TODO(msw): Find any remaining crashes related to http://crbug.com/327776
132 // No view hierarchy changes are expected if the widget no longer exists.
133 widget_initialized_
|= details
.child
== this && details
.is_add
&& GetWidget();
134 CHECK(GetWidget() || !widget_initialized_
);
137 void ExtensionPopup::OnWidgetDestroying(views::Widget
* widget
) {
138 BubbleDelegateView::OnWidgetDestroying(widget
);
139 aura::Window
* bubble_window
= GetWidget()->GetNativeWindow();
140 aura::client::ActivationClient
* activation_client
=
141 aura::client::GetActivationClient(bubble_window
->GetRootWindow());
142 // If the popup was being inspected with devtools and the browser window was
143 // closed, then the root window and activation client are already destroyed.
144 if (activation_client
)
145 activation_client
->RemoveObserver(this);
148 void ExtensionPopup::OnWidgetActivationChanged(views::Widget
* widget
,
150 // TODO(msw): Find any remaining crashes related to http://crbug.com/327776
151 // No calls are expected if the widget isn't initialized or no longer exists.
152 CHECK(widget_initialized_
);
155 // Close on anchor window activation (ie. user clicked the browser window).
156 if (!inspect_with_devtools_
&& widget
&& active
&&
157 widget
->GetNativeWindow() == anchor_widget()->GetNativeWindow())
158 GetWidget()->Close();
161 void ExtensionPopup::OnWindowActivated(aura::Window
* gained_active
,
162 aura::Window
* lost_active
) {
163 // TODO(msw): Find any remaining crashes related to http://crbug.com/327776
164 // No calls are expected if the widget isn't initialized or no longer exists.
165 CHECK(widget_initialized_
);
168 // Close on anchor window activation (ie. user clicked the browser window).
169 // DesktopNativeWidgetAura does not trigger the expected browser widget
170 // [de]activation events when activating widgets in its own root window.
171 // This additional check handles those cases. See: http://crbug.com/320889
172 if (!inspect_with_devtools_
&&
173 gained_active
== anchor_widget()->GetNativeWindow())
174 GetWidget()->Close();
177 void ExtensionPopup::ActiveTabChanged(content::WebContents
* old_contents
,
178 content::WebContents
* new_contents
,
181 GetWidget()->Close();
185 ExtensionPopup
* ExtensionPopup::ShowPopup(const GURL
& url
,
187 views::View
* anchor_view
,
188 views::BubbleBorder::Arrow arrow
,
189 ShowAction show_action
) {
190 extensions::ExtensionViewHost
* host
=
191 extensions::ExtensionViewHostFactory::CreatePopupHost(url
, browser
);
192 ExtensionPopup
* popup
= new ExtensionPopup(host
, anchor_view
, arrow
,
194 views::BubbleDelegateView::CreateBubble(popup
);
196 gfx::NativeView native_view
= popup
->GetWidget()->GetNativeView();
197 wm::SetWindowVisibilityAnimationType(
198 native_view
, wm::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL
);
199 wm::SetWindowVisibilityAnimationVerticalPosition(native_view
, -3.0f
);
201 // If the host had somehow finished loading, then we'd miss the notification
202 // and not show. This seems to happen in single-process mode.
203 if (host
->did_stop_loading())
206 aura::Window
* bubble_window
= popup
->GetWidget()->GetNativeWindow();
207 aura::client::ActivationClient
* activation_client
=
208 aura::client::GetActivationClient(bubble_window
->GetRootWindow());
209 activation_client
->AddObserver(popup
);
214 void ExtensionPopup::ShowBubble() {
217 // Focus on the host contents when the bubble is first shown.
218 host()->host_contents()->Focus();
220 if (inspect_with_devtools_
) {
221 DevToolsWindow::OpenDevToolsWindow(host()->render_view_host(),
222 DevToolsToggleAction::ShowConsole());