extension_popup.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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. 4 5#include "chrome/browser/ui/views/extensions/extension_popup.h" 6 7#include "base/bind.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" 27 28namespace { 29 30ExtensionViewViews* GetExtensionView(extensions::ExtensionViewHost* host) { 31 return static_cast<ExtensionViewViews*>(host->view()); 32} 33 34} // namespace 35 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. 39const int ExtensionPopup::kMinWidth = 25; 40const int ExtensionPopup::kMinHeight = 25; 41const int ExtensionPopup::kMaxWidth = 800; 42const int ExtensionPopup::kMaxHeight = 600; 43 44ExtensionPopup::ExtensionPopup(extensions::ExtensionViewHost* host, 45 views::View* anchor_view, 46 views::BubbleBorder::Arrow arrow, 47 ShowAction show_action) 48 : BubbleDelegateView(anchor_view, arrow), 49 host_(host), 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); 62 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())); 66 67 // Listen for the containing view calling window.close(); 68 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE, 69 content::Source<content::BrowserContext>(host->browser_context())); 70 content::DevToolsManager::GetInstance()->AddAgentStateCallback( 71 devtools_callback_); 72 73 GetExtensionView(host)->GetBrowser()->tab_strip_model()->AddObserver(this); 74} 75 76ExtensionPopup::~ExtensionPopup() { 77 content::DevToolsManager::GetInstance()->RemoveAgentStateCallback( 78 devtools_callback_); 79 80 GetExtensionView( 81 host_.get())->GetBrowser()->tab_strip_model()->RemoveObserver(this); 82} 83 84void ExtensionPopup::Observe(int type, 85 const content::NotificationSource& source, 86 const content::NotificationDetails& details) { 87 switch (type) { 88 case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME: 89 DCHECK_EQ(host()->host_contents(), 90 content::Source<content::WebContents>(source).ptr()); 91 // Show when the content finishes loading and its width is computed. 92 ShowBubble(); 93 break; 94 case chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE: 95 // If we aren't the host of the popup, then disregard the notification. 96 if (content::Details<extensions::ExtensionHost>(host()) == details) 97 GetWidget()->Close(); 98 break; 99 default: 100 NOTREACHED() << L"Received unexpected notification"; 101 } 102} 103 104void ExtensionPopup::OnDevToolsStateChanged( 105 content::DevToolsAgentHost* agent_host, 106 bool attached) { 107 // First check that the devtools are being opened on this popup. 108 if (host()->render_view_host() != agent_host->GetRenderViewHost()) 109 return; 110 111 if (attached) { 112 // Set inspect_with_devtools_ so the popup will be kept open while 113 // the devtools are open. 114 inspect_with_devtools_ = true; 115 } else { 116 // Widget::Close posts a task, which should give the devtools window a 117 // chance to finish detaching from the inspected RenderViewHost. 118 GetWidget()->Close(); 119 } 120} 121 122void ExtensionPopup::OnExtensionSizeChanged(ExtensionViewViews* view) { 123 SizeToContents(); 124} 125 126gfx::Size ExtensionPopup::GetPreferredSize() const { 127 // Constrain the size to popup min/max. 128 gfx::Size sz = views::View::GetPreferredSize(); 129 sz.set_width(std::max(kMinWidth, std::min(kMaxWidth, sz.width()))); 130 sz.set_height(std::max(kMinHeight, std::min(kMaxHeight, sz.height()))); 131 return sz; 132} 133 134void ExtensionPopup::ViewHierarchyChanged( 135 const ViewHierarchyChangedDetails& details) { 136 // TODO(msw): Find any remaining crashes related to http://crbug.com/327776 137 // No view hierarchy changes are expected if the widget no longer exists. 138 widget_initialized_ |= details.child == this && details.is_add && GetWidget(); 139 CHECK(GetWidget() || !widget_initialized_); 140} 141 142void ExtensionPopup::OnWidgetDestroying(views::Widget* widget) { 143 BubbleDelegateView::OnWidgetDestroying(widget); 144 aura::Window* bubble_window = GetWidget()->GetNativeWindow(); 145 aura::client::ActivationClient* activation_client = 146 aura::client::GetActivationClient(bubble_window->GetRootWindow()); 147 // If the popup was being inspected with devtools and the browser window was 148 // closed, then the root window and activation client are already destroyed. 149 if (activation_client) 150 activation_client->RemoveObserver(this); 151} 152 153void ExtensionPopup::OnWidgetActivationChanged(views::Widget* widget, 154 bool active) { 155 // TODO(msw): Find any remaining crashes related to http://crbug.com/327776 156 // No calls are expected if the widget isn't initialized or no longer exists. 157 CHECK(widget_initialized_); 158 CHECK(GetWidget()); 159 160 // Close on anchor window activation (ie. user clicked the browser window). 161 if (!inspect_with_devtools_ && widget && active && 162 widget->GetNativeWindow() == anchor_widget()->GetNativeWindow()) 163 GetWidget()->Close(); 164} 165 166void ExtensionPopup::OnWindowActivated(aura::Window* gained_active, 167 aura::Window* lost_active) { 168 // TODO(msw): Find any remaining crashes related to http://crbug.com/327776 169 // No calls are expected if the widget isn't initialized or no longer exists. 170 CHECK(widget_initialized_); 171 CHECK(GetWidget()); 172 173 // Close on anchor window activation (ie. user clicked the browser window). 174 // DesktopNativeWidgetAura does not trigger the expected browser widget 175 // [de]activation events when activating widgets in its own root window. 176 // This additional check handles those cases. See: http://crbug.com/320889 177 if (!inspect_with_devtools_ && 178 gained_active == anchor_widget()->GetNativeWindow()) 179 GetWidget()->Close(); 180} 181 182void ExtensionPopup::ActiveTabChanged(content::WebContents* old_contents, 183 content::WebContents* new_contents, 184 int index, 185 int reason) { 186 GetWidget()->Close(); 187} 188 189// static 190ExtensionPopup* ExtensionPopup::ShowPopup(const GURL& url, 191 Browser* browser, 192 views::View* anchor_view, 193 views::BubbleBorder::Arrow arrow, 194 ShowAction show_action) { 195 extensions::ExtensionViewHost* host = 196 extensions::ExtensionViewHostFactory::CreatePopupHost(url, browser); 197 ExtensionPopup* popup = new ExtensionPopup(host, anchor_view, arrow, 198 show_action); 199 views::BubbleDelegateView::CreateBubble(popup); 200 201 gfx::NativeView native_view = popup->GetWidget()->GetNativeView(); 202 wm::SetWindowVisibilityAnimationType( 203 native_view, wm::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL); 204 wm::SetWindowVisibilityAnimationVerticalPosition(native_view, -3.0f); 205 206 // If the host had somehow finished loading, then we'd miss the notification 207 // and not show. This seems to happen in single-process mode. 208 if (host->did_stop_loading()) 209 popup->ShowBubble(); 210 211 aura::Window* bubble_window = popup->GetWidget()->GetNativeWindow(); 212 aura::client::ActivationClient* activation_client = 213 aura::client::GetActivationClient(bubble_window->GetRootWindow()); 214 activation_client->AddObserver(popup); 215 216 return popup; 217} 218 219void ExtensionPopup::ShowBubble() { 220 GetWidget()->Show(); 221 222 // Focus on the host contents when the bubble is first shown. 223 host()->host_contents()->Focus(); 224 225 if (inspect_with_devtools_) { 226 DevToolsWindow::OpenDevToolsWindow(host()->render_view_host(), 227 DevToolsToggleAction::ShowConsole()); 228 } 229} 230