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. 4 5 #include "extensions/browser/guest_view/web_view/web_view_guest.h" 6 7 #include "base/message_loop/message_loop.h" 8 #include "base/strings/stringprintf.h" 9 #include "base/strings/utf_string_conversions.h" 10 #include "content/public/browser/browser_context.h" 11 #include "content/public/browser/browser_thread.h" 12 #include "content/public/browser/child_process_security_policy.h" 13 #include "content/public/browser/native_web_keyboard_event.h" 14 #include "content/public/browser/navigation_entry.h" 15 #include "content/public/browser/notification_details.h" 16 #include "content/public/browser/notification_service.h" 17 #include "content/public/browser/notification_source.h" 18 #include "content/public/browser/notification_types.h" 19 #include "content/public/browser/render_process_host.h" 20 #include "content/public/browser/render_view_host.h" 21 #include "content/public/browser/render_widget_host_view.h" 22 #include "content/public/browser/resource_request_details.h" 23 #include "content/public/browser/site_instance.h" 24 #include "content/public/browser/storage_partition.h" 25 #include "content/public/browser/user_metrics.h" 26 #include "content/public/browser/web_contents.h" 27 #include "content/public/browser/web_contents_delegate.h" 28 #include "content/public/common/media_stream_request.h" 29 #include "content/public/common/page_zoom.h" 30 #include "content/public/common/result_codes.h" 31 #include "content/public/common/stop_find_action.h" 32 #include "content/public/common/url_constants.h" 33 #include "extensions/browser/api/extensions_api_client.h" 34 #include "extensions/browser/api/web_view/web_view_internal_api.h" 35 #include "extensions/browser/extension_system.h" 36 #include "extensions/browser/guest_view/guest_view_manager.h" 37 #include "extensions/browser/guest_view/web_view/web_view_constants.h" 38 #include "extensions/browser/guest_view/web_view/web_view_permission_helper.h" 39 #include "extensions/browser/guest_view/web_view/web_view_permission_types.h" 40 #include "extensions/browser/guest_view/web_view/web_view_renderer_state.h" 41 #include "extensions/common/constants.h" 42 #include "extensions/common/extension_messages.h" 43 #include "extensions/common/guest_view/guest_view_constants.h" 44 #include "extensions/strings/grit/extensions_strings.h" 45 #include "ipc/ipc_message_macros.h" 46 #include "net/base/escape.h" 47 #include "net/base/net_errors.h" 48 #include "ui/base/models/simple_menu_model.h" 49 50 using base::UserMetricsAction; 51 using content::RenderFrameHost; 52 using content::ResourceType; 53 using content::WebContents; 54 55 namespace extensions { 56 57 namespace { 58 59 std::string WindowOpenDispositionToString( 60 WindowOpenDisposition window_open_disposition) { 61 switch (window_open_disposition) { 62 case IGNORE_ACTION: 63 return "ignore"; 64 case SAVE_TO_DISK: 65 return "save_to_disk"; 66 case CURRENT_TAB: 67 return "current_tab"; 68 case NEW_BACKGROUND_TAB: 69 return "new_background_tab"; 70 case NEW_FOREGROUND_TAB: 71 return "new_foreground_tab"; 72 case NEW_WINDOW: 73 return "new_window"; 74 case NEW_POPUP: 75 return "new_popup"; 76 default: 77 NOTREACHED() << "Unknown Window Open Disposition"; 78 return "ignore"; 79 } 80 } 81 82 static std::string TerminationStatusToString(base::TerminationStatus status) { 83 switch (status) { 84 case base::TERMINATION_STATUS_NORMAL_TERMINATION: 85 return "normal"; 86 case base::TERMINATION_STATUS_ABNORMAL_TERMINATION: 87 case base::TERMINATION_STATUS_STILL_RUNNING: 88 return "abnormal"; 89 case base::TERMINATION_STATUS_PROCESS_WAS_KILLED: 90 return "killed"; 91 case base::TERMINATION_STATUS_PROCESS_CRASHED: 92 return "crashed"; 93 case base::TERMINATION_STATUS_MAX_ENUM: 94 break; 95 } 96 NOTREACHED() << "Unknown Termination Status."; 97 return "unknown"; 98 } 99 100 std::string GetStoragePartitionIdFromSiteURL(const GURL& site_url) { 101 const std::string& partition_id = site_url.query(); 102 bool persist_storage = site_url.path().find("persist") != std::string::npos; 103 return (persist_storage ? webview::kPersistPrefix : "") + partition_id; 104 } 105 106 void ParsePartitionParam(const base::DictionaryValue& create_params, 107 std::string* storage_partition_id, 108 bool* persist_storage) { 109 std::string partition_str; 110 if (!create_params.GetString(webview::kStoragePartitionId, &partition_str)) { 111 return; 112 } 113 114 // Since the "persist:" prefix is in ASCII, StartsWith will work fine on 115 // UTF-8 encoded |partition_id|. If the prefix is a match, we can safely 116 // remove the prefix without splicing in the middle of a multi-byte codepoint. 117 // We can use the rest of the string as UTF-8 encoded one. 118 if (StartsWithASCII(partition_str, "persist:", true)) { 119 size_t index = partition_str.find(":"); 120 CHECK(index != std::string::npos); 121 // It is safe to do index + 1, since we tested for the full prefix above. 122 *storage_partition_id = partition_str.substr(index + 1); 123 124 if (storage_partition_id->empty()) { 125 // TODO(lazyboy): Better way to deal with this error. 126 return; 127 } 128 *persist_storage = true; 129 } else { 130 *storage_partition_id = partition_str; 131 *persist_storage = false; 132 } 133 } 134 135 } // namespace 136 137 // static 138 GuestViewBase* WebViewGuest::Create(content::BrowserContext* browser_context, 139 int guest_instance_id) { 140 return new WebViewGuest(browser_context, guest_instance_id); 141 } 142 143 // static 144 bool WebViewGuest::GetGuestPartitionConfigForSite( 145 const GURL& site, 146 std::string* partition_domain, 147 std::string* partition_name, 148 bool* in_memory) { 149 if (!site.SchemeIs(content::kGuestScheme)) 150 return false; 151 152 // Since guest URLs are only used for packaged apps, there must be an app 153 // id in the URL. 154 CHECK(site.has_host()); 155 *partition_domain = site.host(); 156 // Since persistence is optional, the path must either be empty or the 157 // literal string. 158 *in_memory = (site.path() != "/persist"); 159 // The partition name is user supplied value, which we have encoded when the 160 // URL was created, so it needs to be decoded. 161 *partition_name = 162 net::UnescapeURLComponent(site.query(), net::UnescapeRule::NORMAL); 163 return true; 164 } 165 166 // static 167 const char WebViewGuest::Type[] = "webview"; 168 169 // static 170 int WebViewGuest::GetViewInstanceId(WebContents* contents) { 171 WebViewGuest* guest = FromWebContents(contents); 172 if (!guest) 173 return guestview::kInstanceIDNone; 174 175 return guest->view_instance_id(); 176 } 177 178 const char* WebViewGuest::GetAPINamespace() const { 179 return webview::kAPINamespace; 180 } 181 182 int WebViewGuest::GetTaskPrefix() const { 183 return IDS_EXTENSION_TASK_MANAGER_WEBVIEW_TAG_PREFIX; 184 } 185 186 void WebViewGuest::CreateWebContents( 187 const std::string& embedder_extension_id, 188 int embedder_render_process_id, 189 const GURL& embedder_site_url, 190 const base::DictionaryValue& create_params, 191 const WebContentsCreatedCallback& callback) { 192 content::RenderProcessHost* embedder_render_process_host = 193 content::RenderProcessHost::FromID(embedder_render_process_id); 194 std::string storage_partition_id; 195 bool persist_storage = false; 196 std::string storage_partition_string; 197 ParsePartitionParam(create_params, &storage_partition_id, &persist_storage); 198 // Validate that the partition id coming from the renderer is valid UTF-8, 199 // since we depend on this in other parts of the code, such as FilePath 200 // creation. If the validation fails, treat it as a bad message and kill the 201 // renderer process. 202 if (!base::IsStringUTF8(storage_partition_id)) { 203 content::RecordAction( 204 base::UserMetricsAction("BadMessageTerminate_BPGM")); 205 base::KillProcess( 206 embedder_render_process_host->GetHandle(), 207 content::RESULT_CODE_KILLED_BAD_MESSAGE, false); 208 callback.Run(NULL); 209 return; 210 } 211 std::string url_encoded_partition = net::EscapeQueryParamValue( 212 storage_partition_id, false); 213 // The SiteInstance of a given webview tag is based on the fact that it's 214 // a guest process in addition to which platform application or which WebUI 215 // page the tag belongs to and what storage partition is in use, rather than 216 // the URL that the tag is being navigated to. 217 std::string partition_domain; 218 if (embedder_extension_id.empty()) { 219 DCHECK(content::ChildProcessSecurityPolicy::GetInstance()->HasWebUIBindings( 220 embedder_render_process_id)); 221 partition_domain = embedder_site_url.host(); 222 } else { 223 partition_domain = embedder_extension_id; 224 } 225 GURL guest_site(base::StringPrintf("%s://%s/%s?%s", 226 content::kGuestScheme, 227 partition_domain.c_str(), 228 persist_storage ? "persist" : "", 229 url_encoded_partition.c_str())); 230 231 // If we already have a webview tag in the same app using the same storage 232 // partition, we should use the same SiteInstance so the existing tag and 233 // the new tag can script each other. 234 GuestViewManager* guest_view_manager = 235 GuestViewManager::FromBrowserContext( 236 embedder_render_process_host->GetBrowserContext()); 237 content::SiteInstance* guest_site_instance = 238 guest_view_manager->GetGuestSiteInstance(guest_site); 239 if (!guest_site_instance) { 240 // Create the SiteInstance in a new BrowsingInstance, which will ensure 241 // that webview tags are also not allowed to send messages across 242 // different partitions. 243 guest_site_instance = content::SiteInstance::CreateForURL( 244 embedder_render_process_host->GetBrowserContext(), guest_site); 245 } 246 WebContents::CreateParams params( 247 embedder_render_process_host->GetBrowserContext(), 248 guest_site_instance); 249 params.guest_delegate = this; 250 callback.Run(WebContents::Create(params)); 251 } 252 253 void WebViewGuest::DidAttachToEmbedder() { 254 SetUpAutoSize(); 255 256 std::string name; 257 if (attach_params()->GetString(webview::kAttributeName, &name)) { 258 // If the guest window's name is empty, then the WebView tag's name is 259 // assigned. Otherwise, the guest window's name takes precedence over the 260 // WebView tag's name. 261 if (name_.empty()) 262 name_ = name; 263 } 264 ReportFrameNameChange(name_); 265 266 std::string user_agent_override; 267 if (attach_params()->GetString(webview::kParameterUserAgentOverride, 268 &user_agent_override)) { 269 SetUserAgentOverride(user_agent_override); 270 } else { 271 SetUserAgentOverride(""); 272 } 273 274 std::string src; 275 if (attach_params()->GetString(webview::kAttributeSrc, &src) && !src.empty()) 276 NavigateGuest(src); 277 278 if (GetOpener()) { 279 // We need to do a navigation here if the target URL has changed between 280 // the time the WebContents was created and the time it was attached. 281 // We also need to do an initial navigation if a RenderView was never 282 // created for the new window in cases where there is no referrer. 283 PendingWindowMap::iterator it = 284 GetOpener()->pending_new_windows_.find(this); 285 if (it != GetOpener()->pending_new_windows_.end()) { 286 const NewWindowInfo& new_window_info = it->second; 287 if (new_window_info.changed || !web_contents()->HasOpener()) 288 NavigateGuest(new_window_info.url.spec()); 289 } else { 290 NOTREACHED(); 291 } 292 293 // Once a new guest is attached to the DOM of the embedder page, then the 294 // lifetime of the new guest is no longer managed by the opener guest. 295 GetOpener()->pending_new_windows_.erase(this); 296 } 297 298 bool allow_transparency = false; 299 attach_params()->GetBoolean(webview::kAttributeAllowTransparency, 300 &allow_transparency); 301 // We need to set the background opaque flag after navigation to ensure that 302 // there is a RenderWidgetHostView available. 303 SetAllowTransparency(allow_transparency); 304 305 if (web_view_guest_delegate_) 306 web_view_guest_delegate_->OnDidAttachToEmbedder(); 307 } 308 309 void WebViewGuest::DidInitialize() { 310 script_executor_.reset( 311 new ScriptExecutor(web_contents(), &script_observers_)); 312 313 notification_registrar_.Add(this, 314 content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME, 315 content::Source<WebContents>(web_contents())); 316 317 notification_registrar_.Add(this, 318 content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT, 319 content::Source<WebContents>(web_contents())); 320 321 if (web_view_guest_delegate_) 322 web_view_guest_delegate_->OnDidInitialize(); 323 AttachWebViewHelpers(web_contents()); 324 } 325 326 void WebViewGuest::AttachWebViewHelpers(WebContents* contents) { 327 if (web_view_guest_delegate_) 328 web_view_guest_delegate_->OnAttachWebViewHelpers(contents); 329 web_view_permission_helper_.reset(new WebViewPermissionHelper(this)); 330 } 331 332 void WebViewGuest::DidStopLoading() { 333 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue()); 334 DispatchEventToEmbedder( 335 new GuestViewBase::Event(webview::kEventLoadStop, args.Pass())); 336 } 337 338 void WebViewGuest::EmbedderDestroyed() { 339 if (web_view_guest_delegate_) 340 web_view_guest_delegate_->OnEmbedderDestroyed(); 341 } 342 343 void WebViewGuest::GuestDestroyed() { 344 // Clean up custom context menu items for this guest. 345 if (web_view_guest_delegate_) 346 web_view_guest_delegate_->OnGuestDestroyed(); 347 RemoveWebViewStateFromIOThread(web_contents()); 348 } 349 350 void WebViewGuest::GuestReady() { 351 // The guest RenderView should always live in an isolated guest process. 352 CHECK(web_contents()->GetRenderProcessHost()->IsIsolatedGuest()); 353 Send(new ExtensionMsg_SetFrameName(web_contents()->GetRoutingID(), name_)); 354 355 // We don't want to accidentally set the opacity of an interstitial page. 356 // WebContents::GetRenderWidgetHostView will return the RWHV of an 357 // interstitial page if one is showing at this time. We only want opacity 358 // to apply to web pages. 359 web_contents()->GetRenderViewHost()->GetView()-> 360 SetBackgroundOpaque(guest_opaque_); 361 } 362 363 void WebViewGuest::GuestSizeChangedDueToAutoSize(const gfx::Size& old_size, 364 const gfx::Size& new_size) { 365 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue()); 366 args->SetInteger(webview::kOldHeight, old_size.height()); 367 args->SetInteger(webview::kOldWidth, old_size.width()); 368 args->SetInteger(webview::kNewHeight, new_size.height()); 369 args->SetInteger(webview::kNewWidth, new_size.width()); 370 DispatchEventToEmbedder( 371 new GuestViewBase::Event(webview::kEventSizeChanged, args.Pass())); 372 } 373 374 bool WebViewGuest::IsAutoSizeSupported() const { 375 return true; 376 } 377 378 bool WebViewGuest::IsDragAndDropEnabled() const { 379 return true; 380 } 381 382 void WebViewGuest::WillDestroy() { 383 if (!attached() && GetOpener()) 384 GetOpener()->pending_new_windows_.erase(this); 385 DestroyUnattachedWindows(); 386 387 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue()); 388 DispatchEventToEmbedder( 389 new GuestViewBase::Event(webview::kEventPluginDestroyed, args.Pass())); 390 } 391 392 bool WebViewGuest::AddMessageToConsole(WebContents* source, 393 int32 level, 394 const base::string16& message, 395 int32 line_no, 396 const base::string16& source_id) { 397 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue()); 398 // Log levels are from base/logging.h: LogSeverity. 399 args->SetInteger(webview::kLevel, level); 400 args->SetString(webview::kMessage, message); 401 args->SetInteger(webview::kLine, line_no); 402 args->SetString(webview::kSourceId, source_id); 403 DispatchEventToEmbedder( 404 new GuestViewBase::Event(webview::kEventConsoleMessage, args.Pass())); 405 return true; 406 } 407 408 void WebViewGuest::CloseContents(WebContents* source) { 409 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue()); 410 DispatchEventToEmbedder( 411 new GuestViewBase::Event(webview::kEventClose, args.Pass())); 412 } 413 414 void WebViewGuest::FindReply(WebContents* source, 415 int request_id, 416 int number_of_matches, 417 const gfx::Rect& selection_rect, 418 int active_match_ordinal, 419 bool final_update) { 420 find_helper_.FindReply(request_id, 421 number_of_matches, 422 selection_rect, 423 active_match_ordinal, 424 final_update); 425 } 426 427 bool WebViewGuest::HandleContextMenu( 428 const content::ContextMenuParams& params) { 429 if (!web_view_guest_delegate_) 430 return false; 431 return web_view_guest_delegate_->HandleContextMenu(params); 432 } 433 434 void WebViewGuest::HandleKeyboardEvent( 435 WebContents* source, 436 const content::NativeWebKeyboardEvent& event) { 437 if (!attached()) 438 return; 439 440 if (HandleKeyboardShortcuts(event)) 441 return; 442 443 // Send the unhandled keyboard events back to the embedder to reprocess them. 444 // TODO(fsamuel): This introduces the possibility of out-of-order keyboard 445 // events because the guest may be arbitrarily delayed when responding to 446 // keyboard events. In that time, the embedder may have received and processed 447 // additional key events. This needs to be fixed as soon as possible. 448 // See http://crbug.com/229882. 449 embedder_web_contents()->GetDelegate()->HandleKeyboardEvent( 450 web_contents(), event); 451 } 452 453 void WebViewGuest::LoadProgressChanged(content::WebContents* source, 454 double progress) { 455 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue()); 456 args->SetString(guestview::kUrl, web_contents()->GetURL().spec()); 457 args->SetDouble(webview::kProgress, progress); 458 DispatchEventToEmbedder( 459 new GuestViewBase::Event(webview::kEventLoadProgress, args.Pass())); 460 } 461 462 void WebViewGuest::LoadAbort(bool is_top_level, 463 const GURL& url, 464 const std::string& error_type) { 465 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue()); 466 args->SetBoolean(guestview::kIsTopLevel, is_top_level); 467 args->SetString(guestview::kUrl, url.possibly_invalid_spec()); 468 args->SetString(guestview::kReason, error_type); 469 DispatchEventToEmbedder( 470 new GuestViewBase::Event(webview::kEventLoadAbort, args.Pass())); 471 } 472 473 void WebViewGuest::OnFrameNameChanged(bool is_top_level, 474 const std::string& name) { 475 if (!is_top_level) 476 return; 477 478 if (name_ == name) 479 return; 480 481 ReportFrameNameChange(name); 482 } 483 484 void WebViewGuest::CreateNewGuestWebViewWindow( 485 const content::OpenURLParams& params) { 486 GuestViewManager* guest_manager = 487 GuestViewManager::FromBrowserContext(browser_context()); 488 // Set the attach params to use the same partition as the opener. 489 // We pull the partition information from the site's URL, which is of the 490 // form guest://site/{persist}?{partition_name}. 491 const GURL& site_url = web_contents()->GetSiteInstance()->GetSiteURL(); 492 const std::string storage_partition_id = 493 GetStoragePartitionIdFromSiteURL(site_url); 494 base::DictionaryValue create_params; 495 create_params.SetString(webview::kStoragePartitionId, storage_partition_id); 496 497 guest_manager->CreateGuest(WebViewGuest::Type, 498 embedder_extension_id(), 499 embedder_web_contents(), 500 create_params, 501 base::Bind(&WebViewGuest::NewGuestWebViewCallback, 502 base::Unretained(this), 503 params)); 504 } 505 506 void WebViewGuest::NewGuestWebViewCallback( 507 const content::OpenURLParams& params, 508 content::WebContents* guest_web_contents) { 509 WebViewGuest* new_guest = WebViewGuest::FromWebContents(guest_web_contents); 510 new_guest->SetOpener(this); 511 512 // Take ownership of |new_guest|. 513 pending_new_windows_.insert( 514 std::make_pair(new_guest, NewWindowInfo(params.url, std::string()))); 515 516 // Request permission to show the new window. 517 RequestNewWindowPermission(params.disposition, 518 gfx::Rect(), 519 params.user_gesture, 520 new_guest->web_contents()); 521 } 522 523 // TODO(fsamuel): Find a reliable way to test the 'responsive' and 524 // 'unresponsive' events. 525 void WebViewGuest::RendererResponsive(content::WebContents* source) { 526 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue()); 527 args->SetInteger(webview::kProcessId, 528 web_contents()->GetRenderProcessHost()->GetID()); 529 DispatchEventToEmbedder( 530 new GuestViewBase::Event(webview::kEventResponsive, args.Pass())); 531 } 532 533 void WebViewGuest::RendererUnresponsive(content::WebContents* source) { 534 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue()); 535 args->SetInteger(webview::kProcessId, 536 web_contents()->GetRenderProcessHost()->GetID()); 537 DispatchEventToEmbedder( 538 new GuestViewBase::Event(webview::kEventUnresponsive, args.Pass())); 539 } 540 541 void WebViewGuest::Observe(int type, 542 const content::NotificationSource& source, 543 const content::NotificationDetails& details) { 544 switch (type) { 545 case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME: { 546 DCHECK_EQ(content::Source<WebContents>(source).ptr(), web_contents()); 547 if (content::Source<WebContents>(source).ptr() == web_contents()) 548 LoadHandlerCalled(); 549 break; 550 } 551 case content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT: { 552 DCHECK_EQ(content::Source<WebContents>(source).ptr(), web_contents()); 553 content::ResourceRedirectDetails* resource_redirect_details = 554 content::Details<content::ResourceRedirectDetails>(details).ptr(); 555 bool is_top_level = resource_redirect_details->resource_type == 556 content::RESOURCE_TYPE_MAIN_FRAME; 557 LoadRedirect(resource_redirect_details->url, 558 resource_redirect_details->new_url, 559 is_top_level); 560 break; 561 } 562 default: 563 NOTREACHED() << "Unexpected notification sent."; 564 break; 565 } 566 } 567 568 double WebViewGuest::GetZoom() { 569 if (!web_view_guest_delegate_) 570 return 1.0; 571 return web_view_guest_delegate_->GetZoom(); 572 } 573 574 void WebViewGuest::Find( 575 const base::string16& search_text, 576 const blink::WebFindOptions& options, 577 scoped_refptr<WebViewInternalFindFunction> find_function) { 578 find_helper_.Find(web_contents(), search_text, options, find_function); 579 } 580 581 void WebViewGuest::StopFinding(content::StopFindAction action) { 582 find_helper_.CancelAllFindSessions(); 583 web_contents()->StopFinding(action); 584 } 585 586 void WebViewGuest::Go(int relative_index) { 587 web_contents()->GetController().GoToOffset(relative_index); 588 } 589 590 void WebViewGuest::Reload() { 591 // TODO(fsamuel): Don't check for repost because we don't want to show 592 // Chromium's repost warning. We might want to implement a separate API 593 // for registering a callback if a repost is about to happen. 594 web_contents()->GetController().Reload(false); 595 } 596 597 void WebViewGuest::SetUserAgentOverride( 598 const std::string& user_agent_override) { 599 if (!attached()) 600 return; 601 is_overriding_user_agent_ = !user_agent_override.empty(); 602 if (is_overriding_user_agent_) { 603 content::RecordAction(UserMetricsAction("WebView.Guest.OverrideUA")); 604 } 605 web_contents()->SetUserAgentOverride(user_agent_override); 606 } 607 608 void WebViewGuest::Stop() { 609 web_contents()->Stop(); 610 } 611 612 void WebViewGuest::Terminate() { 613 content::RecordAction(UserMetricsAction("WebView.Guest.Terminate")); 614 base::ProcessHandle process_handle = 615 web_contents()->GetRenderProcessHost()->GetHandle(); 616 if (process_handle) 617 base::KillProcess(process_handle, content::RESULT_CODE_KILLED, false); 618 } 619 620 bool WebViewGuest::ClearData(const base::Time remove_since, 621 uint32 removal_mask, 622 const base::Closure& callback) { 623 content::RecordAction(UserMetricsAction("WebView.Guest.ClearData")); 624 content::StoragePartition* partition = 625 content::BrowserContext::GetStoragePartition( 626 web_contents()->GetBrowserContext(), 627 web_contents()->GetSiteInstance()); 628 629 if (!partition) 630 return false; 631 632 partition->ClearData( 633 removal_mask, 634 content::StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL, 635 GURL(), 636 content::StoragePartition::OriginMatcherFunction(), 637 remove_since, 638 base::Time::Now(), 639 callback); 640 return true; 641 } 642 643 WebViewGuest::WebViewGuest(content::BrowserContext* browser_context, 644 int guest_instance_id) 645 : GuestView<WebViewGuest>(browser_context, guest_instance_id), 646 find_helper_(this), 647 is_overriding_user_agent_(false), 648 guest_opaque_(true), 649 javascript_dialog_helper_(this) { 650 web_view_guest_delegate_.reset( 651 ExtensionsAPIClient::Get()->CreateWebViewGuestDelegate(this)); 652 } 653 654 WebViewGuest::~WebViewGuest() { 655 } 656 657 void WebViewGuest::DidCommitProvisionalLoadForFrame( 658 content::RenderFrameHost* render_frame_host, 659 const GURL& url, 660 ui::PageTransition transition_type) { 661 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue()); 662 args->SetString(guestview::kUrl, url.spec()); 663 args->SetBoolean(guestview::kIsTopLevel, !render_frame_host->GetParent()); 664 args->SetString(webview::kInternalBaseURLForDataURL, 665 web_contents() 666 ->GetController() 667 .GetLastCommittedEntry() 668 ->GetBaseURLForDataURL() 669 .spec()); 670 args->SetInteger(webview::kInternalCurrentEntryIndex, 671 web_contents()->GetController().GetCurrentEntryIndex()); 672 args->SetInteger(webview::kInternalEntryCount, 673 web_contents()->GetController().GetEntryCount()); 674 args->SetInteger(webview::kInternalProcessId, 675 web_contents()->GetRenderProcessHost()->GetID()); 676 DispatchEventToEmbedder( 677 new GuestViewBase::Event(webview::kEventLoadCommit, args.Pass())); 678 679 find_helper_.CancelAllFindSessions(); 680 if (web_view_guest_delegate_) { 681 web_view_guest_delegate_->OnDidCommitProvisionalLoadForFrame( 682 !render_frame_host->GetParent()); 683 } 684 } 685 686 void WebViewGuest::DidFailProvisionalLoad( 687 content::RenderFrameHost* render_frame_host, 688 const GURL& validated_url, 689 int error_code, 690 const base::string16& error_description) { 691 LoadAbort(!render_frame_host->GetParent(), validated_url, 692 net::ErrorToShortString(error_code)); 693 } 694 695 void WebViewGuest::DidStartProvisionalLoadForFrame( 696 content::RenderFrameHost* render_frame_host, 697 const GURL& validated_url, 698 bool is_error_page, 699 bool is_iframe_srcdoc) { 700 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue()); 701 args->SetString(guestview::kUrl, validated_url.spec()); 702 args->SetBoolean(guestview::kIsTopLevel, !render_frame_host->GetParent()); 703 DispatchEventToEmbedder( 704 new GuestViewBase::Event(webview::kEventLoadStart, args.Pass())); 705 } 706 707 void WebViewGuest::DocumentLoadedInFrame( 708 content::RenderFrameHost* render_frame_host) { 709 if (web_view_guest_delegate_) 710 web_view_guest_delegate_->OnDocumentLoadedInFrame(render_frame_host); 711 } 712 713 bool WebViewGuest::OnMessageReceived(const IPC::Message& message, 714 RenderFrameHost* render_frame_host) { 715 bool handled = true; 716 IPC_BEGIN_MESSAGE_MAP(WebViewGuest, message) 717 IPC_MESSAGE_HANDLER(ExtensionHostMsg_FrameNameChanged, OnFrameNameChanged) 718 IPC_MESSAGE_UNHANDLED(handled = false) 719 IPC_END_MESSAGE_MAP() 720 return handled; 721 } 722 723 void WebViewGuest::RenderProcessGone(base::TerminationStatus status) { 724 // Cancel all find sessions in progress. 725 find_helper_.CancelAllFindSessions(); 726 727 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue()); 728 args->SetInteger(webview::kProcessId, 729 web_contents()->GetRenderProcessHost()->GetID()); 730 args->SetString(webview::kReason, TerminationStatusToString(status)); 731 DispatchEventToEmbedder( 732 new GuestViewBase::Event(webview::kEventExit, args.Pass())); 733 } 734 735 void WebViewGuest::UserAgentOverrideSet(const std::string& user_agent) { 736 if (!attached()) 737 return; 738 content::NavigationController& controller = web_contents()->GetController(); 739 content::NavigationEntry* entry = controller.GetVisibleEntry(); 740 if (!entry) 741 return; 742 entry->SetIsOverridingUserAgent(!user_agent.empty()); 743 web_contents()->GetController().Reload(false); 744 } 745 746 void WebViewGuest::ReportFrameNameChange(const std::string& name) { 747 name_ = name; 748 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue()); 749 args->SetString(webview::kName, name); 750 DispatchEventToEmbedder( 751 new GuestViewBase::Event(webview::kEventFrameNameChanged, args.Pass())); 752 } 753 754 void WebViewGuest::LoadHandlerCalled() { 755 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue()); 756 DispatchEventToEmbedder( 757 new GuestViewBase::Event(webview::kEventContentLoad, args.Pass())); 758 } 759 760 void WebViewGuest::LoadRedirect(const GURL& old_url, 761 const GURL& new_url, 762 bool is_top_level) { 763 scoped_ptr<base::DictionaryValue> args(new base::DictionaryValue()); 764 args->SetBoolean(guestview::kIsTopLevel, is_top_level); 765 args->SetString(webview::kNewURL, new_url.spec()); 766 args->SetString(webview::kOldURL, old_url.spec()); 767 DispatchEventToEmbedder( 768 new GuestViewBase::Event(webview::kEventLoadRedirect, args.Pass())); 769 } 770 771 void WebViewGuest::PushWebViewStateToIOThread() { 772 const GURL& site_url = web_contents()->GetSiteInstance()->GetSiteURL(); 773 std::string partition_domain; 774 std::string partition_id; 775 bool in_memory; 776 if (!GetGuestPartitionConfigForSite( 777 site_url, &partition_domain, &partition_id, &in_memory)) { 778 NOTREACHED(); 779 return; 780 } 781 782 WebViewRendererState::WebViewInfo web_view_info; 783 web_view_info.embedder_process_id = embedder_render_process_id(); 784 web_view_info.instance_id = view_instance_id(); 785 web_view_info.partition_id = partition_id; 786 web_view_info.embedder_extension_id = embedder_extension_id(); 787 788 content::BrowserThread::PostTask( 789 content::BrowserThread::IO, 790 FROM_HERE, 791 base::Bind(&WebViewRendererState::AddGuest, 792 base::Unretained(WebViewRendererState::GetInstance()), 793 web_contents()->GetRenderProcessHost()->GetID(), 794 web_contents()->GetRoutingID(), 795 web_view_info)); 796 } 797 798 // static 799 void WebViewGuest::RemoveWebViewStateFromIOThread( 800 WebContents* web_contents) { 801 content::BrowserThread::PostTask( 802 content::BrowserThread::IO, FROM_HERE, 803 base::Bind( 804 &WebViewRendererState::RemoveGuest, 805 base::Unretained(WebViewRendererState::GetInstance()), 806 web_contents->GetRenderProcessHost()->GetID(), 807 web_contents->GetRoutingID())); 808 } 809 810 content::WebContents* WebViewGuest::CreateNewGuestWindow( 811 const content::WebContents::CreateParams& create_params) { 812 GuestViewManager* guest_manager = 813 GuestViewManager::FromBrowserContext(browser_context()); 814 return guest_manager->CreateGuestWithWebContentsParams( 815 WebViewGuest::Type, 816 embedder_extension_id(), 817 embedder_web_contents()->GetRenderProcessHost()->GetID(), 818 create_params); 819 } 820 821 void WebViewGuest::RequestMediaAccessPermission( 822 content::WebContents* source, 823 const content::MediaStreamRequest& request, 824 const content::MediaResponseCallback& callback) { 825 web_view_permission_helper_->RequestMediaAccessPermission(source, 826 request, 827 callback); 828 } 829 830 bool WebViewGuest::CheckMediaAccessPermission(content::WebContents* source, 831 const GURL& security_origin, 832 content::MediaStreamType type) { 833 return web_view_permission_helper_->CheckMediaAccessPermission( 834 source, security_origin, type); 835 } 836 837 void WebViewGuest::CanDownload( 838 content::RenderViewHost* render_view_host, 839 const GURL& url, 840 const std::string& request_method, 841 const base::Callback<void(bool)>& callback) { 842 web_view_permission_helper_->CanDownload(render_view_host, 843 url, 844 request_method, 845 callback); 846 } 847 848 void WebViewGuest::RequestPointerLockPermission( 849 bool user_gesture, 850 bool last_unlocked_by_target, 851 const base::Callback<void(bool)>& callback) { 852 web_view_permission_helper_->RequestPointerLockPermission( 853 user_gesture, 854 last_unlocked_by_target, 855 callback); 856 } 857 858 void WebViewGuest::WillAttachToEmbedder() { 859 // We must install the mapping from guests to WebViews prior to resuming 860 // suspended resource loads so that the WebRequest API will catch resource 861 // requests. 862 PushWebViewStateToIOThread(); 863 } 864 865 content::JavaScriptDialogManager* 866 WebViewGuest::GetJavaScriptDialogManager() { 867 return &javascript_dialog_helper_; 868 } 869 870 content::ColorChooser* WebViewGuest::OpenColorChooser( 871 WebContents* web_contents, 872 SkColor color, 873 const std::vector<content::ColorSuggestion>& suggestions) { 874 if (!attached() || !embedder_web_contents()->GetDelegate()) 875 return NULL; 876 return embedder_web_contents()->GetDelegate()->OpenColorChooser( 877 web_contents, color, suggestions); 878 } 879 880 void WebViewGuest::NavigateGuest(const std::string& src) { 881 if (!attached()) 882 return; 883 884 GURL url = ResolveURL(src); 885 886 // Do not allow navigating a guest to schemes other than known safe schemes. 887 // This will block the embedder trying to load unwanted schemes, e.g. 888 // chrome://settings. 889 bool scheme_is_blocked = 890 (!content::ChildProcessSecurityPolicy::GetInstance()->IsWebSafeScheme( 891 url.scheme()) && 892 !url.SchemeIs(url::kAboutScheme)) || 893 url.SchemeIs(url::kJavaScriptScheme); 894 if (scheme_is_blocked || !url.is_valid()) { 895 LoadAbort(true /* is_top_level */, url, 896 net::ErrorToShortString(net::ERR_ABORTED)); 897 return; 898 } 899 900 GURL validated_url(url); 901 web_contents()->GetRenderProcessHost()->FilterURL(false, &validated_url); 902 // As guests do not swap processes on navigation, only navigations to 903 // normal web URLs are supported. No protocol handlers are installed for 904 // other schemes (e.g., WebUI or extensions), and no permissions or bindings 905 // can be granted to the guest process. 906 LoadURLWithParams(validated_url, 907 content::Referrer(), 908 ui::PAGE_TRANSITION_AUTO_TOPLEVEL, 909 web_contents()); 910 } 911 912 bool WebViewGuest::HandleKeyboardShortcuts( 913 const content::NativeWebKeyboardEvent& event) { 914 if (event.type != blink::WebInputEvent::RawKeyDown) 915 return false; 916 917 // If the user hits the escape key without any modifiers then unlock the 918 // mouse if necessary. 919 if ((event.windowsKeyCode == ui::VKEY_ESCAPE) && 920 !(event.modifiers & blink::WebInputEvent::InputModifiers)) { 921 return web_contents()->GotResponseToLockMouseRequest(false); 922 } 923 924 #if defined(OS_MACOSX) 925 if (event.modifiers != blink::WebInputEvent::MetaKey) 926 return false; 927 928 if (event.windowsKeyCode == ui::VKEY_OEM_4) { 929 Go(-1); 930 return true; 931 } 932 933 if (event.windowsKeyCode == ui::VKEY_OEM_6) { 934 Go(1); 935 return true; 936 } 937 #else 938 if (event.windowsKeyCode == ui::VKEY_BROWSER_BACK) { 939 Go(-1); 940 return true; 941 } 942 943 if (event.windowsKeyCode == ui::VKEY_BROWSER_FORWARD) { 944 Go(1); 945 return true; 946 } 947 #endif 948 949 return false; 950 } 951 952 void WebViewGuest::SetUpAutoSize() { 953 // Read the autosize parameters passed in from the embedder. 954 bool auto_size_enabled = false; 955 attach_params()->GetBoolean(webview::kAttributeAutoSize, &auto_size_enabled); 956 957 int max_height = 0; 958 int max_width = 0; 959 attach_params()->GetInteger(webview::kAttributeMaxHeight, &max_height); 960 attach_params()->GetInteger(webview::kAttributeMaxWidth, &max_width); 961 962 int min_height = 0; 963 int min_width = 0; 964 attach_params()->GetInteger(webview::kAttributeMinHeight, &min_height); 965 attach_params()->GetInteger(webview::kAttributeMinWidth, &min_width); 966 967 // Call SetAutoSize to apply all the appropriate validation and clipping of 968 // values. 969 SetAutoSize(auto_size_enabled, 970 gfx::Size(min_width, min_height), 971 gfx::Size(max_width, max_height)); 972 } 973 974 void WebViewGuest::ShowContextMenu( 975 int request_id, 976 const WebViewGuestDelegate::MenuItemVector* items) { 977 if (web_view_guest_delegate_) 978 web_view_guest_delegate_->OnShowContextMenu(request_id, items); 979 } 980 981 void WebViewGuest::SetName(const std::string& name) { 982 if (name_ == name) 983 return; 984 name_ = name; 985 986 Send(new ExtensionMsg_SetFrameName(routing_id(), name_)); 987 } 988 989 void WebViewGuest::SetZoom(double zoom_factor) { 990 if (web_view_guest_delegate_) 991 web_view_guest_delegate_->OnSetZoom(zoom_factor); 992 } 993 994 void WebViewGuest::SetAllowTransparency(bool allow) { 995 if (guest_opaque_ != allow) 996 return; 997 998 guest_opaque_ = !allow; 999 if (!web_contents()->GetRenderViewHost()->GetView()) 1000 return; 1001 1002 web_contents()->GetRenderViewHost()->GetView()->SetBackgroundOpaque(!allow); 1003 } 1004 1005 void WebViewGuest::AddNewContents(content::WebContents* source, 1006 content::WebContents* new_contents, 1007 WindowOpenDisposition disposition, 1008 const gfx::Rect& initial_pos, 1009 bool user_gesture, 1010 bool* was_blocked) { 1011 if (was_blocked) 1012 *was_blocked = false; 1013 RequestNewWindowPermission(disposition, 1014 initial_pos, 1015 user_gesture, 1016 new_contents); 1017 } 1018 1019 content::WebContents* WebViewGuest::OpenURLFromTab( 1020 content::WebContents* source, 1021 const content::OpenURLParams& params) { 1022 // If the guest wishes to navigate away prior to attachment then we save the 1023 // navigation to perform upon attachment. Navigation initializes a lot of 1024 // state that assumes an embedder exists, such as RenderWidgetHostViewGuest. 1025 // Navigation also resumes resource loading which we don't want to allow 1026 // until attachment. 1027 if (!attached()) { 1028 WebViewGuest* opener = GetOpener(); 1029 PendingWindowMap::iterator it = 1030 opener->pending_new_windows_.find(this); 1031 if (it == opener->pending_new_windows_.end()) 1032 return NULL; 1033 const NewWindowInfo& info = it->second; 1034 NewWindowInfo new_window_info(params.url, info.name); 1035 new_window_info.changed = new_window_info.url != info.url; 1036 it->second = new_window_info; 1037 return NULL; 1038 } 1039 if (params.disposition == CURRENT_TAB) { 1040 // This can happen for cross-site redirects. 1041 LoadURLWithParams(params.url, params.referrer, params.transition, source); 1042 return source; 1043 } 1044 1045 CreateNewGuestWebViewWindow(params); 1046 return NULL; 1047 } 1048 1049 void WebViewGuest::WebContentsCreated(WebContents* source_contents, 1050 int opener_render_frame_id, 1051 const base::string16& frame_name, 1052 const GURL& target_url, 1053 content::WebContents* new_contents) { 1054 WebViewGuest* guest = WebViewGuest::FromWebContents(new_contents); 1055 CHECK(guest); 1056 guest->SetOpener(this); 1057 std::string guest_name = base::UTF16ToUTF8(frame_name); 1058 guest->name_ = guest_name; 1059 pending_new_windows_.insert( 1060 std::make_pair(guest, NewWindowInfo(target_url, guest_name))); 1061 } 1062 1063 void WebViewGuest::LoadURLWithParams(const GURL& url, 1064 const content::Referrer& referrer, 1065 ui::PageTransition transition_type, 1066 content::WebContents* web_contents) { 1067 content::NavigationController::LoadURLParams load_url_params(url); 1068 load_url_params.referrer = referrer; 1069 load_url_params.transition_type = transition_type; 1070 load_url_params.extra_headers = std::string(); 1071 if (is_overriding_user_agent_) { 1072 load_url_params.override_user_agent = 1073 content::NavigationController::UA_OVERRIDE_TRUE; 1074 } 1075 web_contents->GetController().LoadURLWithParams(load_url_params); 1076 } 1077 1078 void WebViewGuest::RequestNewWindowPermission( 1079 WindowOpenDisposition disposition, 1080 const gfx::Rect& initial_bounds, 1081 bool user_gesture, 1082 content::WebContents* new_contents) { 1083 WebViewGuest* guest = WebViewGuest::FromWebContents(new_contents); 1084 if (!guest) 1085 return; 1086 PendingWindowMap::iterator it = pending_new_windows_.find(guest); 1087 if (it == pending_new_windows_.end()) 1088 return; 1089 const NewWindowInfo& new_window_info = it->second; 1090 1091 // Retrieve the opener partition info if we have it. 1092 const GURL& site_url = new_contents->GetSiteInstance()->GetSiteURL(); 1093 std::string storage_partition_id = GetStoragePartitionIdFromSiteURL(site_url); 1094 1095 base::DictionaryValue request_info; 1096 request_info.SetInteger(webview::kInitialHeight, initial_bounds.height()); 1097 request_info.SetInteger(webview::kInitialWidth, initial_bounds.width()); 1098 request_info.Set(webview::kTargetURL, 1099 new base::StringValue(new_window_info.url.spec())); 1100 request_info.Set(webview::kName, new base::StringValue(new_window_info.name)); 1101 request_info.SetInteger(webview::kWindowID, guest->guest_instance_id()); 1102 // We pass in partition info so that window-s created through newwindow 1103 // API can use it to set their partition attribute. 1104 request_info.Set(webview::kStoragePartitionId, 1105 new base::StringValue(storage_partition_id)); 1106 request_info.Set( 1107 webview::kWindowOpenDisposition, 1108 new base::StringValue(WindowOpenDispositionToString(disposition))); 1109 1110 web_view_permission_helper_-> 1111 RequestPermission(WEB_VIEW_PERMISSION_TYPE_NEW_WINDOW, 1112 request_info, 1113 base::Bind(&WebViewGuest::OnWebViewNewWindowResponse, 1114 base::Unretained(this), 1115 guest->guest_instance_id()), 1116 false /* allowed_by_default */); 1117 } 1118 1119 void WebViewGuest::DestroyUnattachedWindows() { 1120 // Destroy() reaches in and removes the WebViewGuest from its opener's 1121 // pending_new_windows_ set. To avoid mutating the set while iterating, we 1122 // create a copy of the pending new windows set and iterate over the copy. 1123 PendingWindowMap pending_new_windows(pending_new_windows_); 1124 // Clean up unattached new windows opened by this guest. 1125 for (PendingWindowMap::const_iterator it = pending_new_windows.begin(); 1126 it != pending_new_windows.end(); ++it) { 1127 it->first->Destroy(); 1128 } 1129 // All pending windows should be removed from the set after Destroy() is 1130 // called on all of them. 1131 DCHECK(pending_new_windows_.empty()); 1132 } 1133 1134 GURL WebViewGuest::ResolveURL(const std::string& src) { 1135 if (!in_extension()) { 1136 return GURL(src); 1137 } 1138 1139 GURL default_url(base::StringPrintf("%s://%s/", 1140 kExtensionScheme, 1141 embedder_extension_id().c_str())); 1142 return default_url.Resolve(src); 1143 } 1144 1145 void WebViewGuest::OnWebViewNewWindowResponse( 1146 int new_window_instance_id, 1147 bool allow, 1148 const std::string& user_input) { 1149 WebViewGuest* guest = 1150 WebViewGuest::From(embedder_render_process_id(), new_window_instance_id); 1151 if (!guest) 1152 return; 1153 1154 if (!allow) 1155 guest->Destroy(); 1156 } 1157 1158 } // namespace extensions 1159