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/ui/browser.h"
12 #include "chrome/browser/ui/tabs/tab_strip_model.h"
13 #include "content/public/browser/devtools_agent_host.h"
14 #include "content/public/browser/notification_details.h"
15 #include "content/public/browser/notification_source.h"
16 #include "content/public/browser/render_view_host.h"
17 #include "content/public/browser/web_contents.h"
18 #include "ui/gfx/geometry/insets.h"
19 #include "ui/views/layout/fill_layout.h"
20 #include "ui/views/widget/widget.h"
24 ExtensionViewViews
* GetExtensionView(extensions::ExtensionViewHost
* host
) {
25 return static_cast<ExtensionViewViews
*>(host
->view());
30 // The minimum/maximum dimensions of the popup.
31 // The minimum is just a little larger than the size of the button itself.
32 // The maximum is an arbitrary number that should be smaller than most screens.
33 const int ExtensionPopup::kMinWidth
= 25;
34 const int ExtensionPopup::kMinHeight
= 25;
35 const int ExtensionPopup::kMaxWidth
= 800;
36 const int ExtensionPopup::kMaxHeight
= 600;
38 #if !defined(USE_AURA)
40 ExtensionPopup
* ExtensionPopup::Create(extensions::ExtensionViewHost
* host
,
41 views::View
* anchor_view
,
42 views::BubbleBorder::Arrow arrow
,
43 ShowAction show_action
) {
44 auto popup
= new ExtensionPopup(host
, anchor_view
, arrow
, show_action
);
45 views::BubbleDelegateView::CreateBubble(popup
);
50 ExtensionPopup::ExtensionPopup(extensions::ExtensionViewHost
* host
,
51 views::View
* anchor_view
,
52 views::BubbleBorder::Arrow arrow
,
53 ShowAction show_action
)
54 : BubbleDelegateView(anchor_view
, arrow
),
56 devtools_callback_(base::Bind(
57 &ExtensionPopup::OnDevToolsStateChanged
, base::Unretained(this))),
58 widget_initialized_(false) {
59 inspect_with_devtools_
= show_action
== SHOW_AND_INSPECT
;
60 // Adjust the margin so that contents fit better.
61 const int margin
= views::BubbleBorder::GetCornerRadius() / 2;
62 set_margins(gfx::Insets(margin
, margin
, margin
, margin
));
63 SetLayoutManager(new views::FillLayout());
64 AddChildView(GetExtensionView(host
));
65 GetExtensionView(host
)->set_container(this);
66 // ExtensionPopup closes itself on very specific de-activation conditions.
67 set_close_on_deactivate(false);
70 // Listen for the containing view calling window.close();
73 extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE
,
74 content::Source
<content::BrowserContext
>(host
->browser_context()));
75 content::DevToolsAgentHost::AddAgentStateCallback(devtools_callback_
);
77 GetExtensionView(host
)->GetBrowser()->tab_strip_model()->AddObserver(this);
79 // If the host had somehow finished loading, then we'd miss the notification
80 // and not show. This seems to happen in single-process mode.
81 if (host_
->has_loaded_once()) {
84 // Wait to show the popup until the contained host finishes loading.
86 content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME
,
87 content::Source
<content::WebContents
>(
88 host_
->host_contents()));
92 ExtensionPopup::~ExtensionPopup() {
93 content::DevToolsAgentHost::RemoveAgentStateCallback(devtools_callback_
);
96 host_
.get())->GetBrowser()->tab_strip_model()->RemoveObserver(this);
99 void ExtensionPopup::Observe(int type
,
100 const content::NotificationSource
& source
,
101 const content::NotificationDetails
& details
) {
103 case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME
:
104 DCHECK_EQ(host()->host_contents(),
105 content::Source
<content::WebContents
>(source
).ptr());
106 // Show when the content finishes loading and its width is computed.
109 case extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE
:
110 // If we aren't the host of the popup, then disregard the notification.
111 if (content::Details
<extensions::ExtensionHost
>(host()) == details
)
112 GetWidget()->Close();
115 NOTREACHED() << L
"Received unexpected notification";
119 void ExtensionPopup::OnDevToolsStateChanged(
120 content::DevToolsAgentHost
* agent_host
,
122 // First check that the devtools are being opened on this popup.
123 if (host()->host_contents() != agent_host
->GetWebContents())
127 // Set inspect_with_devtools_ so the popup will be kept open while
128 // the devtools are open.
129 inspect_with_devtools_
= true;
131 // Widget::Close posts a task, which should give the devtools window a
132 // chance to finish detaching from the inspected RenderViewHost.
133 GetWidget()->Close();
137 void ExtensionPopup::OnExtensionSizeChanged(ExtensionViewViews
* view
) {
141 gfx::Size
ExtensionPopup::GetPreferredSize() const {
142 // Constrain the size to popup min/max.
143 gfx::Size sz
= views::View::GetPreferredSize();
144 sz
.set_width(std::max(kMinWidth
, std::min(kMaxWidth
, sz
.width())));
145 sz
.set_height(std::max(kMinHeight
, std::min(kMaxHeight
, sz
.height())));
149 void ExtensionPopup::ViewHierarchyChanged(
150 const ViewHierarchyChangedDetails
& details
) {
151 // TODO(msw): Find any remaining crashes related to http://crbug.com/327776
152 // No view hierarchy changes are expected if the widget no longer exists.
153 widget_initialized_
|= details
.child
== this && details
.is_add
&& GetWidget();
154 CHECK(GetWidget() || !widget_initialized_
);
157 void ExtensionPopup::OnWidgetActivationChanged(views::Widget
* widget
,
159 if (active
&& widget
== anchor_widget())
160 OnAnchorWindowActivation();
163 void ExtensionPopup::ActiveTabChanged(content::WebContents
* old_contents
,
164 content::WebContents
* new_contents
,
167 GetWidget()->Close();
170 void ExtensionPopup::OnAnchorWindowActivation() {
171 // TODO(msw): Find any remaining crashes related to http://crbug.com/327776
172 // No calls are expected if the widget isn't initialized or no longer exists.
173 CHECK(widget_initialized_
);
176 if (!inspect_with_devtools_
)
177 GetWidget()->Close();
181 ExtensionPopup
* ExtensionPopup::ShowPopup(
182 scoped_ptr
<extensions::ExtensionViewHost
> host
,
183 views::View
* anchor_view
,
184 views::BubbleBorder::Arrow arrow
,
185 ShowAction show_action
) {
186 return ExtensionPopup::Create(
187 host
.release(), anchor_view
, arrow
, show_action
);
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());