extension_popup.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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 "base/message_loop/message_loop.h" 9#include "chrome/browser/chrome_notification_types.h" 10#include "chrome/browser/devtools/devtools_window.h" 11#include "chrome/browser/extensions/extension_view_host.h" 12#include "chrome/browser/extensions/extension_view_host_factory.h" 13#include "chrome/browser/platform_util.h" 14#include "chrome/browser/profiles/profile.h" 15#include "chrome/browser/ui/browser.h" 16#include "chrome/browser/ui/browser_window.h" 17#include "chrome/browser/ui/host_desktop.h" 18#include "chrome/browser/ui/views/frame/browser_view.h" 19#include "content/public/browser/devtools_agent_host.h" 20#include "content/public/browser/devtools_manager.h" 21#include "content/public/browser/notification_details.h" 22#include "content/public/browser/notification_source.h" 23#include "content/public/browser/render_view_host.h" 24#include "content/public/browser/web_contents.h" 25#include "content/public/browser/web_contents_view.h" 26#include "ui/gfx/insets.h" 27#include "ui/views/layout/fill_layout.h" 28#include "ui/views/widget/widget.h" 29 30#if defined(USE_AURA) 31#include "ui/aura/client/activation_client.h" 32#include "ui/aura/window.h" 33#include "ui/views/corewm/window_animations.h" 34#endif 35 36#if defined(OS_WIN) 37#include "ui/views/win/hwnd_util.h" 38#endif 39 40using content::BrowserContext; 41using content::RenderViewHost; 42using content::WebContents; 43 44namespace { 45 46// Returns true if |possible_owner| is the owner of |child|. 47bool IsOwnerOf(gfx::NativeView child, gfx::NativeView possible_owner) { 48 if (!child) 49 return false; 50#if defined(OS_WIN) 51 if (::GetWindow(views::HWNDForNativeView(child), GW_OWNER) == 52 views::HWNDForNativeView(possible_owner)) 53 return true; 54#endif 55 return false; 56} 57 58} // namespace 59 60// The minimum/maximum dimensions of the popup. 61// The minimum is just a little larger than the size of the button itself. 62// The maximum is an arbitrary number that should be smaller than most screens. 63const int ExtensionPopup::kMinWidth = 25; 64const int ExtensionPopup::kMinHeight = 25; 65const int ExtensionPopup::kMaxWidth = 800; 66const int ExtensionPopup::kMaxHeight = 600; 67 68ExtensionPopup::ExtensionPopup(extensions::ExtensionViewHost* host, 69 views::View* anchor_view, 70 views::BubbleBorder::Arrow arrow, 71 ShowAction show_action) 72 : BubbleDelegateView(anchor_view, arrow), 73 host_(host), 74 devtools_callback_(base::Bind( 75 &ExtensionPopup::OnDevToolsStateChanged, base::Unretained(this))) { 76 inspect_with_devtools_ = show_action == SHOW_AND_INSPECT; 77 // Adjust the margin so that contents fit better. 78 const int margin = views::BubbleBorder::GetCornerRadius() / 2; 79 set_margins(gfx::Insets(margin, margin, margin, margin)); 80 SetLayoutManager(new views::FillLayout()); 81 AddChildView(host->view()); 82 host->view()->SetContainer(this); 83 // Use OnNativeFocusChange to check for child window activation on deactivate. 84 set_close_on_deactivate(false); 85 // Make the bubble move with its anchor (during inspection, etc.). 86 set_move_with_anchor(true); 87 88 // Wait to show the popup until the contained host finishes loading. 89 registrar_.Add(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME, 90 content::Source<WebContents>(host->host_contents())); 91 92 // Listen for the containing view calling window.close(); 93 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE, 94 content::Source<BrowserContext>(host->browser_context())); 95 content::DevToolsManager::GetInstance()->AddAgentStateCallback( 96 devtools_callback_); 97} 98 99ExtensionPopup::~ExtensionPopup() { 100 content::DevToolsManager::GetInstance()->RemoveAgentStateCallback( 101 devtools_callback_); 102} 103 104void ExtensionPopup::Observe(int type, 105 const content::NotificationSource& source, 106 const content::NotificationDetails& details) { 107 switch (type) { 108 case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME: 109 DCHECK(content::Source<WebContents>(host()->host_contents()) == source); 110 // Show when the content finishes loading and its width is computed. 111 ShowBubble(); 112 break; 113 case chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE: 114 // If we aren't the host of the popup, then disregard the notification. 115 if (content::Details<extensions::ExtensionHost>(host()) == details) 116 GetWidget()->Close(); 117 break; 118 default: 119 NOTREACHED() << L"Received unexpected notification"; 120 } 121} 122 123void ExtensionPopup::OnDevToolsStateChanged( 124 content::DevToolsAgentHost* agent_host, bool attached) { 125 // First check that the devtools are being opened on this popup. 126 if (host()->render_view_host() != agent_host->GetRenderViewHost()) 127 return; 128 129 if (attached) { 130 // Set inspect_with_devtools_ so the popup will be kept open while 131 // the devtools are open. 132 inspect_with_devtools_ = true; 133 } else { 134 // Widget::Close posts a task, which should give the devtools window a 135 // chance to finish detaching from the inspected RenderViewHost. 136 GetWidget()->Close(); 137 } 138} 139 140void ExtensionPopup::OnExtensionSizeChanged(ExtensionViewViews* view) { 141 SizeToContents(); 142} 143 144gfx::Size ExtensionPopup::GetPreferredSize() { 145 // Constrain the size to popup min/max. 146 gfx::Size sz = views::View::GetPreferredSize(); 147 sz.set_width(std::max(kMinWidth, std::min(kMaxWidth, sz.width()))); 148 sz.set_height(std::max(kMinHeight, std::min(kMaxHeight, sz.height()))); 149 return sz; 150} 151 152void ExtensionPopup::OnWidgetDestroying(views::Widget* widget) { 153 BubbleDelegateView::OnWidgetDestroying(widget); 154#if defined(USE_AURA) 155 aura::Window* bubble_window = GetWidget()->GetNativeWindow(); 156 aura::client::ActivationClient* activation_client = 157 aura::client::GetActivationClient(bubble_window->GetRootWindow()); 158 activation_client->RemoveObserver(this); 159#endif 160} 161 162void ExtensionPopup::OnWidgetActivationChanged(views::Widget* widget, 163 bool active) { 164 BubbleDelegateView::OnWidgetActivationChanged(widget, active); 165 // Dismiss only if the window being activated is not owned by this popup's 166 // window. In particular, don't dismiss when we lose activation to a child 167 // dialog box. Possibly relevant: http://crbug.com/106723 and 168 // http://crbug.com/179786 169 views::Widget* this_widget = GetWidget(); 170 gfx::NativeView activated_view = widget->GetNativeView(); 171 gfx::NativeView this_view = this_widget->GetNativeView(); 172 if (active && !inspect_with_devtools_ && activated_view != this_view && 173 !IsOwnerOf(activated_view, this_view)) 174 this_widget->Close(); 175} 176 177#if defined(USE_AURA) 178void ExtensionPopup::OnWindowActivated(aura::Window* gained_active, 179 aura::Window* lost_active) { 180 // DesktopNativeWidgetAura does not trigger the expected browser widget 181 // [de]activation events when activating widgets in its own root window. 182 // This additional check handles those cases. See: http://crbug.com/320889 183 aura::Window* this_window = GetWidget()->GetNativeWindow(); 184 aura::Window* anchor_window = anchor_widget()->GetNativeWindow(); 185 chrome::HostDesktopType host_desktop_type = 186 chrome::GetHostDesktopTypeForNativeWindow(this_window); 187 if (!inspect_with_devtools_ && anchor_window == gained_active && 188 host_desktop_type != chrome::HOST_DESKTOP_TYPE_ASH && 189 this_window->GetRootWindow() == anchor_window->GetRootWindow() && 190 gained_active->transient_parent() != this_window) 191 GetWidget()->Close(); 192} 193#endif 194 195// static 196ExtensionPopup* ExtensionPopup::ShowPopup(const GURL& url, 197 Browser* browser, 198 views::View* anchor_view, 199 views::BubbleBorder::Arrow arrow, 200 ShowAction show_action) { 201 extensions::ExtensionViewHost* host = 202 extensions::ExtensionViewHostFactory::CreatePopupHost(url, browser); 203 ExtensionPopup* popup = new ExtensionPopup(host, anchor_view, arrow, 204 show_action); 205 views::BubbleDelegateView::CreateBubble(popup); 206 207#if defined(USE_AURA) 208 gfx::NativeView native_view = popup->GetWidget()->GetNativeView(); 209 views::corewm::SetWindowVisibilityAnimationType( 210 native_view, 211 views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL); 212 views::corewm::SetWindowVisibilityAnimationVerticalPosition( 213 native_view, 214 -3.0f); 215#endif 216 217 // If the host had somehow finished loading, then we'd miss the notification 218 // and not show. This seems to happen in single-process mode. 219 if (host->did_stop_loading()) 220 popup->ShowBubble(); 221 222#if defined(USE_AURA) 223 aura::Window* bubble_window = popup->GetWidget()->GetNativeWindow(); 224 aura::client::ActivationClient* activation_client = 225 aura::client::GetActivationClient(bubble_window->GetRootWindow()); 226 activation_client->AddObserver(popup); 227#endif 228 229 return popup; 230} 231 232void ExtensionPopup::ShowBubble() { 233 GetWidget()->Show(); 234 235 // Focus on the host contents when the bubble is first shown. 236 host()->host_contents()->GetView()->Focus(); 237 238 if (inspect_with_devtools_) { 239 DevToolsWindow::ToggleDevToolsWindow(host()->render_view_host(), 240 true, 241 DevToolsToggleAction::ShowConsole()); 242 } 243} 244