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