extension_popup.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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( 69 this, 70 extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE, 71 content::Source<content::BrowserContext>(host->browser_context())); 72 content::DevToolsManager::GetInstance()->AddAgentStateCallback( 73 devtools_callback_); 74 75 GetExtensionView(host)->GetBrowser()->tab_strip_model()->AddObserver(this); 76} 77 78ExtensionPopup::~ExtensionPopup() { 79 content::DevToolsManager::GetInstance()->RemoveAgentStateCallback( 80 devtools_callback_); 81 82 GetExtensionView( 83 host_.get())->GetBrowser()->tab_strip_model()->RemoveObserver(this); 84} 85 86void ExtensionPopup::Observe(int type, 87 const content::NotificationSource& source, 88 const content::NotificationDetails& details) { 89 switch (type) { 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. 94 ShowBubble(); 95 break; 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) 99 GetWidget()->Close(); 100 break; 101 default: 102 NOTREACHED() << L"Received unexpected notification"; 103 } 104} 105 106void ExtensionPopup::OnDevToolsStateChanged( 107 content::DevToolsAgentHost* agent_host, 108 bool attached) { 109 // First check that the devtools are being opened on this popup. 110 if (host()->host_contents() != agent_host->GetWebContents()) 111 return; 112 113 if (attached) { 114 // Set inspect_with_devtools_ so the popup will be kept open while 115 // the devtools are open. 116 inspect_with_devtools_ = true; 117 } else { 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(); 121 } 122} 123 124void ExtensionPopup::OnExtensionSizeChanged(ExtensionViewViews* view) { 125 SizeToContents(); 126} 127 128gfx::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()))); 133 return sz; 134} 135 136void 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_); 142} 143 144void 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); 153} 154 155void ExtensionPopup::OnWidgetActivationChanged(views::Widget* widget, 156 bool active) { 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_); 160 CHECK(GetWidget()); 161 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(); 166} 167 168void 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_); 173 CHECK(GetWidget()); 174 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(); 182} 183 184void ExtensionPopup::ActiveTabChanged(content::WebContents* old_contents, 185 content::WebContents* new_contents, 186 int index, 187 int reason) { 188 GetWidget()->Close(); 189} 190 191// static 192ExtensionPopup* ExtensionPopup::ShowPopup(const GURL& url, 193 Browser* browser, 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, 200 show_action); 201 views::BubbleDelegateView::CreateBubble(popup); 202 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); 207 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()) 211 popup->ShowBubble(); 212 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); 217 218 return popup; 219} 220 221void ExtensionPopup::ShowBubble() { 222 GetWidget()->Show(); 223 224 // Focus on the host contents when the bubble is first shown. 225 host()->host_contents()->Focus(); 226 227 if (inspect_with_devtools_) { 228 DevToolsWindow::OpenDevToolsWindow(host()->host_contents(), 229 DevToolsToggleAction::ShowConsole()); 230 } 231} 232