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/gtk/extensions/extension_popup_gtk.h" 6 7 #include <gtk/gtk.h> 8 9 #include <algorithm> 10 11 #include "base/i18n/rtl.h" 12 #include "base/message_loop.h" 13 #include "chrome/browser/debugger/devtools_manager.h" 14 #include "chrome/browser/extensions/extension_host.h" 15 #include "chrome/browser/extensions/extension_process_manager.h" 16 #include "chrome/browser/profiles/profile.h" 17 #include "chrome/browser/renderer_host/render_widget_host_view_gtk.h" 18 #include "chrome/browser/ui/browser.h" 19 #include "chrome/browser/ui/browser_window.h" 20 #include "chrome/browser/ui/gtk/gtk_theme_service.h" 21 #include "content/browser/renderer_host/render_view_host.h" 22 #include "content/common/notification_details.h" 23 #include "content/common/notification_source.h" 24 #include "googleurl/src/gurl.h" 25 26 ExtensionPopupGtk* ExtensionPopupGtk::current_extension_popup_ = NULL; 27 28 // The minimum/maximum dimensions of the extension popup. 29 // The minimum is just a little larger than the size of a browser action button. 30 // The maximum is an arbitrary number that should be smaller than most screens. 31 const int ExtensionPopupGtk::kMinWidth = 25; 32 const int ExtensionPopupGtk::kMinHeight = 25; 33 const int ExtensionPopupGtk::kMaxWidth = 800; 34 const int ExtensionPopupGtk::kMaxHeight = 600; 35 36 ExtensionPopupGtk::ExtensionPopupGtk(Browser* browser, 37 ExtensionHost* host, 38 GtkWidget* anchor, 39 bool inspect) 40 : browser_(browser), 41 bubble_(NULL), 42 host_(host), 43 anchor_(anchor), 44 being_inspected_(inspect), 45 method_factory_(this) { 46 host_->view()->SetContainer(this); 47 48 // If the host had somehow finished loading, then we'd miss the notification 49 // and not show. This seems to happen in single-process mode. 50 if (host->did_stop_loading()) { 51 ShowPopup(); 52 } else { 53 registrar_.Add(this, NotificationType::EXTENSION_HOST_DID_STOP_LOADING, 54 Source<Profile>(host->profile())); 55 } 56 57 registrar_.Add(this, NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE, 58 Source<Profile>(host->profile())); 59 } 60 61 ExtensionPopupGtk::~ExtensionPopupGtk() { 62 } 63 64 void ExtensionPopupGtk::Observe(NotificationType type, 65 const NotificationSource& source, 66 const NotificationDetails& details) { 67 switch (type.value) { 68 case NotificationType::EXTENSION_HOST_DID_STOP_LOADING: 69 if (Details<ExtensionHost>(host_.get()) == details) 70 ShowPopup(); 71 break; 72 case NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE: 73 if (Details<ExtensionHost>(host_.get()) == details) 74 DestroyPopup(); 75 break; 76 case NotificationType::DEVTOOLS_WINDOW_CLOSING: 77 // Make sure its the devtools window that inspecting our popup. 78 if (Details<RenderViewHost>(host_->render_view_host()) != details) 79 break; 80 81 // If the devtools window is closing, we post a task to ourselves to 82 // close the popup. This gives the devtools window a chance to finish 83 // detaching from the inspected RenderViewHost. 84 MessageLoop::current()->PostTask(FROM_HERE, 85 method_factory_.NewRunnableMethod(&ExtensionPopupGtk::DestroyPopup)); 86 break; 87 default: 88 NOTREACHED() << "Received unexpected notification"; 89 } 90 } 91 92 void ExtensionPopupGtk::ShowPopup() { 93 if (bubble_) { 94 NOTREACHED(); 95 return; 96 } 97 98 if (being_inspected_) { 99 DevToolsManager::GetInstance()->OpenDevToolsWindow( 100 host_->render_view_host()); 101 // Listen for the the devtools window closing. 102 registrar_.Add(this, NotificationType::DEVTOOLS_WINDOW_CLOSING, 103 Source<Profile>(host_->profile())); 104 } 105 106 // Only one instance should be showing at a time. Get rid of the old one, if 107 // any. Typically, |current_extension_popup_| will be NULL, but it can be 108 // non-NULL if a browser action button is clicked while another extension 109 // popup's extension host is still loading. 110 if (current_extension_popup_) 111 current_extension_popup_->DestroyPopup(); 112 current_extension_popup_ = this; 113 114 // We'll be in the upper-right corner of the window for LTR languages, so we 115 // want to put the arrow at the upper-right corner of the bubble to match the 116 // page and app menus. 117 InfoBubbleGtk::ArrowLocationGtk arrow_location = 118 !base::i18n::IsRTL() ? 119 InfoBubbleGtk::ARROW_LOCATION_TOP_RIGHT : 120 InfoBubbleGtk::ARROW_LOCATION_TOP_LEFT; 121 bubble_ = InfoBubbleGtk::Show(anchor_, 122 NULL, 123 host_->view()->native_view(), 124 arrow_location, 125 false, // match_system_theme 126 !being_inspected_, // grab_input 127 GtkThemeService::GetFrom(browser_->profile()), 128 this); 129 } 130 131 bool ExtensionPopupGtk::DestroyPopup() { 132 if (!bubble_) { 133 NOTREACHED(); 134 return false; 135 } 136 137 bubble_->Close(); 138 return true; 139 } 140 141 void ExtensionPopupGtk::InfoBubbleClosing(InfoBubbleGtk* bubble, 142 bool closed_by_escape) { 143 current_extension_popup_ = NULL; 144 delete this; 145 } 146 147 void ExtensionPopupGtk::OnExtensionPreferredSizeChanged( 148 ExtensionViewGtk* view, 149 const gfx::Size& new_size) { 150 int width = std::max(kMinWidth, std::min(kMaxWidth, new_size.width())); 151 int height = std::max(kMinHeight, std::min(kMaxHeight, new_size.height())); 152 153 view->render_view_host()->view()->SetSize(gfx::Size(width, height)); 154 gtk_widget_set_size_request(view->native_view(), width, height); 155 } 156 157 // static 158 void ExtensionPopupGtk::Show(const GURL& url, Browser* browser, 159 GtkWidget* anchor, bool inspect) { 160 ExtensionProcessManager* manager = 161 browser->profile()->GetExtensionProcessManager(); 162 DCHECK(manager); 163 if (!manager) 164 return; 165 166 ExtensionHost* host = manager->CreatePopup(url, browser); 167 // This object will delete itself when the info bubble is closed. 168 new ExtensionPopupGtk(browser, host, anchor, inspect); 169 } 170 171 gfx::Rect ExtensionPopupGtk::GetViewBounds() { 172 return gfx::Rect(host_->view()->native_view()->allocation); 173 } 174