|
7 | 7 | #include <memory> |
8 | 8 | #include <utility> |
9 | 9 |
|
| 10 | +#include "chrome/common/url_constants.h" |
| 11 | +#include "components/url_formatter/url_fixer.h" |
| 12 | +#include "content/public/browser/navigation_entry.h" |
10 | 13 | #include "extensions/browser/extension_api_frame_id_map.h" |
11 | 14 | #include "extensions/common/error_utils.h" |
12 | 15 | #include "extensions/common/manifest_constants.h" |
@@ -319,4 +322,182 @@ ExtensionFunction::ResponseAction TabsSetZoomSettingsFunction::Run() { |
319 | 322 | return RespondNow(NoArguments()); |
320 | 323 | } |
321 | 324 |
|
| 325 | +bool IsKillURL(const GURL& url) { |
| 326 | +#if DCHECK_IS_ON() |
| 327 | + // Caller should ensure that |url| is already "fixed up" by |
| 328 | + // url_formatter::FixupURL, which (among many other things) takes care |
| 329 | + // of rewriting about:kill into chrome://kill/. |
| 330 | + if (url.SchemeIs(url::kAboutScheme)) |
| 331 | + DCHECK(url.IsAboutBlank() || url.IsAboutSrcdoc()); |
| 332 | +#endif |
| 333 | + |
| 334 | + static const char* const kill_hosts[] = { |
| 335 | + chrome::kChromeUICrashHost, chrome::kChromeUIDelayedHangUIHost, |
| 336 | + chrome::kChromeUIHangUIHost, chrome::kChromeUIKillHost, |
| 337 | + chrome::kChromeUIQuitHost, chrome::kChromeUIRestartHost, |
| 338 | + content::kChromeUIBrowserCrashHost, content::kChromeUIMemoryExhaustHost, |
| 339 | + }; |
| 340 | + |
| 341 | + if (!url.SchemeIs(content::kChromeUIScheme)) |
| 342 | + return false; |
| 343 | + |
| 344 | + return base::Contains(kill_hosts, url.host_piece()); |
| 345 | +} |
| 346 | + |
| 347 | +GURL ResolvePossiblyRelativeURL(const std::string& url_string, |
| 348 | + const Extension* extension) { |
| 349 | + GURL url = GURL(url_string); |
| 350 | + if (!url.is_valid() && extension) |
| 351 | + url = extension->GetResourceURL(url_string); |
| 352 | + |
| 353 | + return url; |
| 354 | +} |
| 355 | +bool PrepareURLForNavigation(const std::string& url_string, |
| 356 | + const Extension* extension, |
| 357 | + GURL* return_url, |
| 358 | + std::string* error) { |
| 359 | + GURL url = ResolvePossiblyRelativeURL(url_string, extension); |
| 360 | + |
| 361 | + // Ideally, the URL would only be "fixed" for user input (e.g. for URLs |
| 362 | + // entered into the Omnibox), but some extensions rely on the legacy behavior |
| 363 | + // where all navigations were subject to the "fixing". See also |
| 364 | + // https://crbug.com/1145381. |
| 365 | + url = url_formatter::FixupURL(url.spec(), "" /* = desired_tld */); |
| 366 | + |
| 367 | + // Reject invalid URLs. |
| 368 | + if (!url.is_valid()) { |
| 369 | + const char kInvalidUrlError[] = "Invalid url: \"*\"."; |
| 370 | + *error = ErrorUtils::FormatErrorMessage(kInvalidUrlError, url_string); |
| 371 | + return false; |
| 372 | + } |
| 373 | + |
| 374 | + // Don't let the extension crash the browser or renderers. |
| 375 | + if (IsKillURL(url)) { |
| 376 | + const char kNoCrashBrowserError[] = |
| 377 | + "I'm sorry. I'm afraid I can't do that."; |
| 378 | + *error = kNoCrashBrowserError; |
| 379 | + return false; |
| 380 | + } |
| 381 | + |
| 382 | + // Don't let the extension navigate directly to devtools scheme pages, unless |
| 383 | + // they have applicable permissions. |
| 384 | + if (url.SchemeIs(content::kChromeDevToolsScheme) && |
| 385 | + !(extension->permissions_data()->HasAPIPermission( |
| 386 | + extensions::mojom::APIPermissionID::kDevtools) || |
| 387 | + extension->permissions_data()->HasAPIPermission( |
| 388 | + extensions::mojom::APIPermissionID::kDebugger))) { |
| 389 | + const char kCannotNavigateToDevtools[] = |
| 390 | + "Cannot navigate to a devtools:// page without either the devtools or " |
| 391 | + "debugger permission."; |
| 392 | + *error = kCannotNavigateToDevtools; |
| 393 | + return false; |
| 394 | + } |
| 395 | + |
| 396 | + return_url->Swap(&url); |
| 397 | + return true; |
| 398 | +} |
| 399 | + |
| 400 | +TabsUpdateFunction::TabsUpdateFunction() : web_contents_(nullptr) {} |
| 401 | + |
| 402 | +ExtensionFunction::ResponseAction TabsUpdateFunction::Run() { |
| 403 | + std::unique_ptr<tabs::Update::Params> params( |
| 404 | + tabs::Update::Params::Create(*args_)); |
| 405 | + EXTENSION_FUNCTION_VALIDATE(params.get()); |
| 406 | + |
| 407 | + int tab_id = params->tab_id ? *params->tab_id : -1; |
| 408 | + auto* contents = electron::api::WebContents::FromID(tab_id); |
| 409 | + if (!contents) |
| 410 | + return RespondNow(Error("No such tab")); |
| 411 | + |
| 412 | + web_contents_ = contents->web_contents(); |
| 413 | + |
| 414 | + // Navigate the tab to a new location if the url is different. |
| 415 | + std::string error; |
| 416 | + if (params->update_properties.url.get()) { |
| 417 | + std::string updated_url = *params->update_properties.url; |
| 418 | + if (!UpdateURL(updated_url, tab_id, &error)) |
| 419 | + return RespondNow(Error(std::move(error))); |
| 420 | + } |
| 421 | + |
| 422 | + if (params->update_properties.muted.get()) { |
| 423 | + contents->SetAudioMuted(*params->update_properties.muted); |
| 424 | + } |
| 425 | + |
| 426 | + return RespondNow(GetResult()); |
| 427 | +} |
| 428 | + |
| 429 | +bool TabsUpdateFunction::UpdateURL(const std::string& url_string, |
| 430 | + int tab_id, |
| 431 | + std::string* error) { |
| 432 | + GURL url; |
| 433 | + if (!PrepareURLForNavigation(url_string, extension(), &url, error)) { |
| 434 | + return false; |
| 435 | + } |
| 436 | + |
| 437 | + const bool is_javascript_scheme = url.SchemeIs(url::kJavaScriptScheme); |
| 438 | + // JavaScript URLs are forbidden in chrome.tabs.update(). |
| 439 | + if (is_javascript_scheme) { |
| 440 | + const char kJavaScriptUrlsNotAllowedInTabsUpdate[] = |
| 441 | + "JavaScript URLs are not allowed in chrome.tabs.update. Use " |
| 442 | + "chrome.tabs.executeScript instead."; |
| 443 | + *error = kJavaScriptUrlsNotAllowedInTabsUpdate; |
| 444 | + return false; |
| 445 | + } |
| 446 | + |
| 447 | + content::NavigationController::LoadURLParams load_params(url); |
| 448 | + |
| 449 | + // Treat extension-initiated navigations as renderer-initiated so that the URL |
| 450 | + // does not show in the omnibox until it commits. This avoids URL spoofs |
| 451 | + // since URLs can be opened on behalf of untrusted content. |
| 452 | + load_params.is_renderer_initiated = true; |
| 453 | + // All renderer-initiated navigations need to have an initiator origin. |
| 454 | + load_params.initiator_origin = extension()->origin(); |
| 455 | + // |source_site_instance| needs to be set so that a renderer process |
| 456 | + // compatible with |initiator_origin| is picked by Site Isolation. |
| 457 | + load_params.source_site_instance = content::SiteInstance::CreateForURL( |
| 458 | + web_contents_->GetBrowserContext(), |
| 459 | + load_params.initiator_origin->GetURL()); |
| 460 | + |
| 461 | + // Marking the navigation as initiated via an API means that the focus |
| 462 | + // will stay in the omnibox - see https://crbug.com/1085779. |
| 463 | + load_params.transition_type = ui::PAGE_TRANSITION_FROM_API; |
| 464 | + |
| 465 | + web_contents_->GetController().LoadURLWithParams(load_params); |
| 466 | + |
| 467 | + DCHECK_EQ(url, |
| 468 | + web_contents_->GetController().GetPendingEntry()->GetVirtualURL()); |
| 469 | + |
| 470 | + return true; |
| 471 | +} |
| 472 | + |
| 473 | +ExtensionFunction::ResponseValue TabsUpdateFunction::GetResult() { |
| 474 | + if (!has_callback()) |
| 475 | + return NoArguments(); |
| 476 | + |
| 477 | + tabs::Tab tab; |
| 478 | + |
| 479 | + auto* api_web_contents = electron::api::WebContents::From(web_contents_); |
| 480 | + tab.id = |
| 481 | + std::make_unique<int>(api_web_contents ? api_web_contents->ID() : -1); |
| 482 | + // TODO(nornagon): in Chrome, the tab URL is only available to extensions |
| 483 | + // that have the "tabs" (or "activeTab") permission. We should do the same |
| 484 | + // permission check here. |
| 485 | + tab.url = std::make_unique<std::string>( |
| 486 | + web_contents_->GetLastCommittedURL().spec()); |
| 487 | + |
| 488 | + return ArgumentList(tabs::Get::Results::Create(std::move(tab))); |
| 489 | +} |
| 490 | + |
| 491 | +void TabsUpdateFunction::OnExecuteCodeFinished( |
| 492 | + const std::string& error, |
| 493 | + const GURL& url, |
| 494 | + const base::ListValue& script_result) { |
| 495 | + if (!error.empty()) { |
| 496 | + Respond(Error(error)); |
| 497 | + return; |
| 498 | + } |
| 499 | + |
| 500 | + return Respond(GetResult()); |
| 501 | +} |
| 502 | + |
322 | 503 | } // namespace extensions |
0 commit comments