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/window_controller.h" 10#include "chrome/browser/file_select_helper.h" 11#include "chrome/browser/platform_util.h" 12#include "chrome/browser/ui/browser.h" 13#include "chrome/browser/ui/browser_dialogs.h" 14#include "components/web_modal/web_contents_modal_dialog_manager.h" 15#include "content/public/browser/notification_source.h" 16#include "content/public/browser/render_view_host.h" 17#include "content/public/browser/web_contents.h" 18#include "extensions/browser/extension_system.h" 19#include "extensions/browser/runtime_data.h" 20#include "extensions/common/extension_messages.h" 21#include "grit/browser_resources.h" 22#include "third_party/WebKit/public/web/WebInputEvent.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() 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#else 88 // TODO(port) 89 NOTREACHED(); 90#endif 91} 92 93void ExtensionViewHost::SetAssociatedWebContents(WebContents* web_contents) { 94 associated_web_contents_ = web_contents; 95 if (associated_web_contents_) { 96 // Observe the new WebContents for deletion. 97 associated_web_contents_observer_.reset( 98 new AssociatedWebContentsObserver(this, associated_web_contents_)); 99 } else { 100 associated_web_contents_observer_.reset(); 101 } 102} 103 104void ExtensionViewHost::UnhandledKeyboardEvent( 105 WebContents* source, 106 const content::NativeWebKeyboardEvent& event) { 107 Browser* browser = view_->browser(); 108 if (browser) { 109 // Handle lower priority browser shortcuts such as Ctrl-f. 110 return browser->HandleKeyboardEvent(source, event); 111 } else { 112#if defined(TOOLKIT_VIEWS) 113 // In case there's no Browser (e.g. for dialogs), pass it to 114 // ExtensionViewViews to handle accelerators. The view's FocusManager does 115 // not know anything about Browser accelerators, but might know others such 116 // as Ash's. 117 view_->HandleKeyboardEvent(event); 118#endif 119 } 120} 121 122// ExtensionHost overrides: 123 124void ExtensionViewHost::OnDidStopLoading() { 125 DCHECK(did_stop_loading()); 126#if defined(TOOLKIT_VIEWS) || defined(OS_MACOSX) 127 view_->DidStopLoading(); 128#endif 129} 130 131void ExtensionViewHost::OnDocumentAvailable() { 132 if (extension_host_type() == VIEW_TYPE_EXTENSION_INFOBAR) { 133 // No style sheet for other types, at the moment. 134 InsertInfobarCSS(); 135 } 136} 137 138void ExtensionViewHost::LoadInitialURL() { 139 if (!ExtensionSystem::Get(browser_context())-> 140 runtime_data()->IsBackgroundPageReady(extension())) { 141 // Make sure the background page loads before any others. 142 registrar()->Add(this, 143 chrome::NOTIFICATION_EXTENSION_BACKGROUND_PAGE_READY, 144 content::Source<Extension>(extension())); 145 return; 146 } 147 148 // Popups may spawn modal dialogs, which need positioning information. 149 if (extension_host_type() == VIEW_TYPE_EXTENSION_POPUP) { 150 WebContentsModalDialogManager::CreateForWebContents(host_contents()); 151 WebContentsModalDialogManager::FromWebContents( 152 host_contents())->SetDelegate(this); 153 } 154 155 ExtensionHost::LoadInitialURL(); 156} 157 158bool ExtensionViewHost::IsBackgroundPage() const { 159 DCHECK(view_); 160 return false; 161} 162 163// content::WebContentsDelegate overrides: 164 165WebContents* ExtensionViewHost::OpenURLFromTab( 166 WebContents* source, 167 const OpenURLParams& params) { 168 // Whitelist the dispositions we will allow to be opened. 169 switch (params.disposition) { 170 case SINGLETON_TAB: 171 case NEW_FOREGROUND_TAB: 172 case NEW_BACKGROUND_TAB: 173 case NEW_POPUP: 174 case NEW_WINDOW: 175 case SAVE_TO_DISK: 176 case OFF_THE_RECORD: { 177 // Only allow these from hosts that are bound to a browser (e.g. popups). 178 // Otherwise they are not driven by a user gesture. 179 Browser* browser = view_->browser(); 180 return browser ? browser->OpenURL(params) : NULL; 181 } 182 default: 183 return NULL; 184 } 185} 186 187bool ExtensionViewHost::PreHandleKeyboardEvent( 188 WebContents* source, 189 const NativeWebKeyboardEvent& event, 190 bool* is_keyboard_shortcut) { 191 if (extension_host_type() == VIEW_TYPE_EXTENSION_POPUP && 192 event.type == NativeWebKeyboardEvent::RawKeyDown && 193 event.windowsKeyCode == ui::VKEY_ESCAPE) { 194 DCHECK(is_keyboard_shortcut != NULL); 195 *is_keyboard_shortcut = true; 196 return false; 197 } 198 199 // Handle higher priority browser shortcuts such as Ctrl-w. 200 Browser* browser = view_->browser(); 201 if (browser) 202 return browser->PreHandleKeyboardEvent(source, event, is_keyboard_shortcut); 203 204 *is_keyboard_shortcut = false; 205 return false; 206} 207 208void ExtensionViewHost::HandleKeyboardEvent( 209 WebContents* source, 210 const NativeWebKeyboardEvent& event) { 211 if (extension_host_type() == VIEW_TYPE_EXTENSION_POPUP) { 212 if (event.type == NativeWebKeyboardEvent::RawKeyDown && 213 event.windowsKeyCode == ui::VKEY_ESCAPE) { 214 Close(); 215 return; 216 } 217 } 218 UnhandledKeyboardEvent(source, event); 219} 220 221bool ExtensionViewHost::PreHandleGestureEvent( 222 content::WebContents* source, 223 const blink::WebGestureEvent& event) { 224 // Disable pinch zooming. 225 return event.type == blink::WebGestureEvent::GesturePinchBegin || 226 event.type == blink::WebGestureEvent::GesturePinchUpdate || 227 event.type == blink::WebGestureEvent::GesturePinchEnd; 228} 229 230content::ColorChooser* ExtensionViewHost::OpenColorChooser( 231 WebContents* web_contents, 232 SkColor initial_color, 233 const std::vector<content::ColorSuggestion>& suggestions) { 234 // Similar to the file chooser below, opening a color chooser requires a 235 // visible <input> element to click on. Therefore this code only exists for 236 // extensions with a view. 237 return chrome::ShowColorChooser(web_contents, initial_color); 238} 239 240void ExtensionViewHost::RunFileChooser( 241 WebContents* tab, 242 const content::FileChooserParams& params) { 243 // For security reasons opening a file picker requires a visible <input> 244 // element to click on, so this code only exists for extensions with a view. 245 FileSelectHelper::RunFileChooser(tab, params); 246} 247 248 249void ExtensionViewHost::ResizeDueToAutoResize(WebContents* source, 250 const gfx::Size& new_size) { 251 view_->ResizeDueToAutoResize(new_size); 252} 253 254// content::WebContentsObserver overrides: 255 256void ExtensionViewHost::RenderViewCreated(RenderViewHost* render_view_host) { 257 ExtensionHost::RenderViewCreated(render_view_host); 258 259 view_->RenderViewCreated(); 260 261 // If the host is bound to a window, then extract its id. Extensions hosted 262 // in ExternalTabContainer objects may not have an associated window. 263 WindowController* window = GetExtensionWindowController(); 264 if (window) { 265 render_view_host->Send(new ExtensionMsg_UpdateBrowserWindowId( 266 render_view_host->GetRoutingID(), window->GetWindowId())); 267 } 268} 269 270// web_modal::WebContentsModalDialogManagerDelegate overrides: 271 272web_modal::WebContentsModalDialogHost* 273ExtensionViewHost::GetWebContentsModalDialogHost() { 274 return this; 275} 276 277bool ExtensionViewHost::IsWebContentsVisible(WebContents* web_contents) { 278 return platform_util::IsVisible(web_contents->GetNativeView()); 279} 280 281gfx::NativeView ExtensionViewHost::GetHostView() const { 282 return view_->native_view(); 283} 284 285gfx::Point ExtensionViewHost::GetDialogPosition(const gfx::Size& size) { 286 if (!GetVisibleWebContents()) 287 return gfx::Point(); 288 gfx::Rect bounds = GetVisibleWebContents()->GetViewBounds(); 289 return gfx::Point( 290 std::max(0, (bounds.width() - size.width()) / 2), 291 std::max(0, (bounds.height() - size.height()) / 2)); 292} 293 294gfx::Size ExtensionViewHost::GetMaximumDialogSize() { 295 if (!GetVisibleWebContents()) 296 return gfx::Size(); 297 return GetVisibleWebContents()->GetViewBounds().size(); 298} 299 300void ExtensionViewHost::AddObserver( 301 web_modal::ModalDialogHostObserver* observer) { 302} 303 304void ExtensionViewHost::RemoveObserver( 305 web_modal::ModalDialogHostObserver* observer) { 306} 307 308WindowController* ExtensionViewHost::GetExtensionWindowController() const { 309 return view_->browser() ? view_->browser()->extension_window_controller() 310 : NULL; 311} 312 313WebContents* ExtensionViewHost::GetAssociatedWebContents() const { 314 return associated_web_contents_; 315} 316 317WebContents* ExtensionViewHost::GetVisibleWebContents() const { 318 if (associated_web_contents_) 319 return associated_web_contents_; 320 if (extension_host_type() == VIEW_TYPE_EXTENSION_POPUP) 321 return host_contents(); 322 return NULL; 323} 324 325void ExtensionViewHost::Observe(int type, 326 const content::NotificationSource& source, 327 const content::NotificationDetails& details) { 328 if (type == chrome::NOTIFICATION_EXTENSION_BACKGROUND_PAGE_READY) { 329 DCHECK(ExtensionSystem::Get(browser_context())-> 330 runtime_data()->IsBackgroundPageReady(extension())); 331 LoadInitialURL(); 332 return; 333 } 334 ExtensionHost::Observe(type, source, details); 335} 336 337void ExtensionViewHost::InsertInfobarCSS() { 338 static const base::StringPiece css( 339 ResourceBundle::GetSharedInstance().GetRawDataResource( 340 IDR_EXTENSIONS_INFOBAR_CSS)); 341 342 host_contents()->InsertCSS(css.as_string()); 343} 344 345} // namespace extensions 346