| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/extension_view_host.h" |
| |
| #include "base/strings/string_piece.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/extensions/window_controller.h" |
| #include "chrome/browser/file_select_helper.h" |
| #include "chrome/browser/platform_util.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_dialogs.h" |
| #include "components/web_modal/web_contents_modal_dialog_manager.h" |
| #include "content/public/browser/notification_source.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "extensions/browser/extension_system.h" |
| #include "extensions/browser/runtime_data.h" |
| #include "extensions/common/extension_messages.h" |
| #include "grit/browser_resources.h" |
| #include "third_party/WebKit/public/web/WebInputEvent.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/events/keycodes/keyboard_codes.h" |
| |
| using content::NativeWebKeyboardEvent; |
| using content::OpenURLParams; |
| using content::RenderViewHost; |
| using content::WebContents; |
| using content::WebContentsObserver; |
| using web_modal::WebContentsModalDialogManager; |
| |
| namespace extensions { |
| |
| // Notifies an ExtensionViewHost when a WebContents is destroyed. |
| class ExtensionViewHost::AssociatedWebContentsObserver |
| : public WebContentsObserver { |
| public: |
| AssociatedWebContentsObserver(ExtensionViewHost* host, |
| WebContents* web_contents) |
| : WebContentsObserver(web_contents), host_(host) {} |
| virtual ~AssociatedWebContentsObserver() {} |
| |
| // content::WebContentsObserver: |
| virtual void WebContentsDestroyed() OVERRIDE { |
| // Deleting |this| from here is safe. |
| host_->SetAssociatedWebContents(NULL); |
| } |
| |
| private: |
| ExtensionViewHost* host_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AssociatedWebContentsObserver); |
| }; |
| |
| ExtensionViewHost::ExtensionViewHost( |
| const Extension* extension, |
| content::SiteInstance* site_instance, |
| const GURL& url, |
| ViewType host_type) |
| : ExtensionHost(extension, site_instance, url, host_type), |
| associated_web_contents_(NULL) { |
| // Not used for panels, see PanelHost. |
| DCHECK(host_type == VIEW_TYPE_EXTENSION_DIALOG || |
| host_type == VIEW_TYPE_EXTENSION_INFOBAR || |
| host_type == VIEW_TYPE_EXTENSION_POPUP); |
| } |
| |
| ExtensionViewHost::~ExtensionViewHost() { |
| // The hosting WebContents will be deleted in the base class, so unregister |
| // this object before it deletes the attached WebContentsModalDialogManager. |
| WebContentsModalDialogManager* manager = |
| WebContentsModalDialogManager::FromWebContents(host_contents()); |
| if (manager) |
| manager->SetDelegate(NULL); |
| } |
| |
| void ExtensionViewHost::CreateView(Browser* browser) { |
| #if defined(TOOLKIT_VIEWS) |
| view_.reset(new ExtensionViewViews(this, browser)); |
| // We own |view_|, so don't auto delete when it's removed from the view |
| // hierarchy. |
| view_->set_owned_by_client(); |
| #elif defined(OS_MACOSX) |
| view_.reset(new ExtensionViewMac(this, browser)); |
| view_->Init(); |
| #else |
| // TODO(port) |
| NOTREACHED(); |
| #endif |
| } |
| |
| void ExtensionViewHost::SetAssociatedWebContents(WebContents* web_contents) { |
| associated_web_contents_ = web_contents; |
| if (associated_web_contents_) { |
| // Observe the new WebContents for deletion. |
| associated_web_contents_observer_.reset( |
| new AssociatedWebContentsObserver(this, associated_web_contents_)); |
| } else { |
| associated_web_contents_observer_.reset(); |
| } |
| } |
| |
| void ExtensionViewHost::UnhandledKeyboardEvent( |
| WebContents* source, |
| const content::NativeWebKeyboardEvent& event) { |
| Browser* browser = view_->browser(); |
| if (browser) { |
| // Handle lower priority browser shortcuts such as Ctrl-f. |
| return browser->HandleKeyboardEvent(source, event); |
| } else { |
| #if defined(TOOLKIT_VIEWS) |
| // In case there's no Browser (e.g. for dialogs), pass it to |
| // ExtensionViewViews to handle accelerators. The view's FocusManager does |
| // not know anything about Browser accelerators, but might know others such |
| // as Ash's. |
| view_->HandleKeyboardEvent(event); |
| #endif |
| } |
| } |
| |
| // ExtensionHost overrides: |
| |
| void ExtensionViewHost::OnDidStopLoading() { |
| DCHECK(did_stop_loading()); |
| #if defined(TOOLKIT_VIEWS) || defined(OS_MACOSX) |
| view_->DidStopLoading(); |
| #endif |
| } |
| |
| void ExtensionViewHost::OnDocumentAvailable() { |
| if (extension_host_type() == VIEW_TYPE_EXTENSION_INFOBAR) { |
| // No style sheet for other types, at the moment. |
| InsertInfobarCSS(); |
| } |
| } |
| |
| void ExtensionViewHost::LoadInitialURL() { |
| if (!ExtensionSystem::Get(browser_context())-> |
| runtime_data()->IsBackgroundPageReady(extension())) { |
| // Make sure the background page loads before any others. |
| registrar()->Add(this, |
| chrome::NOTIFICATION_EXTENSION_BACKGROUND_PAGE_READY, |
| content::Source<Extension>(extension())); |
| return; |
| } |
| |
| // Popups may spawn modal dialogs, which need positioning information. |
| if (extension_host_type() == VIEW_TYPE_EXTENSION_POPUP) { |
| WebContentsModalDialogManager::CreateForWebContents(host_contents()); |
| WebContentsModalDialogManager::FromWebContents( |
| host_contents())->SetDelegate(this); |
| } |
| |
| ExtensionHost::LoadInitialURL(); |
| } |
| |
| bool ExtensionViewHost::IsBackgroundPage() const { |
| DCHECK(view_); |
| return false; |
| } |
| |
| // content::WebContentsDelegate overrides: |
| |
| WebContents* ExtensionViewHost::OpenURLFromTab( |
| WebContents* source, |
| const OpenURLParams& params) { |
| // Whitelist the dispositions we will allow to be opened. |
| switch (params.disposition) { |
| case SINGLETON_TAB: |
| case NEW_FOREGROUND_TAB: |
| case NEW_BACKGROUND_TAB: |
| case NEW_POPUP: |
| case NEW_WINDOW: |
| case SAVE_TO_DISK: |
| case OFF_THE_RECORD: { |
| // Only allow these from hosts that are bound to a browser (e.g. popups). |
| // Otherwise they are not driven by a user gesture. |
| Browser* browser = view_->browser(); |
| return browser ? browser->OpenURL(params) : NULL; |
| } |
| default: |
| return NULL; |
| } |
| } |
| |
| bool ExtensionViewHost::PreHandleKeyboardEvent( |
| WebContents* source, |
| const NativeWebKeyboardEvent& event, |
| bool* is_keyboard_shortcut) { |
| if (extension_host_type() == VIEW_TYPE_EXTENSION_POPUP && |
| event.type == NativeWebKeyboardEvent::RawKeyDown && |
| event.windowsKeyCode == ui::VKEY_ESCAPE) { |
| DCHECK(is_keyboard_shortcut != NULL); |
| *is_keyboard_shortcut = true; |
| return false; |
| } |
| |
| // Handle higher priority browser shortcuts such as Ctrl-w. |
| Browser* browser = view_->browser(); |
| if (browser) |
| return browser->PreHandleKeyboardEvent(source, event, is_keyboard_shortcut); |
| |
| *is_keyboard_shortcut = false; |
| return false; |
| } |
| |
| void ExtensionViewHost::HandleKeyboardEvent( |
| WebContents* source, |
| const NativeWebKeyboardEvent& event) { |
| if (extension_host_type() == VIEW_TYPE_EXTENSION_POPUP) { |
| if (event.type == NativeWebKeyboardEvent::RawKeyDown && |
| event.windowsKeyCode == ui::VKEY_ESCAPE) { |
| Close(); |
| return; |
| } |
| } |
| UnhandledKeyboardEvent(source, event); |
| } |
| |
| bool ExtensionViewHost::PreHandleGestureEvent( |
| content::WebContents* source, |
| const blink::WebGestureEvent& event) { |
| // Disable pinch zooming. |
| return event.type == blink::WebGestureEvent::GesturePinchBegin || |
| event.type == blink::WebGestureEvent::GesturePinchUpdate || |
| event.type == blink::WebGestureEvent::GesturePinchEnd; |
| } |
| |
| content::ColorChooser* ExtensionViewHost::OpenColorChooser( |
| WebContents* web_contents, |
| SkColor initial_color, |
| const std::vector<content::ColorSuggestion>& suggestions) { |
| // Similar to the file chooser below, opening a color chooser requires a |
| // visible <input> element to click on. Therefore this code only exists for |
| // extensions with a view. |
| return chrome::ShowColorChooser(web_contents, initial_color); |
| } |
| |
| void ExtensionViewHost::RunFileChooser( |
| WebContents* tab, |
| const content::FileChooserParams& params) { |
| // For security reasons opening a file picker requires a visible <input> |
| // element to click on, so this code only exists for extensions with a view. |
| FileSelectHelper::RunFileChooser(tab, params); |
| } |
| |
| |
| void ExtensionViewHost::ResizeDueToAutoResize(WebContents* source, |
| const gfx::Size& new_size) { |
| view_->ResizeDueToAutoResize(new_size); |
| } |
| |
| // content::WebContentsObserver overrides: |
| |
| void ExtensionViewHost::RenderViewCreated(RenderViewHost* render_view_host) { |
| ExtensionHost::RenderViewCreated(render_view_host); |
| |
| view_->RenderViewCreated(); |
| |
| // If the host is bound to a window, then extract its id. Extensions hosted |
| // in ExternalTabContainer objects may not have an associated window. |
| WindowController* window = GetExtensionWindowController(); |
| if (window) { |
| render_view_host->Send(new ExtensionMsg_UpdateBrowserWindowId( |
| render_view_host->GetRoutingID(), window->GetWindowId())); |
| } |
| } |
| |
| // web_modal::WebContentsModalDialogManagerDelegate overrides: |
| |
| web_modal::WebContentsModalDialogHost* |
| ExtensionViewHost::GetWebContentsModalDialogHost() { |
| return this; |
| } |
| |
| bool ExtensionViewHost::IsWebContentsVisible(WebContents* web_contents) { |
| return platform_util::IsVisible(web_contents->GetNativeView()); |
| } |
| |
| gfx::NativeView ExtensionViewHost::GetHostView() const { |
| return view_->native_view(); |
| } |
| |
| gfx::Point ExtensionViewHost::GetDialogPosition(const gfx::Size& size) { |
| if (!GetVisibleWebContents()) |
| return gfx::Point(); |
| gfx::Rect bounds = GetVisibleWebContents()->GetViewBounds(); |
| return gfx::Point( |
| std::max(0, (bounds.width() - size.width()) / 2), |
| std::max(0, (bounds.height() - size.height()) / 2)); |
| } |
| |
| gfx::Size ExtensionViewHost::GetMaximumDialogSize() { |
| if (!GetVisibleWebContents()) |
| return gfx::Size(); |
| return GetVisibleWebContents()->GetViewBounds().size(); |
| } |
| |
| void ExtensionViewHost::AddObserver( |
| web_modal::ModalDialogHostObserver* observer) { |
| } |
| |
| void ExtensionViewHost::RemoveObserver( |
| web_modal::ModalDialogHostObserver* observer) { |
| } |
| |
| WindowController* ExtensionViewHost::GetExtensionWindowController() const { |
| return view_->browser() ? view_->browser()->extension_window_controller() |
| : NULL; |
| } |
| |
| WebContents* ExtensionViewHost::GetAssociatedWebContents() const { |
| return associated_web_contents_; |
| } |
| |
| WebContents* ExtensionViewHost::GetVisibleWebContents() const { |
| if (associated_web_contents_) |
| return associated_web_contents_; |
| if (extension_host_type() == VIEW_TYPE_EXTENSION_POPUP) |
| return host_contents(); |
| return NULL; |
| } |
| |
| void ExtensionViewHost::Observe(int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| if (type == chrome::NOTIFICATION_EXTENSION_BACKGROUND_PAGE_READY) { |
| DCHECK(ExtensionSystem::Get(browser_context())-> |
| runtime_data()->IsBackgroundPageReady(extension())); |
| LoadInitialURL(); |
| return; |
| } |
| ExtensionHost::Observe(type, source, details); |
| } |
| |
| void ExtensionViewHost::InsertInfobarCSS() { |
| static const base::StringPiece css( |
| ResourceBundle::GetSharedInstance().GetRawDataResource( |
| IDR_EXTENSIONS_INFOBAR_CSS)); |
| |
| host_contents()->InsertCSS(css.as_string()); |
| } |
| |
| } // namespace extensions |