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:
AssociatedWebContentsObserver(ExtensionViewHost * host,WebContents * web_contents)39 AssociatedWebContentsObserver(ExtensionViewHost* host,
40 WebContents* web_contents)
41 : WebContentsObserver(web_contents), host_(host) {}
~AssociatedWebContentsObserver()42 virtual ~AssociatedWebContentsObserver() {}
43
44 // content::WebContentsObserver:
WebContentsDestroyed()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
ExtensionViewHost(const Extension * extension,content::SiteInstance * site_instance,const GURL & url,ViewType host_type)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
~ExtensionViewHost()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
CreateView(Browser * browser)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
SetAssociatedWebContents(WebContents * web_contents)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
UnhandledKeyboardEvent(WebContents * source,const content::NativeWebKeyboardEvent & event)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
OnDidStopLoading()124 void ExtensionViewHost::OnDidStopLoading() {
125 DCHECK(did_stop_loading());
126 #if defined(TOOLKIT_VIEWS) || defined(OS_MACOSX)
127 view_->DidStopLoading();
128 #endif
129 }
130
OnDocumentAvailable()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
LoadInitialURL()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
IsBackgroundPage() const158 bool ExtensionViewHost::IsBackgroundPage() const {
159 DCHECK(view_);
160 return false;
161 }
162
163 // content::WebContentsDelegate overrides:
164
OpenURLFromTab(WebContents * source,const OpenURLParams & params)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
PreHandleKeyboardEvent(WebContents * source,const NativeWebKeyboardEvent & event,bool * is_keyboard_shortcut)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
HandleKeyboardEvent(WebContents * source,const NativeWebKeyboardEvent & event)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
PreHandleGestureEvent(content::WebContents * source,const blink::WebGestureEvent & event)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
OpenColorChooser(WebContents * web_contents,SkColor initial_color,const std::vector<content::ColorSuggestion> & suggestions)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
RunFileChooser(WebContents * tab,const content::FileChooserParams & params)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
ResizeDueToAutoResize(WebContents * source,const gfx::Size & new_size)249 void ExtensionViewHost::ResizeDueToAutoResize(WebContents* source,
250 const gfx::Size& new_size) {
251 view_->ResizeDueToAutoResize(new_size);
252 }
253
254 // content::WebContentsObserver overrides:
255
RenderViewCreated(RenderViewHost * render_view_host)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*
GetWebContentsModalDialogHost()273 ExtensionViewHost::GetWebContentsModalDialogHost() {
274 return this;
275 }
276
IsWebContentsVisible(WebContents * web_contents)277 bool ExtensionViewHost::IsWebContentsVisible(WebContents* web_contents) {
278 return platform_util::IsVisible(web_contents->GetNativeView());
279 }
280
GetHostView() const281 gfx::NativeView ExtensionViewHost::GetHostView() const {
282 return view_->native_view();
283 }
284
GetDialogPosition(const gfx::Size & size)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
GetMaximumDialogSize()294 gfx::Size ExtensionViewHost::GetMaximumDialogSize() {
295 if (!GetVisibleWebContents())
296 return gfx::Size();
297 return GetVisibleWebContents()->GetViewBounds().size();
298 }
299
AddObserver(web_modal::ModalDialogHostObserver * observer)300 void ExtensionViewHost::AddObserver(
301 web_modal::ModalDialogHostObserver* observer) {
302 }
303
RemoveObserver(web_modal::ModalDialogHostObserver * observer)304 void ExtensionViewHost::RemoveObserver(
305 web_modal::ModalDialogHostObserver* observer) {
306 }
307
GetExtensionWindowController() const308 WindowController* ExtensionViewHost::GetExtensionWindowController() const {
309 return view_->browser() ? view_->browser()->extension_window_controller()
310 : NULL;
311 }
312
GetAssociatedWebContents() const313 WebContents* ExtensionViewHost::GetAssociatedWebContents() const {
314 return associated_web_contents_;
315 }
316
GetVisibleWebContents() const317 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
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)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
InsertInfobarCSS()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