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