Part 1/3 of http://crrev.com/225043005: Fire "contextmenu" event on
[chromium-blink-merge.git] / chrome / browser / guest_view / web_view / web_view_guest.cc
blobc964e87f9e8303773628bdc2a96b8504132922eb
1 // Copyright 2014 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.
5 #include "chrome/browser/guest_view/web_view/web_view_guest.h"
7 #include "base/message_loop/message_loop.h"
8 #include "base/strings/stringprintf.h"
9 #include "chrome/browser/chrome_notification_types.h"
10 #include "chrome/browser/extensions/api/web_request/web_request_api.h"
11 #include "chrome/browser/extensions/api/webview/webview_api.h"
12 #include "chrome/browser/extensions/chrome_extension_web_contents_observer.h"
13 #include "chrome/browser/extensions/extension_renderer_state.h"
14 #include "chrome/browser/extensions/menu_manager.h"
15 #include "chrome/browser/extensions/script_executor.h"
16 #include "chrome/browser/favicon/favicon_tab_helper.h"
17 #include "chrome/browser/guest_view/guest_view_constants.h"
18 #include "chrome/browser/guest_view/web_view/web_view_constants.h"
19 #include "chrome/browser/guest_view/web_view/web_view_permission_types.h"
20 #include "chrome/browser/renderer_context_menu/context_menu_delegate.h"
21 #include "chrome/browser/renderer_context_menu/render_view_context_menu.h"
22 #include "chrome/common/chrome_version_info.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "content/public/browser/geolocation_permission_context.h"
25 #include "content/public/browser/native_web_keyboard_event.h"
26 #include "content/public/browser/navigation_entry.h"
27 #include "content/public/browser/notification_details.h"
28 #include "content/public/browser/notification_service.h"
29 #include "content/public/browser/notification_source.h"
30 #include "content/public/browser/notification_types.h"
31 #include "content/public/browser/render_process_host.h"
32 #include "content/public/browser/resource_request_details.h"
33 #include "content/public/browser/site_instance.h"
34 #include "content/public/browser/storage_partition.h"
35 #include "content/public/browser/user_metrics.h"
36 #include "content/public/browser/web_contents.h"
37 #include "content/public/browser/web_contents_delegate.h"
38 #include "content/public/common/media_stream_request.h"
39 #include "content/public/common/page_zoom.h"
40 #include "content/public/common/result_codes.h"
41 #include "content/public/common/stop_find_action.h"
42 #include "extensions/common/constants.h"
43 #include "net/base/net_errors.h"
44 #include "third_party/WebKit/public/web/WebFindOptions.h"
45 #include "ui/base/models/simple_menu_model.h"
47 #if defined(ENABLE_PLUGINS)
48 #include "chrome/browser/guest_view/web_view/plugin_permission_helper.h"
49 #endif
51 #if defined(OS_CHROMEOS)
52 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
53 #endif
55 using base::UserMetricsAction;
56 using content::WebContents;
58 namespace {
60 static std::string TerminationStatusToString(base::TerminationStatus status) {
61 switch (status) {
62 case base::TERMINATION_STATUS_NORMAL_TERMINATION:
63 return "normal";
64 case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
65 case base::TERMINATION_STATUS_STILL_RUNNING:
66 return "abnormal";
67 case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
68 return "killed";
69 case base::TERMINATION_STATUS_PROCESS_CRASHED:
70 #if defined(OS_ANDROID)
71 case base::TERMINATION_STATUS_OOM_PROTECTED:
72 #endif
73 return "crashed";
74 case base::TERMINATION_STATUS_MAX_ENUM:
75 break;
77 NOTREACHED() << "Unknown Termination Status.";
78 return "unknown";
81 static std::string PermissionTypeToString(BrowserPluginPermissionType type) {
82 switch (type) {
83 case BROWSER_PLUGIN_PERMISSION_TYPE_NEW_WINDOW:
84 return webview::kPermissionTypeNewWindow;
85 case BROWSER_PLUGIN_PERMISSION_TYPE_UNKNOWN:
86 NOTREACHED();
87 break;
88 default: {
89 WebViewPermissionType webview = static_cast<WebViewPermissionType>(type);
90 switch (webview) {
91 case WEB_VIEW_PERMISSION_TYPE_DOWNLOAD:
92 return webview::kPermissionTypeDownload;
93 case WEB_VIEW_PERMISSION_TYPE_GEOLOCATION:
94 return webview::kPermissionTypeGeolocation;
95 case WEB_VIEW_PERMISSION_TYPE_JAVASCRIPT_DIALOG:
96 return webview::kPermissionTypeDialog;
97 case WEB_VIEW_PERMISSION_TYPE_LOAD_PLUGIN:
98 return webview::kPermissionTypeLoadPlugin;
99 case WEB_VIEW_PERMISSION_TYPE_MEDIA:
100 return webview::kPermissionTypeMedia;
101 case WEB_VIEW_PERMISSION_TYPE_POINTER_LOCK:
102 return webview::kPermissionTypePointerLock;
104 NOTREACHED();
107 return std::string();
110 void RemoveWebViewEventListenersOnIOThread(
111 void* profile,
112 const std::string& extension_id,
113 int embedder_process_id,
114 int view_instance_id) {
115 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
116 ExtensionWebRequestEventRouter::GetInstance()->RemoveWebViewEventListeners(
117 profile,
118 extension_id,
119 embedder_process_id,
120 view_instance_id);
123 void AttachWebViewHelpers(WebContents* contents) {
124 FaviconTabHelper::CreateForWebContents(contents);
125 extensions::ChromeExtensionWebContentsObserver::CreateForWebContents(
126 contents);
127 #if defined(ENABLE_PLUGINS)
128 PluginPermissionHelper::CreateForWebContents(contents);
129 #endif
132 } // namespace
134 WebViewGuest::WebViewGuest(WebContents* guest_web_contents,
135 const std::string& extension_id)
136 : GuestView<WebViewGuest>(guest_web_contents, extension_id),
137 WebContentsObserver(guest_web_contents),
138 script_executor_(new extensions::ScriptExecutor(guest_web_contents,
139 &script_observers_)),
140 current_context_menu_request_id_(0),
141 next_permission_request_id_(0),
142 is_overriding_user_agent_(false),
143 pending_reload_on_attachment_(false),
144 main_frame_id_(0),
145 chromevox_injected_(false),
146 find_helper_(this),
147 javascript_dialog_helper_(this) {
148 notification_registrar_.Add(
149 this, content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
150 content::Source<WebContents>(guest_web_contents));
152 notification_registrar_.Add(
153 this, content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT,
154 content::Source<WebContents>(guest_web_contents));
156 #if defined(OS_CHROMEOS)
157 chromeos::AccessibilityManager* accessibility_manager =
158 chromeos::AccessibilityManager::Get();
159 CHECK(accessibility_manager);
160 accessibility_subscription_ = accessibility_manager->RegisterCallback(
161 base::Bind(&WebViewGuest::OnAccessibilityStatusChanged,
162 base::Unretained(this)));
163 #endif
165 AttachWebViewHelpers(guest_web_contents);
168 // static
169 const char WebViewGuest::Type[] = "webview";
171 // static.
172 int WebViewGuest::GetViewInstanceId(WebContents* contents) {
173 WebViewGuest* guest = FromWebContents(contents);
174 if (!guest)
175 return guestview::kInstanceIDNone;
177 return guest->view_instance_id();
180 // static
181 void WebViewGuest::RecordUserInitiatedUMA(const PermissionResponseInfo& info,
182 bool allow) {
183 if (allow) {
184 // Note that |allow| == true means the embedder explicitly allowed the
185 // request. For some requests they might still fail. An example of such
186 // scenario would be: an embedder allows geolocation request but doesn't
187 // have geolocation access on its own.
188 switch (info.permission_type) {
189 case BROWSER_PLUGIN_PERMISSION_TYPE_NEW_WINDOW:
190 content::RecordAction(
191 UserMetricsAction("BrowserPlugin.PermissionAllow.NewWindow"));
192 break;
193 case BROWSER_PLUGIN_PERMISSION_TYPE_UNKNOWN:
194 break;
195 default: {
196 WebViewPermissionType webview_permission_type =
197 static_cast<WebViewPermissionType>(info.permission_type);
198 switch (webview_permission_type) {
199 case WEB_VIEW_PERMISSION_TYPE_DOWNLOAD:
200 content::RecordAction(
201 UserMetricsAction("WebView.PermissionAllow.Download"));
202 break;
203 case WEB_VIEW_PERMISSION_TYPE_GEOLOCATION:
204 content::RecordAction(
205 UserMetricsAction("WebView.PermissionAllow.Geolocation"));
206 break;
207 case WEB_VIEW_PERMISSION_TYPE_JAVASCRIPT_DIALOG:
208 content::RecordAction(
209 UserMetricsAction("WebView.PermissionAllow.JSDialog"));
210 break;
211 case WEB_VIEW_PERMISSION_TYPE_LOAD_PLUGIN:
212 content::RecordAction(
213 UserMetricsAction("WebView.Guest.PermissionAllow.PluginLoad"));
214 case WEB_VIEW_PERMISSION_TYPE_MEDIA:
215 content::RecordAction(
216 UserMetricsAction("WebView.PermissionAllow.Media"));
217 break;
218 case WEB_VIEW_PERMISSION_TYPE_POINTER_LOCK:
219 content::RecordAction(
220 UserMetricsAction("WebView.PermissionAllow.PointerLock"));
221 break;
222 default:
223 break;
227 } else {
228 switch (info.permission_type) {
229 case BROWSER_PLUGIN_PERMISSION_TYPE_NEW_WINDOW:
230 content::RecordAction(
231 UserMetricsAction("BrowserPlugin.PermissionDeny.NewWindow"));
232 break;
233 case BROWSER_PLUGIN_PERMISSION_TYPE_UNKNOWN:
234 break;
235 default: {
236 WebViewPermissionType webview_permission_type =
237 static_cast<WebViewPermissionType>(info.permission_type);
238 switch (webview_permission_type) {
239 case WEB_VIEW_PERMISSION_TYPE_DOWNLOAD:
240 content::RecordAction(
241 UserMetricsAction("WebView.PermissionDeny.Download"));
242 break;
243 case WEB_VIEW_PERMISSION_TYPE_GEOLOCATION:
244 content::RecordAction(
245 UserMetricsAction("WebView.PermissionDeny.Geolocation"));
246 break;
247 case WEB_VIEW_PERMISSION_TYPE_JAVASCRIPT_DIALOG:
248 content::RecordAction(
249 UserMetricsAction("WebView.PermissionDeny.JSDialog"));
250 break;
251 case WEB_VIEW_PERMISSION_TYPE_LOAD_PLUGIN:
252 content::RecordAction(
253 UserMetricsAction("WebView.Guest.PermissionDeny.PluginLoad"));
254 case WEB_VIEW_PERMISSION_TYPE_MEDIA:
255 content::RecordAction(
256 UserMetricsAction("WebView.PermissionDeny.Media"));
257 break;
258 case WEB_VIEW_PERMISSION_TYPE_POINTER_LOCK:
259 content::RecordAction(
260 UserMetricsAction("WebView.PermissionDeny.PointerLock"));
261 break;
262 default:
263 break;
270 // static
271 scoped_ptr<base::ListValue> WebViewGuest::MenuModelToValue(
272 const ui::SimpleMenuModel& menu_model) {
273 scoped_ptr<base::ListValue> items(new base::ListValue());
274 for (int i = 0; i < menu_model.GetItemCount(); ++i) {
275 base::DictionaryValue* item_value = new base::DictionaryValue();
276 // TODO(lazyboy): We need to expose some kind of enum equivalent of
277 // |command_id| instead of plain integers.
278 item_value->SetInteger(webview::kMenuItemCommandId,
279 menu_model.GetCommandIdAt(i));
280 item_value->SetString(webview::kMenuItemLabel, menu_model.GetLabelAt(i));
281 items->Append(item_value);
283 return items.Pass();
286 void WebViewGuest::Attach(WebContents* embedder_web_contents,
287 const base::DictionaryValue& args) {
288 std::string user_agent_override;
289 if (args.GetString(webview::kParameterUserAgentOverride,
290 &user_agent_override)) {
291 SetUserAgentOverride(user_agent_override);
292 } else {
293 SetUserAgentOverride("");
296 GuestViewBase::Attach(embedder_web_contents, args);
298 AddWebViewToExtensionRendererState();
301 bool WebViewGuest::HandleContextMenu(
302 const content::ContextMenuParams& params) {
303 ContextMenuDelegate* menu_delegate =
304 ContextMenuDelegate::FromWebContents(guest_web_contents());
305 DCHECK(menu_delegate);
307 scoped_ptr<RenderViewContextMenu> menu =
308 menu_delegate->BuildMenu(guest_web_contents(), params);
310 // Pass it to embedder.
311 int request_id = ++current_context_menu_request_id_;
312 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
313 scoped_ptr<base::ListValue> items = MenuModelToValue(menu->menu_model());
314 args->Set(webview::kContextMenuItems, items.release());
315 args->SetInteger(webview::kRequestId, request_id);
316 DispatchEvent(new GuestView::Event(webview::kEventContextMenu, args.Pass()));
318 menu_delegate->ShowMenu(menu.Pass());
319 return true;
322 void WebViewGuest::AddMessageToConsole(int32 level,
323 const base::string16& message,
324 int32 line_no,
325 const base::string16& source_id) {
326 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
327 // Log levels are from base/logging.h: LogSeverity.
328 args->SetInteger(webview::kLevel, level);
329 args->SetString(webview::kMessage, message);
330 args->SetInteger(webview::kLine, line_no);
331 args->SetString(webview::kSourceId, source_id);
332 DispatchEvent(
333 new GuestViewBase::Event(webview::kEventConsoleMessage, args.Pass()));
336 void WebViewGuest::Close() {
337 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
338 DispatchEvent(new GuestViewBase::Event(webview::kEventClose, args.Pass()));
341 void WebViewGuest::DidAttach() {
342 if (pending_reload_on_attachment_) {
343 pending_reload_on_attachment_ = false;
344 guest_web_contents()->GetController().Reload(false);
348 void WebViewGuest::EmbedderDestroyed() {
349 // TODO(fsamuel): WebRequest event listeners for <webview> should survive
350 // reparenting of a <webview> within a single embedder. Right now, we keep
351 // around the browser state for the listener for the lifetime of the embedder.
352 // Ideally, the lifetime of the listeners should match the lifetime of the
353 // <webview> DOM node. Once http://crbug.com/156219 is resolved we can move
354 // the call to RemoveWebViewEventListenersOnIOThread back to
355 // WebViewGuest::WebContentsDestroyed.
356 content::BrowserThread::PostTask(
357 content::BrowserThread::IO,
358 FROM_HERE,
359 base::Bind(
360 &RemoveWebViewEventListenersOnIOThread,
361 browser_context(), embedder_extension_id(),
362 embedder_render_process_id(),
363 view_instance_id()));
366 void WebViewGuest::FindReply(int request_id,
367 int number_of_matches,
368 const gfx::Rect& selection_rect,
369 int active_match_ordinal,
370 bool final_update) {
371 find_helper_.FindReply(request_id, number_of_matches, selection_rect,
372 active_match_ordinal, final_update);
375 void WebViewGuest::GuestProcessGone(base::TerminationStatus status) {
376 // Cancel all find sessions in progress.
377 find_helper_.CancelAllFindSessions();
379 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
380 args->SetInteger(webview::kProcessId,
381 guest_web_contents()->GetRenderProcessHost()->GetID());
382 args->SetString(webview::kReason, TerminationStatusToString(status));
383 DispatchEvent(new GuestViewBase::Event(webview::kEventExit, args.Pass()));
386 bool WebViewGuest::HandleKeyboardEvent(
387 const content::NativeWebKeyboardEvent& event) {
388 if (event.type != blink::WebInputEvent::RawKeyDown)
389 return false;
391 #if defined(OS_MACOSX)
392 if (event.modifiers != blink::WebInputEvent::MetaKey)
393 return false;
395 if (event.windowsKeyCode == ui::VKEY_OEM_4) {
396 Go(-1);
397 return true;
400 if (event.windowsKeyCode == ui::VKEY_OEM_6) {
401 Go(1);
402 return true;
404 #else
405 if (event.windowsKeyCode == ui::VKEY_BROWSER_BACK) {
406 Go(-1);
407 return true;
410 if (event.windowsKeyCode == ui::VKEY_BROWSER_FORWARD) {
411 Go(1);
412 return true;
414 #endif
415 return false;
418 bool WebViewGuest::IsDragAndDropEnabled() {
419 return true;
422 bool WebViewGuest::IsOverridingUserAgent() const {
423 return is_overriding_user_agent_;
426 void WebViewGuest::LoadProgressed(double progress) {
427 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
428 args->SetString(guestview::kUrl, guest_web_contents()->GetURL().spec());
429 args->SetDouble(webview::kProgress, progress);
430 DispatchEvent(
431 new GuestViewBase::Event(webview::kEventLoadProgress, args.Pass()));
434 void WebViewGuest::LoadAbort(bool is_top_level,
435 const GURL& url,
436 const std::string& error_type) {
437 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
438 args->SetBoolean(guestview::kIsTopLevel, is_top_level);
439 args->SetString(guestview::kUrl, url.possibly_invalid_spec());
440 args->SetString(guestview::kReason, error_type);
441 DispatchEvent(
442 new GuestViewBase::Event(webview::kEventLoadAbort, args.Pass()));
445 // TODO(fsamuel): Find a reliable way to test the 'responsive' and
446 // 'unresponsive' events.
447 void WebViewGuest::RendererResponsive() {
448 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
449 args->SetInteger(webview::kProcessId,
450 guest_web_contents()->GetRenderProcessHost()->GetID());
451 DispatchEvent(
452 new GuestViewBase::Event(webview::kEventResponsive, args.Pass()));
455 void WebViewGuest::RendererUnresponsive() {
456 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
457 args->SetInteger(webview::kProcessId,
458 guest_web_contents()->GetRenderProcessHost()->GetID());
459 DispatchEvent(
460 new GuestViewBase::Event(webview::kEventUnresponsive, args.Pass()));
463 void WebViewGuest::RequestPermission(
464 BrowserPluginPermissionType permission_type,
465 const base::DictionaryValue& request_info,
466 const PermissionResponseCallback& callback,
467 bool allowed_by_default) {
468 RequestPermissionInternal(permission_type,
469 request_info,
470 callback,
471 allowed_by_default);
474 void WebViewGuest::Observe(int type,
475 const content::NotificationSource& source,
476 const content::NotificationDetails& details) {
477 switch (type) {
478 case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME: {
479 DCHECK_EQ(content::Source<WebContents>(source).ptr(),
480 guest_web_contents());
481 if (content::Source<WebContents>(source).ptr() == guest_web_contents())
482 LoadHandlerCalled();
483 break;
485 case content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT: {
486 DCHECK_EQ(content::Source<WebContents>(source).ptr(),
487 guest_web_contents());
488 content::ResourceRedirectDetails* resource_redirect_details =
489 content::Details<content::ResourceRedirectDetails>(details).ptr();
490 bool is_top_level =
491 resource_redirect_details->resource_type == ResourceType::MAIN_FRAME;
492 LoadRedirect(resource_redirect_details->url,
493 resource_redirect_details->new_url,
494 is_top_level);
495 break;
497 default:
498 NOTREACHED() << "Unexpected notification sent.";
499 break;
503 void WebViewGuest::SetZoom(double zoom_factor) {
504 double zoom_level = content::ZoomFactorToZoomLevel(zoom_factor);
505 guest_web_contents()->SetZoomLevel(zoom_level);
507 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
508 args->SetDouble(webview::kOldZoomFactor, current_zoom_factor_);
509 args->SetDouble(webview::kNewZoomFactor, zoom_factor);
510 DispatchEvent(
511 new GuestViewBase::Event(webview::kEventZoomChange, args.Pass()));
513 current_zoom_factor_ = zoom_factor;
516 double WebViewGuest::GetZoom() {
517 return current_zoom_factor_;
520 void WebViewGuest::Find(
521 const base::string16& search_text,
522 const blink::WebFindOptions& options,
523 scoped_refptr<extensions::WebviewFindFunction> find_function) {
524 find_helper_.Find(guest_web_contents(), search_text, options, find_function);
527 void WebViewGuest::StopFinding(content::StopFindAction action) {
528 find_helper_.CancelAllFindSessions();
529 guest_web_contents()->StopFinding(action);
532 void WebViewGuest::Go(int relative_index) {
533 guest_web_contents()->GetController().GoToOffset(relative_index);
536 void WebViewGuest::Reload() {
537 // TODO(fsamuel): Don't check for repost because we don't want to show
538 // Chromium's repost warning. We might want to implement a separate API
539 // for registering a callback if a repost is about to happen.
540 guest_web_contents()->GetController().Reload(false);
544 void WebViewGuest::RequestGeolocationPermission(
545 int bridge_id,
546 const GURL& requesting_frame,
547 bool user_gesture,
548 const base::Callback<void(bool)>& callback) {
549 base::DictionaryValue request_info;
550 request_info.Set(guestview::kUrl,
551 base::Value::CreateStringValue(requesting_frame.spec()));
552 request_info.Set(guestview::kUserGesture,
553 base::Value::CreateBooleanValue(user_gesture));
555 // It is safe to hold an unretained pointer to WebViewGuest because this
556 // callback is called from WebViewGuest::SetPermission.
557 const PermissionResponseCallback permission_callback =
558 base::Bind(&WebViewGuest::OnWebViewGeolocationPermissionResponse,
559 base::Unretained(this),
560 bridge_id,
561 user_gesture,
562 callback);
563 int request_id = RequestPermissionInternal(
564 static_cast<BrowserPluginPermissionType>(
565 WEB_VIEW_PERMISSION_TYPE_GEOLOCATION),
566 request_info,
567 permission_callback,
568 false /* allowed_by_default */);
569 bridge_id_to_request_id_map_[bridge_id] = request_id;
572 void WebViewGuest::OnWebViewGeolocationPermissionResponse(
573 int bridge_id,
574 bool user_gesture,
575 const base::Callback<void(bool)>& callback,
576 bool allow,
577 const std::string& user_input) {
578 // The <webview> embedder has allowed the permission. We now need to make sure
579 // that the embedder has geolocation permission.
580 RemoveBridgeID(bridge_id);
582 if (!allow || !attached()) {
583 callback.Run(false);
584 return;
587 content::GeolocationPermissionContext* geolocation_context =
588 browser_context()->GetGeolocationPermissionContext();
590 DCHECK(geolocation_context);
591 geolocation_context->RequestGeolocationPermission(
592 embedder_web_contents()->GetRenderProcessHost()->GetID(),
593 embedder_web_contents()->GetRoutingID(),
594 // The geolocation permission request here is not initiated
595 // through WebGeolocationPermissionRequest. We are only interested
596 // in the fact whether the embedder/app has geolocation
597 // permission. Therefore we use an invalid |bridge_id|.
598 -1 /* bridge_id */,
599 embedder_web_contents()->GetLastCommittedURL(),
600 user_gesture,
601 callback);
604 void WebViewGuest::CancelGeolocationPermissionRequest(int bridge_id) {
605 int request_id = RemoveBridgeID(bridge_id);
606 RequestMap::iterator request_itr =
607 pending_permission_requests_.find(request_id);
609 if (request_itr == pending_permission_requests_.end())
610 return;
612 pending_permission_requests_.erase(request_itr);
615 void WebViewGuest::OnWebViewMediaPermissionResponse(
616 const content::MediaStreamRequest& request,
617 const content::MediaResponseCallback& callback,
618 bool allow,
619 const std::string& user_input) {
620 if (!allow || !attached()) {
621 // Deny the request.
622 callback.Run(content::MediaStreamDevices(),
623 content::MEDIA_DEVICE_INVALID_STATE,
624 scoped_ptr<content::MediaStreamUI>());
625 return;
627 if (!embedder_web_contents()->GetDelegate())
628 return;
630 embedder_web_contents()->GetDelegate()->
631 RequestMediaAccessPermission(embedder_web_contents(), request, callback);
634 void WebViewGuest::OnWebViewDownloadPermissionResponse(
635 const base::Callback<void(bool)>& callback,
636 bool allow,
637 const std::string& user_input) {
638 callback.Run(allow && attached());
641 void WebViewGuest::OnWebViewPointerLockPermissionResponse(
642 const base::Callback<void(bool)>& callback,
643 bool allow,
644 const std::string& user_input) {
645 callback.Run(allow && attached());
648 WebViewGuest::SetPermissionResult WebViewGuest::SetPermission(
649 int request_id,
650 PermissionResponseAction action,
651 const std::string& user_input) {
652 RequestMap::iterator request_itr =
653 pending_permission_requests_.find(request_id);
655 if (request_itr == pending_permission_requests_.end())
656 return SET_PERMISSION_INVALID;
658 const PermissionResponseInfo& info = request_itr->second;
659 bool allow = (action == ALLOW) ||
660 ((action == DEFAULT) && info.allowed_by_default);
662 info.callback.Run(allow, user_input);
664 // Only record user initiated (i.e. non-default) actions.
665 if (action != DEFAULT)
666 RecordUserInitiatedUMA(info, allow);
668 pending_permission_requests_.erase(request_itr);
670 return allow ? SET_PERMISSION_ALLOWED : SET_PERMISSION_DENIED;
673 void WebViewGuest::SetUserAgentOverride(
674 const std::string& user_agent_override) {
675 is_overriding_user_agent_ = !user_agent_override.empty();
676 if (is_overriding_user_agent_) {
677 content::RecordAction(UserMetricsAction("WebView.Guest.OverrideUA"));
679 guest_web_contents()->SetUserAgentOverride(user_agent_override);
682 void WebViewGuest::Stop() {
683 guest_web_contents()->Stop();
686 void WebViewGuest::Terminate() {
687 content::RecordAction(UserMetricsAction("WebView.Guest.Terminate"));
688 base::ProcessHandle process_handle =
689 guest_web_contents()->GetRenderProcessHost()->GetHandle();
690 if (process_handle)
691 base::KillProcess(process_handle, content::RESULT_CODE_KILLED, false);
694 bool WebViewGuest::ClearData(const base::Time remove_since,
695 uint32 removal_mask,
696 const base::Closure& callback) {
697 content::RecordAction(UserMetricsAction("WebView.Guest.ClearData"));
698 content::StoragePartition* partition =
699 content::BrowserContext::GetStoragePartition(
700 guest_web_contents()->GetBrowserContext(),
701 guest_web_contents()->GetSiteInstance());
703 if (!partition)
704 return false;
706 partition->ClearData(
707 removal_mask,
708 content::StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL,
709 GURL(),
710 content::StoragePartition::OriginMatcherFunction(),
711 remove_since,
712 base::Time::Now(),
713 callback);
714 return true;
717 WebViewGuest::~WebViewGuest() {
720 void WebViewGuest::DidCommitProvisionalLoadForFrame(
721 int64 frame_id,
722 const base::string16& frame_unique_name,
723 bool is_main_frame,
724 const GURL& url,
725 content::PageTransition transition_type,
726 content::RenderViewHost* render_view_host) {
727 find_helper_.CancelAllFindSessions();
729 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
730 args->SetString(guestview::kUrl, url.spec());
731 args->SetBoolean(guestview::kIsTopLevel, is_main_frame);
732 args->SetInteger(webview::kInternalCurrentEntryIndex,
733 guest_web_contents()->GetController().GetCurrentEntryIndex());
734 args->SetInteger(webview::kInternalEntryCount,
735 guest_web_contents()->GetController().GetEntryCount());
736 args->SetInteger(webview::kInternalProcessId,
737 guest_web_contents()->GetRenderProcessHost()->GetID());
738 DispatchEvent(
739 new GuestViewBase::Event(webview::kEventLoadCommit, args.Pass()));
741 // Update the current zoom factor for the new page.
742 current_zoom_factor_ = content::ZoomLevelToZoomFactor(
743 guest_web_contents()->GetZoomLevel());
745 if (is_main_frame) {
746 chromevox_injected_ = false;
747 main_frame_id_ = frame_id;
751 void WebViewGuest::DidFailProvisionalLoad(
752 int64 frame_id,
753 const base::string16& frame_unique_name,
754 bool is_main_frame,
755 const GURL& validated_url,
756 int error_code,
757 const base::string16& error_description,
758 content::RenderViewHost* render_view_host) {
759 // Translate the |error_code| into an error string.
760 std::string error_type;
761 base::RemoveChars(net::ErrorToString(error_code), "net::", &error_type);
762 LoadAbort(is_main_frame, validated_url, error_type);
765 void WebViewGuest::DidStartProvisionalLoadForFrame(
766 int64 frame_id,
767 int64 parent_frame_id,
768 bool is_main_frame,
769 const GURL& validated_url,
770 bool is_error_page,
771 bool is_iframe_srcdoc,
772 content::RenderViewHost* render_view_host) {
773 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
774 args->SetString(guestview::kUrl, validated_url.spec());
775 args->SetBoolean(guestview::kIsTopLevel, is_main_frame);
776 DispatchEvent(
777 new GuestViewBase::Event(webview::kEventLoadStart, args.Pass()));
780 void WebViewGuest::DocumentLoadedInFrame(
781 int64 frame_id,
782 content::RenderViewHost* render_view_host) {
783 if (frame_id == main_frame_id_)
784 InjectChromeVoxIfNeeded(render_view_host);
787 void WebViewGuest::DidStopLoading(content::RenderViewHost* render_view_host) {
788 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
789 DispatchEvent(new GuestViewBase::Event(webview::kEventLoadStop, args.Pass()));
792 void WebViewGuest::WebContentsDestroyed(WebContents* web_contents) {
793 // Clean up custom context menu items for this guest.
794 extensions::MenuManager* menu_manager = extensions::MenuManager::Get(
795 Profile::FromBrowserContext(browser_context()));
796 menu_manager->RemoveAllContextItems(extensions::MenuItem::ExtensionKey(
797 embedder_extension_id(), view_instance_id()));
799 RemoveWebViewFromExtensionRendererState(web_contents);
802 void WebViewGuest::UserAgentOverrideSet(const std::string& user_agent) {
803 content::NavigationController& controller =
804 guest_web_contents()->GetController();
805 content::NavigationEntry* entry = controller.GetVisibleEntry();
806 if (!entry)
807 return;
808 entry->SetIsOverridingUserAgent(!user_agent.empty());
809 if (!attached()) {
810 // We cannot reload now because all resource loads are suspended until
811 // attachment.
812 pending_reload_on_attachment_ = true;
813 return;
815 guest_web_contents()->GetController().Reload(false);
818 void WebViewGuest::LoadHandlerCalled() {
819 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
820 DispatchEvent(
821 new GuestViewBase::Event(webview::kEventContentLoad, args.Pass()));
824 void WebViewGuest::LoadRedirect(const GURL& old_url,
825 const GURL& new_url,
826 bool is_top_level) {
827 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
828 args->SetBoolean(guestview::kIsTopLevel, is_top_level);
829 args->SetString(webview::kNewURL, new_url.spec());
830 args->SetString(webview::kOldURL, old_url.spec());
831 DispatchEvent(
832 new GuestViewBase::Event(webview::kEventLoadRedirect, args.Pass()));
835 void WebViewGuest::AddWebViewToExtensionRendererState() {
836 const GURL& site_url = guest_web_contents()->GetSiteInstance()->GetSiteURL();
837 std::string partition_domain;
838 std::string partition_id;
839 bool in_memory;
840 if (!GetGuestPartitionConfigForSite(
841 site_url, &partition_domain, &partition_id, &in_memory)) {
842 NOTREACHED();
843 return;
845 DCHECK(embedder_extension_id() == partition_domain);
847 ExtensionRendererState::WebViewInfo webview_info;
848 webview_info.embedder_process_id = embedder_render_process_id();
849 webview_info.instance_id = view_instance_id();
850 webview_info.partition_id = partition_id;
851 webview_info.embedder_extension_id = embedder_extension_id();
853 content::BrowserThread::PostTask(
854 content::BrowserThread::IO, FROM_HERE,
855 base::Bind(
856 &ExtensionRendererState::AddWebView,
857 base::Unretained(ExtensionRendererState::GetInstance()),
858 guest_web_contents()->GetRenderProcessHost()->GetID(),
859 guest_web_contents()->GetRoutingID(),
860 webview_info));
863 // static
864 void WebViewGuest::RemoveWebViewFromExtensionRendererState(
865 WebContents* web_contents) {
866 content::BrowserThread::PostTask(
867 content::BrowserThread::IO, FROM_HERE,
868 base::Bind(
869 &ExtensionRendererState::RemoveWebView,
870 base::Unretained(ExtensionRendererState::GetInstance()),
871 web_contents->GetRenderProcessHost()->GetID(),
872 web_contents->GetRoutingID()));
875 GURL WebViewGuest::ResolveURL(const std::string& src) {
876 if (!in_extension()) {
877 NOTREACHED();
878 return GURL(src);
881 GURL default_url(base::StringPrintf("%s://%s/",
882 extensions::kExtensionScheme,
883 embedder_extension_id().c_str()));
884 return default_url.Resolve(src);
887 void WebViewGuest::SizeChanged(const gfx::Size& old_size,
888 const gfx::Size& new_size) {
889 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue());
890 args->SetInteger(webview::kOldHeight, old_size.height());
891 args->SetInteger(webview::kOldWidth, old_size.width());
892 args->SetInteger(webview::kNewHeight, new_size.height());
893 args->SetInteger(webview::kNewWidth, new_size.width());
894 DispatchEvent(
895 new GuestViewBase::Event(webview::kEventSizeChanged, args.Pass()));
898 void WebViewGuest::RequestMediaAccessPermission(
899 const content::MediaStreamRequest& request,
900 const content::MediaResponseCallback& callback) {
901 base::DictionaryValue request_info;
902 request_info.Set(
903 guestview::kUrl,
904 base::Value::CreateStringValue(request.security_origin.spec()));
905 RequestPermission(static_cast<BrowserPluginPermissionType>(
906 WEB_VIEW_PERMISSION_TYPE_MEDIA),
907 request_info,
908 base::Bind(&WebViewGuest::OnWebViewMediaPermissionResponse,
909 base::Unretained(this),
910 request,
911 callback),
912 false /* allowed_by_default */);
915 void WebViewGuest::CanDownload(
916 const std::string& request_method,
917 const GURL& url,
918 const base::Callback<void(bool)>& callback) {
919 base::DictionaryValue request_info;
920 request_info.Set(
921 guestview::kUrl,
922 base::Value::CreateStringValue(url.spec()));
923 RequestPermission(
924 static_cast<BrowserPluginPermissionType>(
925 WEB_VIEW_PERMISSION_TYPE_DOWNLOAD),
926 request_info,
927 base::Bind(&WebViewGuest::OnWebViewDownloadPermissionResponse,
928 base::Unretained(this),
929 callback),
930 false /* allowed_by_default */);
933 void WebViewGuest::RequestPointerLockPermission(
934 bool user_gesture,
935 bool last_unlocked_by_target,
936 const base::Callback<void(bool)>& callback) {
937 base::DictionaryValue request_info;
938 request_info.Set(guestview::kUserGesture,
939 base::Value::CreateBooleanValue(user_gesture));
940 request_info.Set(webview::kLastUnlockedBySelf,
941 base::Value::CreateBooleanValue(last_unlocked_by_target));
942 request_info.Set(guestview::kUrl,
943 base::Value::CreateStringValue(
944 guest_web_contents()->GetLastCommittedURL().spec()));
946 RequestPermission(
947 static_cast<BrowserPluginPermissionType>(
948 WEB_VIEW_PERMISSION_TYPE_POINTER_LOCK),
949 request_info,
950 base::Bind(&WebViewGuest::OnWebViewPointerLockPermissionResponse,
951 base::Unretained(this),
952 callback),
953 false /* allowed_by_default */);
956 content::JavaScriptDialogManager*
957 WebViewGuest::GetJavaScriptDialogManager() {
958 return &javascript_dialog_helper_;
961 #if defined(OS_CHROMEOS)
962 void WebViewGuest::OnAccessibilityStatusChanged(
963 const chromeos::AccessibilityStatusEventDetails& details) {
964 if (details.notification_type == chromeos::ACCESSIBILITY_MANAGER_SHUTDOWN) {
965 accessibility_subscription_.reset();
966 } else if (details.notification_type ==
967 chromeos::ACCESSIBILITY_TOGGLE_SPOKEN_FEEDBACK) {
968 if (details.enabled)
969 InjectChromeVoxIfNeeded(guest_web_contents()->GetRenderViewHost());
970 else
971 chromevox_injected_ = false;
974 #endif
976 void WebViewGuest::InjectChromeVoxIfNeeded(
977 content::RenderViewHost* render_view_host) {
978 #if defined(OS_CHROMEOS)
979 if (!chromevox_injected_) {
980 chromeos::AccessibilityManager* manager =
981 chromeos::AccessibilityManager::Get();
982 if (manager && manager->IsSpokenFeedbackEnabled()) {
983 manager->InjectChromeVox(render_view_host);
984 chromevox_injected_ = true;
987 #endif
990 int WebViewGuest::RemoveBridgeID(int bridge_id) {
991 std::map<int, int>::iterator bridge_itr =
992 bridge_id_to_request_id_map_.find(bridge_id);
993 if (bridge_itr == bridge_id_to_request_id_map_.end())
994 return webview::kInvalidPermissionRequestID;
996 int request_id = bridge_itr->second;
997 bridge_id_to_request_id_map_.erase(bridge_itr);
998 return request_id;
1001 int WebViewGuest::RequestPermissionInternal(
1002 BrowserPluginPermissionType permission_type,
1003 const base::DictionaryValue& request_info,
1004 const PermissionResponseCallback& callback,
1005 bool allowed_by_default) {
1006 // If there are too many pending permission requests then reject this request.
1007 if (pending_permission_requests_.size() >=
1008 webview::kMaxOutstandingPermissionRequests) {
1009 // Let the stack unwind before we deny the permission request so that
1010 // objects held by the permission request are not destroyed immediately
1011 // after creation. This is to allow those same objects to be accessed again
1012 // in the same scope without fear of use after freeing.
1013 base::MessageLoop::current()->PostTask(
1014 FROM_HERE,
1015 base::Bind(&PermissionResponseCallback::Run,
1016 base::Owned(new PermissionResponseCallback(callback)),
1017 allowed_by_default,
1018 std::string()));
1019 return webview::kInvalidPermissionRequestID;
1022 int request_id = next_permission_request_id_++;
1023 pending_permission_requests_[request_id] =
1024 PermissionResponseInfo(callback, permission_type, allowed_by_default);
1025 scoped_ptr<base::DictionaryValue> args(request_info.DeepCopy());
1026 args->SetInteger(webview::kRequestId, request_id);
1027 switch (static_cast<int>(permission_type)) {
1028 case BROWSER_PLUGIN_PERMISSION_TYPE_NEW_WINDOW: {
1029 DispatchEvent(
1030 new GuestViewBase::Event(webview::kEventNewWindow, args.Pass()));
1031 break;
1033 case WEB_VIEW_PERMISSION_TYPE_JAVASCRIPT_DIALOG: {
1034 DispatchEvent(
1035 new GuestViewBase::Event(webview::kEventDialog, args.Pass()));
1036 break;
1038 default: {
1039 args->SetString(webview::kPermission,
1040 PermissionTypeToString(permission_type));
1041 DispatchEvent(new GuestViewBase::Event(webview::kEventPermissionRequest,
1042 args.Pass()));
1043 break;
1046 return request_id;
1049 WebViewGuest::PermissionResponseInfo::PermissionResponseInfo()
1050 : permission_type(BROWSER_PLUGIN_PERMISSION_TYPE_UNKNOWN),
1051 allowed_by_default(false) {
1054 WebViewGuest::PermissionResponseInfo::PermissionResponseInfo(
1055 const PermissionResponseCallback& callback,
1056 BrowserPluginPermissionType permission_type,
1057 bool allowed_by_default)
1058 : callback(callback),
1059 permission_type(permission_type),
1060 allowed_by_default(allowed_by_default) {
1063 WebViewGuest::PermissionResponseInfo::~PermissionResponseInfo() {