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