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"
30 ExtensionViewViews* GetExtensionView(extensions::ExtensionViewHost* host) {
31 return static_cast<ExtensionViewViews*>(host->view());
36 // The minimum/maximum dimensions of the popup.
37 // The minimum is just a little larger than the size of the button itself.
38 // The maximum is an arbitrary number that should be smaller than most screens.
39 const int ExtensionPopup::kMinWidth = 25;
40 const int ExtensionPopup::kMinHeight = 25;
41 const int ExtensionPopup::kMaxWidth = 800;
42 const int ExtensionPopup::kMaxHeight = 600;
44 ExtensionPopup::ExtensionPopup(extensions::ExtensionViewHost* host,
45 views::View* anchor_view,
46 views::BubbleBorder::Arrow arrow,
47 ShowAction show_action)
48 : BubbleDelegateView(anchor_view, arrow),
50 devtools_callback_(base::Bind(
51 &ExtensionPopup::OnDevToolsStateChanged, base::Unretained(this))),
52 widget_initialized_(false) {
53 inspect_with_devtools_ = show_action == SHOW_AND_INSPECT;
54 // Adjust the margin so that contents fit better.
55 const int margin = views::BubbleBorder::GetCornerRadius() / 2;
56 set_margins(gfx::Insets(margin, margin, margin, margin));
57 SetLayoutManager(new views::FillLayout());
58 AddChildView(GetExtensionView(host));
59 GetExtensionView(host)->set_container(this);
60 // ExtensionPopup closes itself on very specific de-activation conditions.
61 set_close_on_deactivate(false);
63 // Wait to show the popup until the contained host finishes loading.
64 registrar_.Add(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
65 content::Source<content::WebContents>(host->host_contents()));
67 // Listen for the containing view calling window.close();
70 extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE,
71 content::Source<content::BrowserContext>(host->browser_context()));
72 content::DevToolsManager::GetInstance()->AddAgentStateCallback(
75 GetExtensionView(host)->GetBrowser()->tab_strip_model()->AddObserver(this);
78 ExtensionPopup::~ExtensionPopup() {
79 content::DevToolsManager::GetInstance()->RemoveAgentStateCallback(
83 host_.get())->GetBrowser()->tab_strip_model()->RemoveObserver(this);
86 void ExtensionPopup::Observe(int type,
87 const content::NotificationSource& source,
88 const content::NotificationDetails& details) {
90 case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME:
91 DCHECK_EQ(host()->host_contents(),
92 content::Source<content::WebContents>(source).ptr());
93 // Show when the content finishes loading and its width is computed.
96 case extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE:
97 // If we aren't the host of the popup, then disregard the notification.
98 if (content::Details<extensions::ExtensionHost>(host()) == details)
102 NOTREACHED() << L"Received unexpected notification";
106 void ExtensionPopup::OnDevToolsStateChanged(
107 content::DevToolsAgentHost* agent_host,
109 // First check that the devtools are being opened on this popup.
110 if (host()->host_contents() != agent_host->GetWebContents())
114 // Set inspect_with_devtools_ so the popup will be kept open while
115 // the devtools are open.
116 inspect_with_devtools_ = true;
118 // Widget::Close posts a task, which should give the devtools window a
119 // chance to finish detaching from the inspected RenderViewHost.
120 GetWidget()->Close();
124 void ExtensionPopup::OnExtensionSizeChanged(ExtensionViewViews* view) {
128 gfx::Size ExtensionPopup::GetPreferredSize() const {
129 // Constrain the size to popup min/max.
130 gfx::Size sz = views::View::GetPreferredSize();
131 sz.set_width(std::max(kMinWidth, std::min(kMaxWidth, sz.width())));
132 sz.set_height(std::max(kMinHeight, std::min(kMaxHeight, sz.height())));
136 void ExtensionPopup::ViewHierarchyChanged(
137 const ViewHierarchyChangedDetails& details) {
138 // TODO(msw): Find any remaining crashes related to http://crbug.com/327776
139 // No view hierarchy changes are expected if the widget no longer exists.
140 widget_initialized_ |= details.child == this && details.is_add && GetWidget();
141 CHECK(GetWidget() || !widget_initialized_);
144 void ExtensionPopup::OnWidgetDestroying(views::Widget* widget) {
145 BubbleDelegateView::OnWidgetDestroying(widget);
146 aura::Window* bubble_window = GetWidget()->GetNativeWindow();
147 aura::client::ActivationClient* activation_client =
148 aura::client::GetActivationClient(bubble_window->GetRootWindow());
149 // If the popup was being inspected with devtools and the browser window was
150 // closed, then the root window and activation client are already destroyed.
151 if (activation_client)
152 activation_client->RemoveObserver(this);
155 void ExtensionPopup::OnWidgetActivationChanged(views::Widget* widget,
157 // TODO(msw): Find any remaining crashes related to http://crbug.com/327776
158 // No calls are expected if the widget isn't initialized or no longer exists.
159 CHECK(widget_initialized_);
162 // Close on anchor window activation (ie. user clicked the browser window).
163 if (!inspect_with_devtools_ && widget && active &&
164 widget->GetNativeWindow() == anchor_widget()->GetNativeWindow())
165 GetWidget()->Close();
168 void ExtensionPopup::OnWindowActivated(aura::Window* gained_active,
169 aura::Window* lost_active) {
170 // TODO(msw): Find any remaining crashes related to http://crbug.com/327776
171 // No calls are expected if the widget isn't initialized or no longer exists.
172 CHECK(widget_initialized_);
175 // Close on anchor window activation (ie. user clicked the browser window).
176 // DesktopNativeWidgetAura does not trigger the expected browser widget
177 // [de]activation events when activating widgets in its own root window.
178 // This additional check handles those cases. See: http://crbug.com/320889
179 if (!inspect_with_devtools_ &&
180 gained_active == anchor_widget()->GetNativeWindow())
181 GetWidget()->Close();
184 void ExtensionPopup::ActiveTabChanged(content::WebContents* old_contents,
185 content::WebContents* new_contents,
188 GetWidget()->Close();
192 ExtensionPopup* ExtensionPopup::ShowPopup(const GURL& url,
194 views::View* anchor_view,
195 views::BubbleBorder::Arrow arrow,
196 ShowAction show_action) {
197 extensions::ExtensionViewHost* host =
198 extensions::ExtensionViewHostFactory::CreatePopupHost(url, browser);
199 ExtensionPopup* popup = new ExtensionPopup(host, anchor_view, arrow,
201 views::BubbleDelegateView::CreateBubble(popup);
203 gfx::NativeView native_view = popup->GetWidget()->GetNativeView();
204 wm::SetWindowVisibilityAnimationType(
205 native_view, wm::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL);
206 wm::SetWindowVisibilityAnimationVerticalPosition(native_view, -3.0f);
208 // If the host had somehow finished loading, then we'd miss the notification
209 // and not show. This seems to happen in single-process mode.
210 if (host->did_stop_loading())
213 aura::Window* bubble_window = popup->GetWidget()->GetNativeWindow();
214 aura::client::ActivationClient* activation_client =
215 aura::client::GetActivationClient(bubble_window->GetRootWindow());
216 activation_client->AddObserver(popup);
221 void ExtensionPopup::ShowBubble() {
224 // Focus on the host contents when the bubble is first shown.
225 host()->host_contents()->Focus();
227 if (inspect_with_devtools_) {
228 DevToolsWindow::OpenDevToolsWindow(host()->host_contents(),
229 DevToolsToggleAction::ShowConsole());