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 "chrome/browser/chrome_notification_types.h" 9 #include "chrome/browser/devtools/devtools_window.h" 10 #include "chrome/browser/extensions/extension_view_host.h" 11 #include "chrome/browser/extensions/extension_view_host_factory.h" 12 #include "chrome/browser/ui/browser.h" 13 #include "chrome/browser/ui/tabs/tab_strip_model.h" 14 #include "content/public/browser/devtools_agent_host.h" 15 #include "content/public/browser/notification_details.h" 16 #include "content/public/browser/notification_source.h" 17 #include "content/public/browser/render_view_host.h" 18 #include "content/public/browser/web_contents.h" 19 #include "ui/aura/window.h" 20 #include "ui/gfx/insets.h" 21 #include "ui/views/layout/fill_layout.h" 22 #include "ui/views/widget/widget.h" 23 #include "ui/wm/core/window_animations.h" 24 #include "ui/wm/core/window_util.h" 25 #include "ui/wm/public/activation_client.h" 26 27 namespace { 28 29 ExtensionViewViews* GetExtensionView(extensions::ExtensionViewHost* host) { 30 return static_cast<ExtensionViewViews*>(host->view()); 31 } 32 33 } // namespace 34 35 // The minimum/maximum dimensions of the popup. 36 // The minimum is just a little larger than the size of the button itself. 37 // The maximum is an arbitrary number that should be smaller than most screens. 38 const int ExtensionPopup::kMinWidth = 25; 39 const int ExtensionPopup::kMinHeight = 25; 40 const int ExtensionPopup::kMaxWidth = 800; 41 const int ExtensionPopup::kMaxHeight = 600; 42 43 ExtensionPopup::ExtensionPopup(extensions::ExtensionViewHost* host, 44 views::View* anchor_view, 45 views::BubbleBorder::Arrow arrow, 46 ShowAction show_action) 47 : BubbleDelegateView(anchor_view, arrow), 48 host_(host), 49 devtools_callback_(base::Bind( 50 &ExtensionPopup::OnDevToolsStateChanged, base::Unretained(this))), 51 widget_initialized_(false) { 52 inspect_with_devtools_ = show_action == SHOW_AND_INSPECT; 53 // Adjust the margin so that contents fit better. 54 const int margin = views::BubbleBorder::GetCornerRadius() / 2; 55 set_margins(gfx::Insets(margin, margin, margin, margin)); 56 SetLayoutManager(new views::FillLayout()); 57 AddChildView(GetExtensionView(host)); 58 GetExtensionView(host)->set_container(this); 59 // ExtensionPopup closes itself on very specific de-activation conditions. 60 set_close_on_deactivate(false); 61 62 // Wait to show the popup until the contained host finishes loading. 63 registrar_.Add(this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME, 64 content::Source<content::WebContents>(host->host_contents())); 65 66 // Listen for the containing view calling window.close(); 67 registrar_.Add( 68 this, 69 extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE, 70 content::Source<content::BrowserContext>(host->browser_context())); 71 content::DevToolsAgentHost::AddAgentStateCallback(devtools_callback_); 72 73 GetExtensionView(host)->GetBrowser()->tab_strip_model()->AddObserver(this); 74 } 75 76 ExtensionPopup::~ExtensionPopup() { 77 content::DevToolsAgentHost::RemoveAgentStateCallback(devtools_callback_); 78 79 GetExtensionView( 80 host_.get())->GetBrowser()->tab_strip_model()->RemoveObserver(this); 81 } 82 83 void ExtensionPopup::Observe(int type, 84 const content::NotificationSource& source, 85 const content::NotificationDetails& details) { 86 switch (type) { 87 case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME: 88 DCHECK_EQ(host()->host_contents(), 89 content::Source<content::WebContents>(source).ptr()); 90 // Show when the content finishes loading and its width is computed. 91 ShowBubble(); 92 break; 93 case extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE: 94 // If we aren't the host of the popup, then disregard the notification. 95 if (content::Details<extensions::ExtensionHost>(host()) == details) 96 GetWidget()->Close(); 97 break; 98 default: 99 NOTREACHED() << L"Received unexpected notification"; 100 } 101 } 102 103 void ExtensionPopup::OnDevToolsStateChanged( 104 content::DevToolsAgentHost* agent_host, 105 bool attached) { 106 // First check that the devtools are being opened on this popup. 107 if (host()->host_contents() != agent_host->GetWebContents()) 108 return; 109 110 if (attached) { 111 // Set inspect_with_devtools_ so the popup will be kept open while 112 // the devtools are open. 113 inspect_with_devtools_ = true; 114 } else { 115 // Widget::Close posts a task, which should give the devtools window a 116 // chance to finish detaching from the inspected RenderViewHost. 117 GetWidget()->Close(); 118 } 119 } 120 121 void ExtensionPopup::OnExtensionSizeChanged(ExtensionViewViews* view) { 122 SizeToContents(); 123 } 124 125 gfx::Size ExtensionPopup::GetPreferredSize() const { 126 // Constrain the size to popup min/max. 127 gfx::Size sz = views::View::GetPreferredSize(); 128 sz.set_width(std::max(kMinWidth, std::min(kMaxWidth, sz.width()))); 129 sz.set_height(std::max(kMinHeight, std::min(kMaxHeight, sz.height()))); 130 return sz; 131 } 132 133 void ExtensionPopup::ViewHierarchyChanged( 134 const ViewHierarchyChangedDetails& details) { 135 // TODO(msw): Find any remaining crashes related to http://crbug.com/327776 136 // No view hierarchy changes are expected if the widget no longer exists. 137 widget_initialized_ |= details.child == this && details.is_add && GetWidget(); 138 CHECK(GetWidget() || !widget_initialized_); 139 } 140 141 void ExtensionPopup::OnWidgetDestroying(views::Widget* widget) { 142 BubbleDelegateView::OnWidgetDestroying(widget); 143 aura::Window* bubble_window = GetWidget()->GetNativeWindow(); 144 aura::client::ActivationClient* activation_client = 145 aura::client::GetActivationClient(bubble_window->GetRootWindow()); 146 // If the popup was being inspected with devtools and the browser window was 147 // closed, then the root window and activation client are already destroyed. 148 if (activation_client) 149 activation_client->RemoveObserver(this); 150 } 151 152 void ExtensionPopup::OnWidgetActivationChanged(views::Widget* widget, 153 bool active) { 154 // TODO(msw): Find any remaining crashes related to http://crbug.com/327776 155 // No calls are expected if the widget isn't initialized or no longer exists. 156 CHECK(widget_initialized_); 157 CHECK(GetWidget()); 158 159 // Close on anchor window activation (ie. user clicked the browser window). 160 if (!inspect_with_devtools_ && widget && active && 161 widget->GetNativeWindow() == anchor_widget()->GetNativeWindow()) 162 GetWidget()->Close(); 163 } 164 165 void ExtensionPopup::OnWindowActivated(aura::Window* gained_active, 166 aura::Window* lost_active) { 167 // TODO(msw): Find any remaining crashes related to http://crbug.com/327776 168 // No calls are expected if the widget isn't initialized or no longer exists. 169 CHECK(widget_initialized_); 170 CHECK(GetWidget()); 171 172 // Close on anchor window activation (ie. user clicked the browser window). 173 // DesktopNativeWidgetAura does not trigger the expected browser widget 174 // [de]activation events when activating widgets in its own root window. 175 // This additional check handles those cases. See: http://crbug.com/320889 176 if (!inspect_with_devtools_ && 177 gained_active == anchor_widget()->GetNativeWindow()) 178 GetWidget()->Close(); 179 } 180 181 void ExtensionPopup::ActiveTabChanged(content::WebContents* old_contents, 182 content::WebContents* new_contents, 183 int index, 184 int reason) { 185 GetWidget()->Close(); 186 } 187 188 // static 189 ExtensionPopup* ExtensionPopup::ShowPopup(const GURL& url, 190 Browser* browser, 191 views::View* anchor_view, 192 views::BubbleBorder::Arrow arrow, 193 ShowAction show_action) { 194 extensions::ExtensionViewHost* host = 195 extensions::ExtensionViewHostFactory::CreatePopupHost(url, browser); 196 ExtensionPopup* popup = new ExtensionPopup(host, anchor_view, arrow, 197 show_action); 198 views::BubbleDelegateView::CreateBubble(popup); 199 200 gfx::NativeView native_view = popup->GetWidget()->GetNativeView(); 201 wm::SetWindowVisibilityAnimationType( 202 native_view, wm::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL); 203 wm::SetWindowVisibilityAnimationVerticalPosition(native_view, -3.0f); 204 205 // If the host had somehow finished loading, then we'd miss the notification 206 // and not show. This seems to happen in single-process mode. 207 if (host->did_stop_loading()) 208 popup->ShowBubble(); 209 210 aura::Window* bubble_window = popup->GetWidget()->GetNativeWindow(); 211 aura::client::ActivationClient* activation_client = 212 aura::client::GetActivationClient(bubble_window->GetRootWindow()); 213 activation_client->AddObserver(popup); 214 215 return popup; 216 } 217 218 void ExtensionPopup::ShowBubble() { 219 GetWidget()->Show(); 220 221 // Focus on the host contents when the bubble is first shown. 222 host()->host_contents()->Focus(); 223 224 if (inspect_with_devtools_) { 225 DevToolsWindow::OpenDevToolsWindow(host()->host_contents(), 226 DevToolsToggleAction::ShowConsole()); 227 } 228 } 229