// 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/extension_service.h"
#include "chrome/browser/extensions/extension_system.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 "chrome/common/extensions/extension_messages.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 "content/public/browser/web_contents_view.h"
#include "grit/browser_resources.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(WebContents* web_contents) 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();
#elif defined(TOOLKIT_GTK)
view_.reset(new ExtensionViewGtk(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::GetForBrowserContext(browser_context())->
extension_service()->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);
}
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->GetView()->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()->GetView()->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()->GetView()->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::GetForBrowserContext(browser_context())->
extension_service()->IsBackgroundPageReady(extension()));
LoadInitialURL();
return;
}
ExtensionHost::Observe(type, source, details);
}
void ExtensionViewHost::InsertInfobarCSS() {
static const base::StringPiece css(
ResourceBundle::GetSharedInstance().GetRawDataResource(
IDR_EXTENSIONS_INFOBAR_CSS));
render_view_host()->InsertCSS(base::string16(), css.as_string());
}
} // namespace extensions