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 38 using content::RenderViewHost; 39 using content::WebContents; 40 41 namespace { 42 43 // Returns true if |possible_owner| is the owner of |child|. 44 bool 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. 60 const int ExtensionPopup::kMinWidth = 25; 61 const int ExtensionPopup::kMinHeight = 25; 62 const int ExtensionPopup::kMaxWidth = 800; 63 const int ExtensionPopup::kMaxHeight = 600; 64 65 ExtensionPopup::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 96 ExtensionPopup::~ExtensionPopup() { 97 content::DevToolsManager::GetInstance()->RemoveAgentStateCallback( 98 devtools_callback_); 99 } 100 101 void 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 120 void 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 137 void ExtensionPopup::OnExtensionSizeChanged(ExtensionViewViews* view) { 138 SizeToContents(); 139 } 140 141 gfx::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 149 void 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 165 ExtensionPopup* 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 195 void 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