Home | History | Annotate | Download | only in extensions
      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 
     26 using content::NativeWebKeyboardEvent;
     27 using content::OpenURLParams;
     28 using content::RenderViewHost;
     29 using content::WebContents;
     30 using content::WebContentsObserver;
     31 using web_modal::WebContentsModalDialogManager;
     32 
     33 namespace extensions {
     34 
     35 // Notifies an ExtensionViewHost when a WebContents is destroyed.
     36 class 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 
     56 ExtensionViewHost::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 
     69 ExtensionViewHost::~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 
     78 void 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 
     93 void 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 
    104 void 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 
    124 void ExtensionViewHost::OnDidStopLoading() {
    125   DCHECK(did_stop_loading());
    126 #if defined(TOOLKIT_VIEWS) || defined(OS_MACOSX)
    127   view_->DidStopLoading();
    128 #endif
    129 }
    130 
    131 void 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 
    138 void 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 
    158 bool ExtensionViewHost::IsBackgroundPage() const {
    159   DCHECK(view_);
    160   return false;
    161 }
    162 
    163 // content::WebContentsDelegate overrides:
    164 
    165 WebContents* 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 
    187 bool 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 
    208 void 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 
    221 bool 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 
    230 content::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 
    240 void 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 
    249 void ExtensionViewHost::ResizeDueToAutoResize(WebContents* source,
    250                                           const gfx::Size& new_size) {
    251   view_->ResizeDueToAutoResize(new_size);
    252 }
    253 
    254 // content::WebContentsObserver overrides:
    255 
    256 void 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 
    272 web_modal::WebContentsModalDialogHost*
    273 ExtensionViewHost::GetWebContentsModalDialogHost() {
    274   return this;
    275 }
    276 
    277 bool ExtensionViewHost::IsWebContentsVisible(WebContents* web_contents) {
    278   return platform_util::IsVisible(web_contents->GetNativeView());
    279 }
    280 
    281 gfx::NativeView ExtensionViewHost::GetHostView() const {
    282   return view_->native_view();
    283 }
    284 
    285 gfx::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 
    294 gfx::Size ExtensionViewHost::GetMaximumDialogSize() {
    295   if (!GetVisibleWebContents())
    296     return gfx::Size();
    297   return GetVisibleWebContents()->GetViewBounds().size();
    298 }
    299 
    300 void ExtensionViewHost::AddObserver(
    301     web_modal::ModalDialogHostObserver* observer) {
    302 }
    303 
    304 void ExtensionViewHost::RemoveObserver(
    305     web_modal::ModalDialogHostObserver* observer) {
    306 }
    307 
    308 WindowController* ExtensionViewHost::GetExtensionWindowController() const {
    309   return view_->browser() ? view_->browser()->extension_window_controller()
    310                           : NULL;
    311 }
    312 
    313 WebContents* ExtensionViewHost::GetAssociatedWebContents() const {
    314   return associated_web_contents_;
    315 }
    316 
    317 WebContents* 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 
    325 void 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 
    337 void 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