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/notification_details.h"
16 #include "content/public/browser/notification_source.h"
17 #include "content/public/browser/render_view_host.h"
18 #include "content/public/browser/web_contents.h"
19 #include "ui/gfx/geometry/insets.h"
20 #include "ui/views/layout/fill_layout.h"
21 #include "ui/views/widget/widget.h"
25 ExtensionViewViews
* GetExtensionView(extensions::ExtensionViewHost
* host
) {
26 return static_cast<ExtensionViewViews
*>(host
->view());
31 // The minimum/maximum dimensions of the popup.
32 // The minimum is just a little larger than the size of the button itself.
33 // The maximum is an arbitrary number that should be smaller than most screens.
34 const int ExtensionPopup::kMinWidth
= 25;
35 const int ExtensionPopup::kMinHeight
= 25;
36 const int ExtensionPopup::kMaxWidth
= 800;
37 const int ExtensionPopup::kMaxHeight
= 600;
39 #if !defined(USE_AURA)
41 ExtensionPopup
* ExtensionPopup::Create(extensions::ExtensionViewHost
* host
,
42 views::View
* anchor_view
,
43 views::BubbleBorder::Arrow arrow
,
44 ShowAction show_action
) {
45 auto popup
= new ExtensionPopup(host
, anchor_view
, arrow
, show_action
);
46 views::BubbleDelegateView::CreateBubble(popup
);
51 ExtensionPopup::ExtensionPopup(extensions::ExtensionViewHost
* host
,
52 views::View
* anchor_view
,
53 views::BubbleBorder::Arrow arrow
,
54 ShowAction show_action
)
55 : BubbleDelegateView(anchor_view
, arrow
),
57 devtools_callback_(base::Bind(
58 &ExtensionPopup::OnDevToolsStateChanged
, base::Unretained(this))),
59 widget_initialized_(false) {
60 inspect_with_devtools_
= show_action
== SHOW_AND_INSPECT
;
61 // Adjust the margin so that contents fit better.
62 const int margin
= views::BubbleBorder::GetCornerRadius() / 2;
63 set_margins(gfx::Insets(margin
, margin
, margin
, margin
));
64 SetLayoutManager(new views::FillLayout());
65 AddChildView(GetExtensionView(host
));
66 GetExtensionView(host
)->set_container(this);
67 // ExtensionPopup closes itself on very specific de-activation conditions.
68 set_close_on_deactivate(false);
70 // Wait to show the popup until the contained host finishes loading.
71 registrar_
.Add(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME
,
72 content::Source
<content::WebContents
>(host
->host_contents()));
74 // Listen for the containing view calling window.close();
77 extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE
,
78 content::Source
<content::BrowserContext
>(host
->browser_context()));
79 content::DevToolsAgentHost::AddAgentStateCallback(devtools_callback_
);
81 GetExtensionView(host
)->GetBrowser()->tab_strip_model()->AddObserver(this);
84 ExtensionPopup::~ExtensionPopup() {
85 content::DevToolsAgentHost::RemoveAgentStateCallback(devtools_callback_
);
88 host_
.get())->GetBrowser()->tab_strip_model()->RemoveObserver(this);
91 void ExtensionPopup::Observe(int type
,
92 const content::NotificationSource
& source
,
93 const content::NotificationDetails
& details
) {
95 case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME
:
96 DCHECK_EQ(host()->host_contents(),
97 content::Source
<content::WebContents
>(source
).ptr());
98 // Show when the content finishes loading and its width is computed.
101 case extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE
:
102 // If we aren't the host of the popup, then disregard the notification.
103 if (content::Details
<extensions::ExtensionHost
>(host()) == details
)
104 GetWidget()->Close();
107 NOTREACHED() << L
"Received unexpected notification";
111 void ExtensionPopup::OnDevToolsStateChanged(
112 content::DevToolsAgentHost
* agent_host
,
114 // First check that the devtools are being opened on this popup.
115 if (host()->host_contents() != agent_host
->GetWebContents())
119 // Set inspect_with_devtools_ so the popup will be kept open while
120 // the devtools are open.
121 inspect_with_devtools_
= true;
123 // Widget::Close posts a task, which should give the devtools window a
124 // chance to finish detaching from the inspected RenderViewHost.
125 GetWidget()->Close();
129 void ExtensionPopup::OnExtensionSizeChanged(ExtensionViewViews
* view
) {
133 gfx::Size
ExtensionPopup::GetPreferredSize() const {
134 // Constrain the size to popup min/max.
135 gfx::Size sz
= views::View::GetPreferredSize();
136 sz
.set_width(std::max(kMinWidth
, std::min(kMaxWidth
, sz
.width())));
137 sz
.set_height(std::max(kMinHeight
, std::min(kMaxHeight
, sz
.height())));
141 void ExtensionPopup::ViewHierarchyChanged(
142 const ViewHierarchyChangedDetails
& details
) {
143 // TODO(msw): Find any remaining crashes related to http://crbug.com/327776
144 // No view hierarchy changes are expected if the widget no longer exists.
145 widget_initialized_
|= details
.child
== this && details
.is_add
&& GetWidget();
146 CHECK(GetWidget() || !widget_initialized_
);
149 void ExtensionPopup::OnWidgetActivationChanged(views::Widget
* widget
,
151 if (active
&& widget
== anchor_widget())
152 OnAnchorWindowActivation();
155 void ExtensionPopup::ActiveTabChanged(content::WebContents
* old_contents
,
156 content::WebContents
* new_contents
,
159 GetWidget()->Close();
162 void ExtensionPopup::OnAnchorWindowActivation() {
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 if (!inspect_with_devtools_
)
169 GetWidget()->Close();
173 ExtensionPopup
* ExtensionPopup::ShowPopup(const GURL
& url
,
175 views::View
* anchor_view
,
176 views::BubbleBorder::Arrow arrow
,
177 ShowAction show_action
) {
178 extensions::ExtensionViewHost
* host
=
179 extensions::ExtensionViewHostFactory::CreatePopupHost(url
, browser
);
180 auto popup
= ExtensionPopup::Create(host
, anchor_view
, arrow
, show_action
);
182 // If the host had somehow finished loading, then we'd miss the notification
183 // and not show. This seems to happen in single-process mode.
184 if (host
->has_loaded_once())
190 void ExtensionPopup::ShowBubble() {
193 // Focus on the host contents when the bubble is first shown.
194 host()->host_contents()->Focus();
196 if (inspect_with_devtools_
) {
197 DevToolsWindow::OpenDevToolsWindow(host()->host_contents(),
198 DevToolsToggleAction::ShowConsole());