extension_popup.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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.h" 9#include "chrome/browser/debugger/devtools_window.h" 10#include "chrome/browser/extensions/extension_process_manager.h" 11#include "chrome/browser/extensions/extension_system.h" 12#include "chrome/browser/platform_util.h" 13#include "chrome/browser/profiles/profile.h" 14#include "chrome/browser/ui/browser.h" 15#include "chrome/browser/ui/browser_window.h" 16#include "chrome/common/chrome_notification_types.h" 17#include "content/public/browser/notification_details.h" 18#include "content/public/browser/notification_source.h" 19#include "content/public/browser/render_view_host.h" 20#include "content/public/browser/web_contents.h" 21#include "ui/gfx/insets.h" 22#include "ui/views/layout/fill_layout.h" 23#include "ui/views/widget/widget.h" 24 25#if defined(USE_AURA) 26#include "ui/aura/window.h" 27#endif 28 29#if defined(USE_ASH) 30#include "ash/wm/window_animations.h" 31#endif 32 33using content::RenderViewHost; 34using content::WebContents; 35 36namespace { 37 38// Returns true if |possible_parent| is a parent window of |child|. 39bool IsParent(gfx::NativeView child, gfx::NativeView possible_parent) { 40 if (!child) 41 return false; 42#if !defined(USE_AURA) && defined(OS_WIN) 43 if (::GetWindow(child, GW_OWNER) == possible_parent) 44 return true; 45#endif 46 gfx::NativeView parent = child; 47 while ((parent = platform_util::GetParent(parent))) { 48 if (possible_parent == parent) 49 return true; 50 } 51 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( 66 Browser* browser, 67 extensions::ExtensionHost* host, 68 views::View* anchor_view, 69 views::BubbleBorder::ArrowLocation arrow_location, 70 ShowAction show_action) 71 : BubbleDelegateView(anchor_view, arrow_location), 72 extension_host_(host), 73 close_bubble_factory_(this) { 74 inspect_with_devtools_ = show_action == SHOW_AND_INSPECT; 75 // Adjust the margin so that contents fit better. 76 const int margin = views::BubbleBorder::GetCornerRadius() / 2; 77 set_margins(gfx::Insets(margin, margin, margin, margin)); 78 SetLayoutManager(new views::FillLayout()); 79 AddChildView(host->view()); 80 host->view()->SetContainer(this); 81 // Use OnNativeFocusChange to check for child window activation on deactivate. 82 set_close_on_deactivate(false); 83 // Make the bubble move with its anchor (during inspection, etc.). 84 set_move_with_anchor(true); 85 86 // Wait to show the popup until the contained host finishes loading. 87 registrar_.Add(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME, 88 content::Source<WebContents>(host->host_contents())); 89 90 // Listen for the containing view calling window.close(); 91 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE, 92 content::Source<Profile>(host->profile())); 93 94 // Listen for the dev tools opening on this popup, so we can stop it going 95 // away when the dev tools get focus. 96 registrar_.Add(this, content::NOTIFICATION_DEVTOOLS_AGENT_ATTACHED, 97 content::Source<Profile>(host->profile())); 98 99 // Listen for the dev tools closing, so we can close this window if it is 100 // being inspected and the inspector is closed. 101 registrar_.Add(this, content::NOTIFICATION_DEVTOOLS_AGENT_DETACHED, 102 content::Source<content::BrowserContext>(host->profile())); 103} 104 105ExtensionPopup::~ExtensionPopup() { 106 views::WidgetFocusManager::GetInstance()->RemoveFocusChangeListener(this); 107} 108 109void ExtensionPopup::Observe(int type, 110 const content::NotificationSource& source, 111 const content::NotificationDetails& details) { 112 switch (type) { 113 case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME: 114 DCHECK(content::Source<WebContents>(host()->host_contents()) == source); 115 // Show when the content finishes loading and its width is computed. 116 ShowBubble(); 117 break; 118 case chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE: 119 // If we aren't the host of the popup, then disregard the notification. 120 if (content::Details<extensions::ExtensionHost>(host()) == details) 121 GetWidget()->Close(); 122 break; 123 case content::NOTIFICATION_DEVTOOLS_AGENT_DETACHED: 124 // Make sure it's the devtools window that inspecting our popup. 125 // Widget::Close posts a task, which should give the devtools window a 126 // chance to finish detaching from the inspected RenderViewHost. 127 if (content::Details<RenderViewHost>(host()->render_view_host()) == 128 details) { 129 GetWidget()->Close(); 130 } 131 break; 132 case content::NOTIFICATION_DEVTOOLS_AGENT_ATTACHED: 133 // First check that the devtools are being opened on this popup. 134 if (content::Details<RenderViewHost>(host()->render_view_host()) == 135 details) { 136 // Set inspect_with_devtools_ so the popup will be kept open while 137 // the devtools are open. 138 inspect_with_devtools_ = true; 139 } 140 break; 141 default: 142 NOTREACHED() << L"Received unexpected notification"; 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::OnNativeFocusChange(gfx::NativeView focused_before, 159 gfx::NativeView focused_now) { 160 // Don't close if a child of this window is activated (only needed on Win). 161 // ExtensionPopups can create Javascipt dialogs; see crbug.com/106723. 162 gfx::NativeView this_window = GetWidget()->GetNativeView(); 163 if (inspect_with_devtools_ || focused_now == this_window || 164 IsParent(focused_now, this_window)) 165 return; 166 // Delay closing the widget because on Aura, closing right away makes the 167 // activation controller trigger another focus change before the current focus 168 // change is complete. 169 if (!close_bubble_factory_.HasWeakPtrs()) { 170 MessageLoop::current()->PostTask(FROM_HERE, 171 base::Bind(&ExtensionPopup::CloseBubble, 172 close_bubble_factory_.GetWeakPtr())); 173 } 174} 175 176// static 177ExtensionPopup* ExtensionPopup::ShowPopup( 178 const GURL& url, 179 Browser* browser, 180 views::View* anchor_view, 181 views::BubbleBorder::ArrowLocation arrow_location, 182 ShowAction show_action) { 183 ExtensionProcessManager* manager = 184 extensions::ExtensionSystem::Get(browser->profile())->process_manager(); 185 extensions::ExtensionHost* host = manager->CreatePopupHost(url, browser); 186 ExtensionPopup* popup = new ExtensionPopup(browser, host, anchor_view, 187 arrow_location, show_action); 188 views::BubbleDelegateView::CreateBubble(popup); 189 190#if defined(USE_ASH) 191 gfx::NativeView native_view = popup->GetWidget()->GetNativeView(); 192 ash::SetWindowVisibilityAnimationType( 193 native_view, 194 ash::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL); 195 ash::SetWindowVisibilityAnimationVerticalPosition( 196 native_view, 197 -3.0f); 198#endif 199 200 // If the host had somehow finished loading, then we'd miss the notification 201 // and not show. This seems to happen in single-process mode. 202 if (host->did_stop_loading()) 203 popup->ShowBubble(); 204 205 return popup; 206} 207 208void ExtensionPopup::ShowBubble() { 209 Show(); 210 211 // Focus on the host contents when the bubble is first shown. 212 host()->host_contents()->Focus(); 213 214 // Listen for widget focus changes after showing (used for non-aura win). 215 views::WidgetFocusManager::GetInstance()->AddFocusChangeListener(this); 216 217 if (inspect_with_devtools_) { 218 DevToolsWindow::ToggleDevToolsWindow(host()->render_view_host(), 219 true, 220 DEVTOOLS_TOGGLE_ACTION_SHOW_CONSOLE); 221 } 222} 223 224void ExtensionPopup::CloseBubble() { 225 GetWidget()->Close(); 226} 227