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