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