From de66fb94068a373f65b0c573dc97388bbaba266e Mon Sep 17 00:00:00 2001 From: Marshall Greenblatt Date: Wed, 16 Jun 2021 15:54:26 -0400 Subject: [PATCH] alloy: Fix link click navigation in PDF files (fixes issue #3143) This change adds a minimal implementation of the |tabs.update| extension API and modifies StreamsPrivateAPI::SendExecuteMimeTypeHandlerEvent to return a valid |streamInfo.tabId| value as required by the navigateInCurrentTab implementation in chrome/browser/resources/pdf/browser_api.js. --- BUILD.gn | 2 + .../extensions/alloy_extensions_util.cc | 20 +++ .../extensions/alloy_extensions_util.h | 21 +++ .../browser/extensions/api/tabs/tabs_api.cc | 159 ++++++++++++++++-- libcef/browser/extensions/api/tabs/tabs_api.h | 39 ++++- .../extensions/chrome_api_registration.cc | 2 + .../extensions/extension_function_details.cc | 16 +- patch/patches/extensions_1947.patch | 39 ++++- 8 files changed, 265 insertions(+), 33 deletions(-) create mode 100644 libcef/browser/extensions/alloy_extensions_util.cc create mode 100644 libcef/browser/extensions/alloy_extensions_util.h diff --git a/BUILD.gn b/BUILD.gn index 8eba1abb8..a9e796168 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -486,6 +486,8 @@ static_library("libcef_static") { "libcef/browser/extensions/api/storage/sync_value_store_cache.h", "libcef/browser/extensions/api/tabs/tabs_api.cc", "libcef/browser/extensions/api/tabs/tabs_api.h", + "libcef/browser/extensions/alloy_extensions_util.cc", + "libcef/browser/extensions/alloy_extensions_util.h", "libcef/browser/extensions/browser_extensions_util.cc", "libcef/browser/extensions/browser_extensions_util.h", "libcef/browser/extensions/browser_platform_delegate_background.cc", diff --git a/libcef/browser/extensions/alloy_extensions_util.cc b/libcef/browser/extensions/alloy_extensions_util.cc new file mode 100644 index 000000000..0dabb1b22 --- /dev/null +++ b/libcef/browser/extensions/alloy_extensions_util.cc @@ -0,0 +1,20 @@ +// Copyright 2021 The Chromium Embedded Framework Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +#include "libcef/browser/extensions/alloy_extensions_util.h" + +#include "libcef/browser/alloy/alloy_browser_host_impl.h" + +namespace extensions { +namespace alloy { + +int GetTabIdForWebContents(content::WebContents* web_contents) { + auto browser = AlloyBrowserHostImpl::GetBrowserForContents(web_contents); + if (!browser) + return -1; + return browser->GetIdentifier(); +} + +} // namespace alloy +} // namespace extensions diff --git a/libcef/browser/extensions/alloy_extensions_util.h b/libcef/browser/extensions/alloy_extensions_util.h new file mode 100644 index 000000000..e68c0c2e1 --- /dev/null +++ b/libcef/browser/extensions/alloy_extensions_util.h @@ -0,0 +1,21 @@ +// Copyright 2021 The Chromium Embedded Framework Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +#ifndef CEF_LIBCEF_BROWSER_EXTENSIONS_ALLOY_EXTENSIONS_UTIL_H_ +#define CEF_LIBCEF_BROWSER_EXTENSIONS_ALLOY_EXTENSIONS_UTIL_H_ + +namespace content { +class WebContents; +} + +namespace extensions { +namespace alloy { + +// Returns the tabId for |web_contents|, or -1 if not found. +int GetTabIdForWebContents(content::WebContents* web_contents); + +} // namespace alloy +} // namespace extensions + +#endif // CEF_LIBCEF_BROWSER_EXTENSIONS_ALLOY_EXTENSIONS_UTIL_H_ diff --git a/libcef/browser/extensions/api/tabs/tabs_api.cc b/libcef/browser/extensions/api/tabs/tabs_api.cc index db10572ee..5197c3b85 100644 --- a/libcef/browser/extensions/api/tabs/tabs_api.cc +++ b/libcef/browser/extensions/api/tabs/tabs_api.cc @@ -6,10 +6,15 @@ #include "libcef/browser/extensions/extension_web_contents_observer.h" +#include "base/notreached.h" #include "base/strings/string_number_conversions.h" #include "chrome/browser/extensions/api/tabs/tabs_constants.h" +#include "chrome/browser/extensions/extension_tab_util.h" #include "components/zoom/zoom_controller.h" +#include "content/public/browser/navigation_controller.h" +#include "content/public/browser/navigation_entry.h" #include "content/public/browser/render_frame_host.h" +#include "content/public/browser/site_instance.h" #include "extensions/browser/extension_api_frame_id_map.h" #include "extensions/browser/extension_zoom_request_client.h" #include "extensions/common/error_utils.h" @@ -99,6 +104,148 @@ ExtensionFunction::ResponseAction TabsCreateFunction::Run() { : NoArguments()); } +BaseAPIFunction::BaseAPIFunction() : cef_details_(this) {} + +content::WebContents* BaseAPIFunction::GetWebContents(int tab_id) { + // Find a browser that we can access, or set |error_| and return nullptr. + CefRefPtr browser = + cef_details_.GetBrowserForTabIdFirstTime(tab_id, &error_); + if (!browser) + return nullptr; + + return browser->web_contents(); +} + +ExtensionFunction::ResponseAction TabsUpdateFunction::Run() { + std::unique_ptr params( + tabs::Update::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params.get()); + + tab_id_ = params->tab_id ? *params->tab_id : -1; + content::WebContents* web_contents = GetWebContents(tab_id_); + if (!web_contents) + return RespondNow(Error(std::move(error_))); + + web_contents_ = web_contents; + + // TODO(rafaelw): handle setting remaining tab properties: + // -title + // -favIconUrl + + // Navigate the tab to a new location if the url is different. + if (params->update_properties.url.get()) { + std::string updated_url = *params->update_properties.url; + if (!UpdateURL(updated_url, tab_id_, &error_)) + return RespondNow(Error(std::move(error_))); + } + + bool active = false; + // TODO(rafaelw): Setting |active| from js doesn't make much sense. + // Move tab selection management up to window. + if (params->update_properties.selected.get()) + active = *params->update_properties.selected; + + // The 'active' property has replaced 'selected'. + if (params->update_properties.active.get()) + active = *params->update_properties.active; + + if (active) { + // TODO: Activate the tab at |tab_id_|. + NOTIMPLEMENTED(); + return RespondNow(Error(tabs_constants::kTabStripNotEditableError)); + } + + if (params->update_properties.highlighted.get() && + *params->update_properties.highlighted) { + // TODO: Highlight the tab at |tab_id_|. + NOTIMPLEMENTED(); + return RespondNow(Error(tabs_constants::kTabStripNotEditableError)); + } + + if (params->update_properties.pinned.get() && + *params->update_properties.pinned) { + // TODO: Pin the tab at |tab_id_|. + NOTIMPLEMENTED(); + return RespondNow(Error(tabs_constants::kTabStripNotEditableError)); + } + + if (params->update_properties.muted.get()) { + // TODO: Mute/unmute the tab at |tab_id_|. + NOTIMPLEMENTED(); + return RespondNow(Error(ErrorUtils::FormatErrorMessage( + tabs_constants::kCannotUpdateMuteCaptured, + base::NumberToString(tab_id_)))); + } + + if (params->update_properties.opener_tab_id.get()) { + int opener_id = *params->update_properties.opener_tab_id; + if (opener_id == tab_id_) + return RespondNow(Error("Cannot set a tab's opener to itself.")); + + // TODO: Set the opener for the tab at |tab_id_|. + NOTIMPLEMENTED(); + return RespondNow(Error(tabs_constants::kTabStripNotEditableError)); + } + + if (params->update_properties.auto_discardable.get()) { + // TODO: Set auto-discardable state for the tab at |tab_id_|. + NOTIMPLEMENTED(); + } + + return RespondNow(GetResult()); +} + +bool TabsUpdateFunction::UpdateURL(const std::string& url_string, + int tab_id, + std::string* error) { + GURL url; + if (!ExtensionTabUtil::PrepareURLForNavigation(url_string, extension(), &url, + error)) { + return false; + } + + const bool is_javascript_scheme = url.SchemeIs(url::kJavaScriptScheme); + // JavaScript URLs are forbidden in chrome.tabs.update(). + if (is_javascript_scheme) { + *error = tabs_constants::kJavaScriptUrlsNotAllowedInTabsUpdate; + return false; + } + + content::NavigationController::LoadURLParams load_params(url); + + // Treat extension-initiated navigations as renderer-initiated so that the URL + // does not show in the omnibox until it commits. This avoids URL spoofs + // since URLs can be opened on behalf of untrusted content. + load_params.is_renderer_initiated = true; + // All renderer-initiated navigations need to have an initiator origin. + load_params.initiator_origin = extension()->origin(); + // |source_site_instance| needs to be set so that a renderer process + // compatible with |initiator_origin| is picked by Site Isolation. + load_params.source_site_instance = content::SiteInstance::CreateForURL( + web_contents_->GetBrowserContext(), + load_params.initiator_origin->GetURL()); + + // Marking the navigation as initiated via an API means that the focus + // will stay in the omnibox - see https://crbug.com/1085779. + load_params.transition_type = ui::PAGE_TRANSITION_FROM_API; + + web_contents_->GetController().LoadURLWithParams(load_params); + + DCHECK_EQ(url, + web_contents_->GetController().GetPendingEntry()->GetVirtualURL()); + + return true; +} + +ExtensionFunction::ResponseValue TabsUpdateFunction::GetResult() { + if (!has_callback()) + return NoArguments(); + + return ArgumentList(tabs::Get::Results::Create(*cef_details_.CreateTabObject( + AlloyBrowserHostImpl::GetBrowserForContents(web_contents_), + /*opener_browser_id=*/-1, /*active=*/true, tab_id_))); +} + ExecuteCodeInTabFunction::ExecuteCodeInTabFunction() : cef_details_(this), execute_tab_id_(-1) {} @@ -246,18 +393,6 @@ bool TabsRemoveCSSFunction::ShouldRemoveCSS() const { return true; } -ZoomAPIFunction::ZoomAPIFunction() : cef_details_(this) {} - -content::WebContents* ZoomAPIFunction::GetWebContents(int tab_id) { - // Find a browser that we can access, or set |error_| and return nullptr. - CefRefPtr browser = - cef_details_.GetBrowserForTabIdFirstTime(tab_id, &error_); - if (!browser) - return nullptr; - - return browser->web_contents(); -} - ExtensionFunction::ResponseAction TabsSetZoomFunction::Run() { std::unique_ptr params( tabs::SetZoom::Params::Create(*args_)); diff --git a/libcef/browser/extensions/api/tabs/tabs_api.h b/libcef/browser/extensions/api/tabs/tabs_api.h index fc03adc1c..409d758db 100644 --- a/libcef/browser/extensions/api/tabs/tabs_api.h +++ b/libcef/browser/extensions/api/tabs/tabs_api.h @@ -42,6 +42,37 @@ class TabsCreateFunction : public ExtensionFunction { const CefExtensionFunctionDetails cef_details_; }; +class BaseAPIFunction : public ExtensionFunction { + public: + BaseAPIFunction(); + + protected: + ~BaseAPIFunction() override {} + + // Gets the WebContents for |tab_id| if it is specified. Otherwise get the + // WebContents for the active tab in the current window. Calling this function + // may set |error_|. + content::WebContents* GetWebContents(int tab_id); + + std::string error_; + const CefExtensionFunctionDetails cef_details_; +}; + +class TabsUpdateFunction : public BaseAPIFunction { + private: + ~TabsUpdateFunction() override {} + + ResponseAction Run() override; + + bool UpdateURL(const std::string& url, int tab_id, std::string* error); + ResponseValue GetResult(); + + DECLARE_EXTENSION_FUNCTION("tabs.update", TABS_UPDATE) + + int tab_id_ = -1; + content::WebContents* web_contents_ = nullptr; +}; + // Implement API calls tabs.executeScript, tabs.insertCSS, and tabs.removeCSS. class ExecuteCodeInTabFunction : public ExecuteCodeFunction { public: @@ -114,7 +145,7 @@ class ZoomAPIFunction : public ExtensionFunction { const CefExtensionFunctionDetails cef_details_; }; -class TabsSetZoomFunction : public ZoomAPIFunction { +class TabsSetZoomFunction : public BaseAPIFunction { private: ~TabsSetZoomFunction() override {} @@ -123,7 +154,7 @@ class TabsSetZoomFunction : public ZoomAPIFunction { DECLARE_EXTENSION_FUNCTION("tabs.setZoom", TABS_SETZOOM) }; -class TabsGetZoomFunction : public ZoomAPIFunction { +class TabsGetZoomFunction : public BaseAPIFunction { private: ~TabsGetZoomFunction() override {} @@ -132,7 +163,7 @@ class TabsGetZoomFunction : public ZoomAPIFunction { DECLARE_EXTENSION_FUNCTION("tabs.getZoom", TABS_GETZOOM) }; -class TabsSetZoomSettingsFunction : public ZoomAPIFunction { +class TabsSetZoomSettingsFunction : public BaseAPIFunction { private: ~TabsSetZoomSettingsFunction() override {} @@ -141,7 +172,7 @@ class TabsSetZoomSettingsFunction : public ZoomAPIFunction { DECLARE_EXTENSION_FUNCTION("tabs.setZoomSettings", TABS_SETZOOMSETTINGS) }; -class TabsGetZoomSettingsFunction : public ZoomAPIFunction { +class TabsGetZoomSettingsFunction : public BaseAPIFunction { private: ~TabsGetZoomSettingsFunction() override {} diff --git a/libcef/browser/extensions/chrome_api_registration.cc b/libcef/browser/extensions/chrome_api_registration.cc index bf2e3006f..23d15c75a 100644 --- a/libcef/browser/extensions/chrome_api_registration.cc +++ b/libcef/browser/extensions/chrome_api_registration.cc @@ -51,6 +51,7 @@ const char* const kSupportedAPIs[] = { "tabs", EXTENSION_FUNCTION_NAME(cefimpl::TabsGetFunction), EXTENSION_FUNCTION_NAME(cefimpl::TabsCreateFunction), + EXTENSION_FUNCTION_NAME(cefimpl::TabsUpdateFunction), EXTENSION_FUNCTION_NAME(cefimpl::TabsExecuteScriptFunction), EXTENSION_FUNCTION_NAME(cefimpl::TabsInsertCSSFunction), EXTENSION_FUNCTION_NAME(cefimpl::TabsRemoveCSSFunction), @@ -95,6 +96,7 @@ void ChromeFunctionRegistry::RegisterAll(ExtensionFunctionRegistry* registry) { registry->RegisterFunction(); registry->RegisterFunction(); registry->RegisterFunction(); + registry->RegisterFunction(); registry->RegisterFunction(); registry->RegisterFunction(); registry->RegisterFunction(); diff --git a/libcef/browser/extensions/extension_function_details.cc b/libcef/browser/extensions/extension_function_details.cc index e6cf50771..c664bfffd 100644 --- a/libcef/browser/extensions/extension_function_details.cc +++ b/libcef/browser/extensions/extension_function_details.cc @@ -318,24 +318,12 @@ base::DictionaryValue* CefExtensionFunctionDetails::OpenTab( GURL url; if (params.url.get()) { std::string url_string = *params.url; - url = ExtensionTabUtil::ResolvePossiblyRelativeURL(url_string, - function()->extension()); - if (!url.is_valid()) { - if (error_message) { - *error_message = - ErrorUtils::FormatErrorMessage(keys::kInvalidUrlError, url_string); - } + if (!ExtensionTabUtil::PrepareURLForNavigation( + url_string, function()->extension(), &url, error_message)) { return nullptr; } } - // Don't let extensions crash the browser or renderers. - if (ExtensionTabUtil::IsKillURL(url)) { - if (error_message) - *error_message = keys::kNoCrashBrowserError; - return nullptr; - } - // Default to foreground for the new tab. The presence of 'active' property // will override this default. bool active = true; diff --git a/patch/patches/extensions_1947.patch b/patch/patches/extensions_1947.patch index 311a5ed34..b8dd90e66 100644 --- a/patch/patches/extensions_1947.patch +++ b/patch/patches/extensions_1947.patch @@ -1,5 +1,5 @@ diff --git chrome/browser/extensions/api/streams_private/streams_private_api.cc chrome/browser/extensions/api/streams_private/streams_private_api.cc -index 5c903a13a14ed..d385c6c0c95c4 100644 +index 5c903a13a14ed..c85964a7bab17 100644 --- chrome/browser/extensions/api/streams_private/streams_private_api.cc +++ chrome/browser/extensions/api/streams_private/streams_private_api.cc @@ -6,6 +6,7 @@ @@ -10,7 +10,18 @@ index 5c903a13a14ed..d385c6c0c95c4 100644 #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/prefetch/no_state_prefetch/chrome_no_state_prefetch_contents_delegate.h" #include "components/no_state_prefetch/browser/no_state_prefetch_contents.h" -@@ -42,6 +43,7 @@ void StreamsPrivateAPI::SendExecuteMimeTypeHandlerEvent( +@@ -18,6 +19,10 @@ + #include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h" + #include "extensions/common/manifest_handlers/mime_types_handler.h" + ++#if BUILDFLAG(ENABLE_CEF) ++#include "cef/libcef/browser/extensions/alloy_extensions_util.h" ++#endif ++ + namespace extensions { + + void StreamsPrivateAPI::SendExecuteMimeTypeHandlerEvent( +@@ -42,6 +47,7 @@ void StreamsPrivateAPI::SendExecuteMimeTypeHandlerEvent( if (!web_contents) return; @@ -18,7 +29,7 @@ index 5c903a13a14ed..d385c6c0c95c4 100644 // If the request was for NoStatePrefetch, abort the prefetcher and do not // continue. This is because plugins cancel NoStatePrefetch, see // http://crbug.com/343590. -@@ -52,6 +54,7 @@ void StreamsPrivateAPI::SendExecuteMimeTypeHandlerEvent( +@@ -52,6 +58,7 @@ void StreamsPrivateAPI::SendExecuteMimeTypeHandlerEvent( no_state_prefetch_contents->Destroy(prerender::FINAL_STATUS_DOWNLOAD); return; } @@ -26,6 +37,28 @@ index 5c903a13a14ed..d385c6c0c95c4 100644 auto* browser_context = web_contents->GetBrowserContext(); +@@ -78,9 +85,18 @@ void StreamsPrivateAPI::SendExecuteMimeTypeHandlerEvent( + // forms of zooming won't work). + // TODO(1042323): Present a coherent representation of a tab id for portal + // contents. +- int tab_id = web_contents->GetOuterWebContents() +- ? SessionID::InvalidValue().id() +- : ExtensionTabUtil::GetTabId(web_contents); ++ int tab_id; ++ if (web_contents->GetOuterWebContents()) { ++ tab_id = SessionID::InvalidValue().id(); ++ } else ++#if BUILDFLAG(ENABLE_CEF) ++ if (cef::IsAlloyRuntimeEnabled()) { ++ tab_id = alloy::GetTabIdForWebContents(web_contents); ++ } else ++#endif // BUILDFLAG(ENABLE_CEF) ++ { ++ tab_id = ExtensionTabUtil::GetTabId(web_contents); ++ } + + std::unique_ptr stream_container( + new StreamContainer(tab_id, embedded, handler_url, extension_id, diff --git extensions/browser/extension_host.cc extensions/browser/extension_host.cc index 7e38155c3b695..78768b28d8c99 100644 --- extensions/browser/extension_host.cc