1 // Copyright (c) 2011 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 <vector> 8 9 #include "chrome/browser/debugger/devtools_manager.h" 10 #include "chrome/browser/debugger/devtools_toggle_action.h" 11 #include "chrome/browser/extensions/extension_host.h" 12 #include "chrome/browser/extensions/extension_process_manager.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/browser/ui/views/frame/browser_view.h" 17 #include "chrome/common/extensions/extension.h" 18 #include "content/browser/renderer_host/render_view_host.h" 19 #include "content/browser/renderer_host/render_widget_host_view.h" 20 #include "content/common/notification_details.h" 21 #include "content/common/notification_source.h" 22 #include "content/common/notification_type.h" 23 #include "views/widget/root_view.h" 24 #include "views/window/window.h" 25 26 #if defined(OS_LINUX) 27 #include "views/widget/widget_gtk.h" 28 #endif 29 30 #if defined(OS_CHROMEOS) 31 #include "chrome/browser/chromeos/wm_ipc.h" 32 #include "third_party/cros/chromeos_wm_ipc_enums.h" 33 #endif 34 35 using std::vector; 36 using views::Widget; 37 38 // The minimum/maximum dimensions of the popup. 39 // The minimum is just a little larger than the size of the button itself. 40 // The maximum is an arbitrary number that should be smaller than most screens. 41 const int ExtensionPopup::kMinWidth = 25; 42 const int ExtensionPopup::kMinHeight = 25; 43 const int ExtensionPopup::kMaxWidth = 800; 44 const int ExtensionPopup::kMaxHeight = 600; 45 46 ExtensionPopup::ExtensionPopup(ExtensionHost* host, 47 views::Widget* frame, 48 const gfx::Rect& relative_to, 49 BubbleBorder::ArrowLocation arrow_location, 50 bool inspect_with_devtools, 51 Observer* observer) 52 : BrowserBubble(host->view(), 53 frame, 54 relative_to, 55 arrow_location), 56 relative_to_(relative_to), 57 extension_host_(host), 58 inspect_with_devtools_(inspect_with_devtools), 59 close_on_lost_focus_(true), 60 closing_(false), 61 observer_(observer) { 62 AddRef(); // Balanced in Close(); 63 set_delegate(this); 64 host->view()->SetContainer(this); 65 66 // We wait to show the popup until the contained host finishes loading. 67 registrar_.Add(this, 68 NotificationType::EXTENSION_HOST_DID_STOP_LOADING, 69 Source<Profile>(host->profile())); 70 71 // Listen for the containing view calling window.close(); 72 registrar_.Add(this, NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE, 73 Source<Profile>(host->profile())); 74 } 75 76 ExtensionPopup::~ExtensionPopup() { 77 } 78 79 void ExtensionPopup::Show(bool activate) { 80 if (visible()) 81 return; 82 83 #if defined(OS_WIN) 84 frame_->GetWindow()->DisableInactiveRendering(); 85 #endif 86 87 ResizeToView(); 88 BrowserBubble::Show(activate); 89 } 90 91 void ExtensionPopup::BubbleBrowserWindowMoved(BrowserBubble* bubble) { 92 ResizeToView(); 93 } 94 95 void ExtensionPopup::BubbleBrowserWindowClosing(BrowserBubble* bubble) { 96 if (!closing_) 97 Close(); 98 } 99 100 void ExtensionPopup::BubbleGotFocus(BrowserBubble* bubble) { 101 // Forward the focus to the renderer. 102 host()->render_view_host()->view()->Focus(); 103 } 104 105 void ExtensionPopup::BubbleLostFocus(BrowserBubble* bubble, 106 bool lost_focus_to_child) { 107 if (closing_ || // We are already closing. 108 inspect_with_devtools_ || // The popup is being inspected. 109 !close_on_lost_focus_ || // Our client is handling focus listening. 110 lost_focus_to_child) // A child of this view got focus. 111 return; 112 113 // When we do close on BubbleLostFocus, we do it in the next event loop 114 // because a subsequent event in this loop may also want to close this popup 115 // and if so, we want to allow that. Example: Clicking the same browser 116 // action button that opened the popup. If we closed immediately, the 117 // browser action container would fail to discover that the same button 118 // was pressed. 119 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(this, 120 &ExtensionPopup::Close)); 121 } 122 123 124 void ExtensionPopup::Observe(NotificationType type, 125 const NotificationSource& source, 126 const NotificationDetails& details) { 127 switch (type.value) { 128 case NotificationType::EXTENSION_HOST_DID_STOP_LOADING: 129 // Once we receive did stop loading, the content will be complete and 130 // the width will have been computed. Now it's safe to show. 131 if (extension_host_.get() == Details<ExtensionHost>(details).ptr()) { 132 Show(true); 133 134 if (inspect_with_devtools_) { 135 // Listen for the the devtools window closing. 136 registrar_.Add(this, NotificationType::DEVTOOLS_WINDOW_CLOSING, 137 Source<Profile>(extension_host_->profile())); 138 DevToolsManager::GetInstance()->ToggleDevToolsWindow( 139 extension_host_->render_view_host(), 140 DEVTOOLS_TOGGLE_ACTION_SHOW_CONSOLE); 141 } 142 } 143 break; 144 case NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE: 145 // If we aren't the host of the popup, then disregard the notification. 146 if (Details<ExtensionHost>(host()) != details) 147 return; 148 Close(); 149 150 break; 151 case NotificationType::DEVTOOLS_WINDOW_CLOSING: 152 // Make sure its the devtools window that inspecting our popup. 153 if (Details<RenderViewHost>(extension_host_->render_view_host()) != 154 details) 155 return; 156 157 // If the devtools window is closing, we post a task to ourselves to 158 // close the popup. This gives the devtools window a chance to finish 159 // detaching from the inspected RenderViewHost. 160 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(this, 161 &ExtensionPopup::Close)); 162 163 break; 164 default: 165 NOTREACHED() << L"Received unexpected notification"; 166 } 167 } 168 169 void ExtensionPopup::OnExtensionPreferredSizeChanged(ExtensionView* view) { 170 // Constrain the size to popup min/max. 171 gfx::Size sz = view->GetPreferredSize(); 172 view->SetBounds(view->x(), view->y(), 173 std::max(kMinWidth, std::min(kMaxWidth, sz.width())), 174 std::max(kMinHeight, std::min(kMaxHeight, sz.height()))); 175 176 ResizeToView(); 177 } 178 179 // static 180 ExtensionPopup* ExtensionPopup::Show( 181 const GURL& url, 182 Browser* browser, 183 const gfx::Rect& relative_to, 184 BubbleBorder::ArrowLocation arrow_location, 185 bool inspect_with_devtools, 186 Observer* observer) { 187 ExtensionProcessManager* manager = 188 browser->profile()->GetExtensionProcessManager(); 189 DCHECK(manager); 190 if (!manager) 191 return NULL; 192 193 ExtensionHost* host = manager->CreatePopup(url, browser); 194 views::Widget* frame = BrowserView::GetBrowserViewForNativeWindow( 195 browser->window()->GetNativeHandle())->GetWidget(); 196 ExtensionPopup* popup = new ExtensionPopup(host, frame, relative_to, 197 arrow_location, 198 inspect_with_devtools, observer); 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->Show(true); 204 205 return popup; 206 } 207 208 void ExtensionPopup::Close() { 209 if (closing_) 210 return; 211 closing_ = true; 212 DetachFromBrowser(); 213 214 if (observer_) 215 observer_->ExtensionPopupIsClosing(this); 216 217 Release(); // Balanced in ctor. 218 } 219