1// Copyright 2013 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/extensions/extension_view_host.h" 6 7#include "base/strings/string_piece.h" 8#include "chrome/browser/chrome_notification_types.h" 9#include "chrome/browser/extensions/extension_service.h" 10#include "chrome/browser/extensions/extension_system.h" 11#include "chrome/browser/extensions/window_controller.h" 12#include "chrome/browser/file_select_helper.h" 13#include "chrome/browser/platform_util.h" 14#include "chrome/browser/ui/browser.h" 15#include "chrome/browser/ui/browser_dialogs.h" 16#include "chrome/common/extensions/extension_messages.h" 17#include "components/web_modal/web_contents_modal_dialog_manager.h" 18#include "content/public/browser/notification_source.h" 19#include "content/public/browser/render_view_host.h" 20#include "content/public/browser/web_contents.h" 21#include "content/public/browser/web_contents_view.h" 22#include "grit/browser_resources.h" 23#include "ui/base/resource/resource_bundle.h" 24#include "ui/events/keycodes/keyboard_codes.h" 25 26using content::NativeWebKeyboardEvent; 27using content::OpenURLParams; 28using content::RenderViewHost; 29using content::WebContents; 30using content::WebContentsObserver; 31using web_modal::WebContentsModalDialogManager; 32 33namespace extensions { 34 35// Notifies an ExtensionViewHost when a WebContents is destroyed. 36class ExtensionViewHost::AssociatedWebContentsObserver 37 : public WebContentsObserver { 38 public: 39 AssociatedWebContentsObserver(ExtensionViewHost* host, 40 WebContents* web_contents) 41 : WebContentsObserver(web_contents), host_(host) {} 42 virtual ~AssociatedWebContentsObserver() {} 43 44 // content::WebContentsObserver: 45 virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE { 46 // Deleting |this| from here is safe. 47 host_->SetAssociatedWebContents(NULL); 48 } 49 50 private: 51 ExtensionViewHost* host_; 52 53 DISALLOW_COPY_AND_ASSIGN(AssociatedWebContentsObserver); 54}; 55 56ExtensionViewHost::ExtensionViewHost( 57 const Extension* extension, 58 content::SiteInstance* site_instance, 59 const GURL& url, 60 ViewType host_type) 61 : ExtensionHost(extension, site_instance, url, host_type), 62 associated_web_contents_(NULL) { 63 // Not used for panels, see PanelHost. 64 DCHECK(host_type == VIEW_TYPE_EXTENSION_DIALOG || 65 host_type == VIEW_TYPE_EXTENSION_INFOBAR || 66 host_type == VIEW_TYPE_EXTENSION_POPUP); 67} 68 69ExtensionViewHost::~ExtensionViewHost() { 70 // The hosting WebContents will be deleted in the base class, so unregister 71 // this object before it deletes the attached WebContentsModalDialogManager. 72 WebContentsModalDialogManager* manager = 73 WebContentsModalDialogManager::FromWebContents(host_contents()); 74 if (manager) 75 manager->SetDelegate(NULL); 76} 77 78void ExtensionViewHost::CreateView(Browser* browser) { 79#if defined(TOOLKIT_VIEWS) 80 view_.reset(new ExtensionViewViews(this, browser)); 81 // We own |view_|, so don't auto delete when it's removed from the view 82 // hierarchy. 83 view_->set_owned_by_client(); 84#elif defined(OS_MACOSX) 85 view_.reset(new ExtensionViewMac(this, browser)); 86 view_->Init(); 87#elif defined(TOOLKIT_GTK) 88 view_.reset(new ExtensionViewGtk(this, browser)); 89 view_->Init(); 90#else 91 // TODO(port) 92 NOTREACHED(); 93#endif 94} 95 96void ExtensionViewHost::SetAssociatedWebContents(WebContents* web_contents) { 97 associated_web_contents_ = web_contents; 98 if (associated_web_contents_) { 99 // Observe the new WebContents for deletion. 100 associated_web_contents_observer_.reset( 101 new AssociatedWebContentsObserver(this, associated_web_contents_)); 102 } else { 103 associated_web_contents_observer_.reset(); 104 } 105} 106 107void ExtensionViewHost::UnhandledKeyboardEvent( 108 WebContents* source, 109 const content::NativeWebKeyboardEvent& event) { 110 Browser* browser = view_->browser(); 111 if (browser) { 112 // Handle lower priority browser shortcuts such as Ctrl-f. 113 return browser->HandleKeyboardEvent(source, event); 114 } else { 115#if defined(TOOLKIT_VIEWS) 116 // In case there's no Browser (e.g. for dialogs), pass it to 117 // ExtensionViewViews to handle accelerators. The view's FocusManager does 118 // not know anything about Browser accelerators, but might know others such 119 // as Ash's. 120 view_->HandleKeyboardEvent(event); 121#endif 122 } 123} 124 125// ExtensionHost overrides: 126 127void ExtensionViewHost::OnDidStopLoading() { 128 DCHECK(did_stop_loading()); 129#if defined(TOOLKIT_VIEWS) || defined(OS_MACOSX) 130 view_->DidStopLoading(); 131#endif 132} 133 134void ExtensionViewHost::OnDocumentAvailable() { 135 if (extension_host_type() == VIEW_TYPE_EXTENSION_INFOBAR) { 136 // No style sheet for other types, at the moment. 137 InsertInfobarCSS(); 138 } 139} 140 141void ExtensionViewHost::LoadInitialURL() { 142 if (!ExtensionSystem::GetForBrowserContext(browser_context())-> 143 extension_service()->IsBackgroundPageReady(extension())) { 144 // Make sure the background page loads before any others. 145 registrar()->Add(this, 146 chrome::NOTIFICATION_EXTENSION_BACKGROUND_PAGE_READY, 147 content::Source<Extension>(extension())); 148 return; 149 } 150 151 // Popups may spawn modal dialogs, which need positioning information. 152 if (extension_host_type() == VIEW_TYPE_EXTENSION_POPUP) { 153 WebContentsModalDialogManager::CreateForWebContents(host_contents()); 154 WebContentsModalDialogManager::FromWebContents( 155 host_contents())->SetDelegate(this); 156 } 157 158 ExtensionHost::LoadInitialURL(); 159} 160 161bool ExtensionViewHost::IsBackgroundPage() const { 162 DCHECK(view_); 163 return false; 164} 165 166// content::WebContentsDelegate overrides: 167 168WebContents* ExtensionViewHost::OpenURLFromTab( 169 WebContents* source, 170 const OpenURLParams& params) { 171 // Whitelist the dispositions we will allow to be opened. 172 switch (params.disposition) { 173 case SINGLETON_TAB: 174 case NEW_FOREGROUND_TAB: 175 case NEW_BACKGROUND_TAB: 176 case NEW_POPUP: 177 case NEW_WINDOW: 178 case SAVE_TO_DISK: 179 case OFF_THE_RECORD: { 180 // Only allow these from hosts that are bound to a browser (e.g. popups). 181 // Otherwise they are not driven by a user gesture. 182 Browser* browser = view_->browser(); 183 return browser ? browser->OpenURL(params) : NULL; 184 } 185 default: 186 return NULL; 187 } 188} 189 190bool ExtensionViewHost::PreHandleKeyboardEvent( 191 WebContents* source, 192 const NativeWebKeyboardEvent& event, 193 bool* is_keyboard_shortcut) { 194 if (extension_host_type() == VIEW_TYPE_EXTENSION_POPUP && 195 event.type == NativeWebKeyboardEvent::RawKeyDown && 196 event.windowsKeyCode == ui::VKEY_ESCAPE) { 197 DCHECK(is_keyboard_shortcut != NULL); 198 *is_keyboard_shortcut = true; 199 return false; 200 } 201 202 // Handle higher priority browser shortcuts such as Ctrl-w. 203 Browser* browser = view_->browser(); 204 if (browser) 205 return browser->PreHandleKeyboardEvent(source, event, is_keyboard_shortcut); 206 207 *is_keyboard_shortcut = false; 208 return false; 209} 210 211void ExtensionViewHost::HandleKeyboardEvent( 212 WebContents* source, 213 const NativeWebKeyboardEvent& event) { 214 if (extension_host_type() == VIEW_TYPE_EXTENSION_POPUP) { 215 if (event.type == NativeWebKeyboardEvent::RawKeyDown && 216 event.windowsKeyCode == ui::VKEY_ESCAPE) { 217 Close(); 218 return; 219 } 220 } 221 UnhandledKeyboardEvent(source, event); 222} 223 224content::ColorChooser* ExtensionViewHost::OpenColorChooser( 225 WebContents* web_contents, 226 SkColor initial_color, 227 const std::vector<content::ColorSuggestion>& suggestions) { 228 // Similar to the file chooser below, opening a color chooser requires a 229 // visible <input> element to click on. Therefore this code only exists for 230 // extensions with a view. 231 return chrome::ShowColorChooser(web_contents, initial_color); 232} 233 234void ExtensionViewHost::RunFileChooser( 235 WebContents* tab, 236 const content::FileChooserParams& params) { 237 // For security reasons opening a file picker requires a visible <input> 238 // element to click on, so this code only exists for extensions with a view. 239 FileSelectHelper::RunFileChooser(tab, params); 240} 241 242 243void ExtensionViewHost::ResizeDueToAutoResize(WebContents* source, 244 const gfx::Size& new_size) { 245 view_->ResizeDueToAutoResize(new_size); 246} 247 248// content::WebContentsObserver overrides: 249 250void ExtensionViewHost::RenderViewCreated(RenderViewHost* render_view_host) { 251 ExtensionHost::RenderViewCreated(render_view_host); 252 253 view_->RenderViewCreated(); 254 255 // If the host is bound to a window, then extract its id. Extensions hosted 256 // in ExternalTabContainer objects may not have an associated window. 257 WindowController* window = GetExtensionWindowController(); 258 if (window) { 259 render_view_host->Send(new ExtensionMsg_UpdateBrowserWindowId( 260 render_view_host->GetRoutingID(), window->GetWindowId())); 261 } 262} 263 264// web_modal::WebContentsModalDialogManagerDelegate overrides: 265 266web_modal::WebContentsModalDialogHost* 267ExtensionViewHost::GetWebContentsModalDialogHost() { 268 return this; 269} 270 271bool ExtensionViewHost::IsWebContentsVisible(WebContents* web_contents) { 272 return platform_util::IsVisible(web_contents->GetView()->GetNativeView()); 273} 274 275gfx::NativeView ExtensionViewHost::GetHostView() const { 276 return view_->native_view(); 277} 278 279gfx::Point ExtensionViewHost::GetDialogPosition(const gfx::Size& size) { 280 if (!GetVisibleWebContents()) 281 return gfx::Point(); 282 gfx::Rect bounds = GetVisibleWebContents()->GetView()->GetViewBounds(); 283 return gfx::Point( 284 std::max(0, (bounds.width() - size.width()) / 2), 285 std::max(0, (bounds.height() - size.height()) / 2)); 286} 287 288gfx::Size ExtensionViewHost::GetMaximumDialogSize() { 289 if (!GetVisibleWebContents()) 290 return gfx::Size(); 291 return GetVisibleWebContents()->GetView()->GetViewBounds().size(); 292} 293 294void ExtensionViewHost::AddObserver( 295 web_modal::ModalDialogHostObserver* observer) { 296} 297 298void ExtensionViewHost::RemoveObserver( 299 web_modal::ModalDialogHostObserver* observer) { 300} 301 302WindowController* ExtensionViewHost::GetExtensionWindowController() const { 303 return view_->browser() ? view_->browser()->extension_window_controller() 304 : NULL; 305} 306 307WebContents* ExtensionViewHost::GetAssociatedWebContents() const { 308 return associated_web_contents_; 309} 310 311WebContents* ExtensionViewHost::GetVisibleWebContents() const { 312 if (associated_web_contents_) 313 return associated_web_contents_; 314 if (extension_host_type() == VIEW_TYPE_EXTENSION_POPUP) 315 return host_contents(); 316 return NULL; 317} 318 319void ExtensionViewHost::Observe(int type, 320 const content::NotificationSource& source, 321 const content::NotificationDetails& details) { 322 if (type == chrome::NOTIFICATION_EXTENSION_BACKGROUND_PAGE_READY) { 323 DCHECK(ExtensionSystem::GetForBrowserContext(browser_context())-> 324 extension_service()->IsBackgroundPageReady(extension())); 325 LoadInitialURL(); 326 return; 327 } 328 ExtensionHost::Observe(type, source, details); 329} 330 331void ExtensionViewHost::InsertInfobarCSS() { 332 static const base::StringPiece css( 333 ResourceBundle::GetSharedInstance().GetRawDataResource( 334 IDR_EXTENSIONS_INFOBAR_CSS)); 335 336 render_view_host()->InsertCSS(base::string16(), css.as_string()); 337} 338 339} // namespace extensions 340