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