Files
electron/shell/browser/api/electron_api_protocol.cc
9e1fffcfb5 chore: bump chromium to 157.0.8091.0 (main) (#54740)
* chore: bump chromium in DEPS to 157.0.8091.0

* chore: update siso revision

* 8518399: [Fullscreen] Reject requestFullscreen() when FullscreenAllowed is false

Upstream moved the kFullscreenAllowed pref check into a new
IsFullscreenAllowedByPref() helper that is now consulted from both
CanEnterFullscreenModeForTab() and EnterFullscreenModeInternal().
Electron has no Profile (ExclusiveAccessContext::GetProfile() returns
nullptr), so guarding only the old call site would now reject every
fullscreen request on non-Mac platforms. Disable the pref lookup inside
the helper instead so it always allows fullscreen.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8518399

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* 7867823: [android] Add a feature to propagate occlusion to viz

DisplayPrivate::SetDisplayVisible() now takes a
viz::mojom::DisplayVisibility enum instead of a bool. Keep the display
kVisible whenever background throttling is disabled, and kInvisible only
when throttling is enabled and the compositor is hidden.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7867823

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* 8510323: Use C++20 std::numbers::e and std::numbers::sqrt2 in blink

Remove the cherry-pick patch now that the upstream change has rolled in.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8510323

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* 8403316: [content] Add ContentClient::Schemes::code_cache_schemes

Upstream landed the embedder code cache scheme list that
feat_allow_code_cache_in_custom_schemes.patch added (it cites the
patch's earlier CL 5019665), so the patch is removed. The upstream
version records the schemes in blink::CommonSchemeRegistry rather than
in url::SchemeRegistry, so:

- RegisterSchemesAsPrivileged() now calls
  CommonSchemeRegistry::RegisterURLSchemeAsSupportingCodeCache() for
  custom schemes with codeCache enabled. The browser process registers
  these after RegisterContentSchemes() has already run, so it cannot
  rely on ContentClient::Schemes for them. Renderers keep registering
  them as CodeCacheWithHashing from the --code-cache-schemes switch.
- ElectronContentClient lists chrome-extension in
  Schemes::code_cache_schemes, matching ChromeContentClient, since the
  browser-side checks no longer special-case IsExtensionScheme().
- feat_allow_enabling_extensions_on_custom_protocols.patch loses the
  removed url::AddCodeCacheScheme() lines from its context.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8403316

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* chore: update patches

* 8362417: content: Add CopyFromSurfaceSecure and plumb is_secure to capturer

RenderWidgetHostViewBase::CopyFromSurface() is now final and dispatches
to CopyFromSurfaceImpl(), which takes is_copy_request_secure and must
pass it on to DelegatedFrameHost::CopyFromCompositingSurface().

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8362417

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* 8499040: Decisions API: Add Blink IDL, Mojo interfaces, Echo impl, and WPTs

Stub the new AIManager decision model methods as unavailable, like the
other unsupported AI APIs.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8499040

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* 8349225: Decouple Cookies API from Profile via CookiesApiDelegate

ExtensionsBrowserClient::GetBrowserContextLifetimeTracker() is now pure
virtual. Electron has no off-the-record sibling contexts to track, so
return nullptr, which the cookies API handles.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8349225

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* 8285500: hid: Restrict WebHID access in guest and partitioned contexts

HidDelegate::IsHidAllowedForFrame() is now pure virtual and gates
HidService creation. Mirror ElectronUsbDelegate::PageMayUseUsb(): deny
WebViewGuests and HTTP(S) pages in non-default storage partitions.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8285500

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* 8488316: [Linux] Query XDG portal versions per interface

RequestXdgDesktopPortal() now takes the portal interface whose version
to report; pass the FileChooser interface.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8488316

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* 8508466: Remove duplicate DownloadManager::CreateDownloadItem() overload

Pass an empty display_name, as the removed overload did.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8508466

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* 8357569: Move Cookies API implementation to //extensions

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8357569

* 8485746: gin: Use v8::CppHeapExternal for CallbackHolderBase

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8485746

* fix: re-enable v8 metagen

Skip-Lint: enabling a feature with cherry picked CL.

* fixup! 8499040: Decisions API: Add Blink IDL, Mojo interfaces, Echo impl, and WPTs

* 8526928: Reland "Mac: Activate the widget when focusing a View in an inactive widget"

FocusManager::SetFocusedViewWithReason() now activates an inactive widget
before focusing a View in it on macOS, and
NativeWidgetMacNSWindowHost::OnIsFirstResponderChanged() no longer
restores focus while the window is not key.

Forward both notifications to super so the views host tracks the
window's key status.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8526928

* fix: stop the remote debugging HTTP server on shutdown

DevToolsHttpHandler keeps a raw_ptr to the DevToolsManagerDelegate owned
by the DevToolsManager singleton, which is destroyed at exit. Electron
never stopped the HTTP server, so the delegate was freed while the
handler still pointed at it.

Skip-Lint: raw_ptr shutdown crash fix

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: electron-claude[bot] <279139701+electron-claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: deepak1556 <hop2deep@gmail.com>
2026-10-09 17:46:09 -05:00

408 lines
15 KiB
C++

// Copyright (c) 2019 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/api/electron_api_protocol.h"
#include <string_view>
#include <vector>
#include "base/command_line.h"
#include "base/no_destructor.h"
#include "content/common/url_schemes.h"
#include "content/public/browser/child_process_security_policy.h"
#include "gin/converter.h"
#include "gin/object_template_builder.h"
#include "shell/browser/browser.h"
#include "shell/browser/javascript_environment.h"
#include "shell/browser/net/protocol_source.h"
#include "shell/browser/protocol_registry.h"
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/net_converter.h"
#include "shell/common/gin_converters/value_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/object_template_builder.h"
#include "shell/common/gin_helper/wrappable_pointer_tags.h"
#include "shell/common/node_includes.h"
#include "shell/common/node_util.h"
#include "shell/common/options_switches.h"
#include "third_party/blink/public/common/scheme_registry.h"
#include "url/url_util.h"
#include "v8/include/cppgc/allocation.h"
namespace {
struct SchemeOptions {
bool standard = false;
bool secure = false;
bool bypassCSP = false;
bool allowServiceWorkers = false;
bool supportFetchAPI = false;
bool corsEnabled = false;
bool stream = false;
bool codeCache = false;
bool allowExtensions = false;
};
struct CustomScheme {
std::string scheme;
SchemeOptions options;
};
} // namespace
namespace gin {
template <>
struct Converter<CustomScheme> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
CustomScheme* out) {
gin::Dictionary dict(isolate);
if (!ConvertFromV8(isolate, val, &dict))
return false;
if (!dict.Get("scheme", &(out->scheme)))
return false;
gin::Dictionary opt(isolate);
// options are optional. Default values specified in SchemeOptions are used
if (dict.Get("privileges", &opt)) {
opt.Get("standard", &(out->options.standard));
opt.Get("secure", &(out->options.secure));
opt.Get("bypassCSP", &(out->options.bypassCSP));
opt.Get("allowServiceWorkers", &(out->options.allowServiceWorkers));
opt.Get("supportFetchAPI", &(out->options.supportFetchAPI));
opt.Get("corsEnabled", &(out->options.corsEnabled));
opt.Get("stream", &(out->options.stream));
opt.Get("codeCache", &(out->options.codeCache));
opt.Get("allowExtensions", &(out->options.allowExtensions));
}
return true;
}
};
} // namespace gin
namespace electron::api {
const gin::WrapperInfo Protocol::kWrapperInfo =
electron::MakeWrapperInfo(electron::kElectronProtocol);
std::vector<std::string>& GetStandardSchemes() {
static base::NoDestructor<std::vector<std::string>> g_standard_schemes;
return *g_standard_schemes;
}
std::vector<std::string>& GetCodeCacheSchemes() {
static base::NoDestructor<std::vector<std::string>> g_code_cache_schemes;
return *g_code_cache_schemes;
}
std::vector<std::string>& GetStreamingSchemes() {
static base::NoDestructor<std::vector<std::string>> g_streaming_schemes;
return *g_streaming_schemes;
}
void AddServiceWorkerScheme(const std::string& scheme) {
// There is no API to add service worker scheme, but there is an API to
// return const reference to the schemes vector.
// If in future the API is changed to return a copy instead of reference,
// the compilation will fail, and we should add a patch at that time.
auto& mutable_schemes =
const_cast<std::vector<std::string>&>(content::GetServiceWorkerSchemes());
mutable_schemes.push_back(scheme);
}
void RegisterSchemesAsPrivileged(gin_helper::ErrorThrower thrower,
v8::Local<v8::Value> val) {
std::vector<CustomScheme> custom_schemes;
if (!gin::ConvertFromV8(thrower.isolate(), val, &custom_schemes)) {
thrower.ThrowError("Argument must be an array of custom schemes.");
return;
}
for (const auto& custom_scheme : custom_schemes) {
// Schemes are comma-separated for --*-schemes. Sanitize names so
// extra commas don't trick child processes into splitting one
// scheme into many.
if (!Browser::IsValidProtocolScheme(custom_scheme.scheme)) {
thrower.ThrowError(
"Invalid scheme name '" + custom_scheme.scheme +
"'. Scheme names must start with an ASCII letter and contain only "
"ASCII letters, digits, '+', '-', or '.'.");
return;
}
if (custom_scheme.options.codeCache && !custom_scheme.options.standard) {
thrower.ThrowError(
"Code cache can only be enabled when the custom scheme is registered "
"as standard scheme.");
return;
}
}
std::vector<std::string> secure_schemes, cspbypassing_schemes, fetch_schemes,
service_worker_schemes, cors_schemes, extension_schemes;
for (const auto& custom_scheme : custom_schemes) {
// Register scheme to privileged list (https, wss, data, chrome-extension)
if (custom_scheme.options.standard) {
auto* policy = content::ChildProcessSecurityPolicy::GetInstance();
url::AddStandardScheme(custom_scheme.scheme.c_str(),
url::SCHEME_WITH_HOST);
GetStandardSchemes().push_back(custom_scheme.scheme);
policy->RegisterWebSafeScheme(custom_scheme.scheme);
}
if (custom_scheme.options.secure) {
secure_schemes.push_back(custom_scheme.scheme);
url::AddSecureScheme(custom_scheme.scheme.c_str());
}
if (custom_scheme.options.bypassCSP) {
cspbypassing_schemes.push_back(custom_scheme.scheme);
url::AddCSPBypassingScheme(custom_scheme.scheme.c_str());
}
if (custom_scheme.options.corsEnabled) {
cors_schemes.push_back(custom_scheme.scheme);
url::AddCorsEnabledScheme(custom_scheme.scheme.c_str());
}
if (custom_scheme.options.supportFetchAPI) {
fetch_schemes.push_back(custom_scheme.scheme);
}
if (custom_scheme.options.allowServiceWorkers) {
service_worker_schemes.push_back(custom_scheme.scheme);
AddServiceWorkerScheme(custom_scheme.scheme);
}
if (custom_scheme.options.stream) {
GetStreamingSchemes().push_back(custom_scheme.scheme);
}
if (custom_scheme.options.codeCache) {
GetCodeCacheSchemes().push_back(custom_scheme.scheme);
blink::CommonSchemeRegistry::RegisterURLSchemeAsSupportingCodeCache(
custom_scheme.scheme);
}
if (custom_scheme.options.allowExtensions) {
extension_schemes.push_back(custom_scheme.scheme);
url::AddExtensionScheme(custom_scheme.scheme.c_str());
}
}
const auto AppendSchemesToCmdLine = [](const std::string_view switch_name,
std::vector<std::string> schemes) {
if (schemes.empty())
return;
// Add the schemes to command line switches, so child processes can also
// register them.
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switch_name, base::JoinString(schemes, ","));
};
AppendSchemesToCmdLine(electron::switches::kSecureSchemes, secure_schemes);
AppendSchemesToCmdLine(electron::switches::kBypassCSPSchemes,
cspbypassing_schemes);
AppendSchemesToCmdLine(electron::switches::kCORSSchemes, cors_schemes);
AppendSchemesToCmdLine(electron::switches::kFetchSchemes, fetch_schemes);
AppendSchemesToCmdLine(electron::switches::kServiceWorkerSchemes,
service_worker_schemes);
AppendSchemesToCmdLine(electron::switches::kExtensionSchemes,
extension_schemes);
AppendSchemesToCmdLine(electron::switches::kStandardSchemes,
GetStandardSchemes());
AppendSchemesToCmdLine(electron::switches::kStreamingSchemes,
GetStreamingSchemes());
AppendSchemesToCmdLine(electron::switches::kCodeCacheSchemes,
GetCodeCacheSchemes());
}
Protocol::Protocol(ProtocolRegistry* protocol_registry)
: protocol_registry_{protocol_registry} {}
Protocol::~Protocol() = default;
// Convert error code to string.
// static
std::string_view Protocol::ErrorCodeToString(Error error) {
switch (error) {
case Error::kRegistered:
return "The scheme has been registered";
case Error::kNotRegistered:
return "The scheme has not been registered";
case Error::kIntercepted:
return "The scheme has been intercepted";
case Error::kNotIntercepted:
return "The scheme has not been intercepted";
default:
return "Unexpected error";
}
}
Protocol::Error Protocol::RegisterProtocol(ProtocolType type,
const std::string& scheme,
const ProtocolHandler& handler) {
bool added = protocol_registry_->RegisterProtocol(type, scheme, handler);
return added ? Error::kOK : Error::kRegistered;
}
bool Protocol::UnregisterProtocol(const std::string& scheme,
gin::Arguments* args) {
bool removed = protocol_registry_->UnregisterProtocol(scheme);
HandleOptionalCallback(args, removed ? Error::kOK : Error::kNotRegistered);
return removed;
}
bool Protocol::IsProtocolRegistered(const std::string& scheme) {
return protocol_registry_->FindRegistered(scheme) != nullptr;
}
void Protocol::RegisterSource(gin_helper::ErrorThrower thrower,
const std::string& scheme,
const base::DictValue& options) {
if (ProtocolRegistry::IsBuiltinScheme(scheme)) {
thrower.ThrowError("Cannot register a source for built-in scheme " +
scheme);
return;
}
std::string error;
scoped_refptr<const ProtocolSource> source =
ProtocolSource::Create(options, &error);
if (!source) {
thrower.ThrowTypeError(error);
return;
}
if (!protocol_registry_->RegisterSource(scheme, std::move(source)))
thrower.ThrowError("Scheme " + scheme + " is already handled");
}
bool Protocol::UnregisterSource(const std::string& scheme) {
return protocol_registry_->UnregisterSource(scheme);
}
v8::Local<v8::Value> Protocol::GetSource(v8::Isolate* isolate,
const std::string& scheme) {
const ProtocolSource* source = protocol_registry_->FindSource(scheme);
if (!source)
return v8::Null(isolate);
return gin::ConvertToV8(isolate, base::ValueView(source->spec()));
}
Protocol::Error Protocol::InterceptProtocol(ProtocolType type,
const std::string& scheme,
const ProtocolHandler& handler) {
bool added = protocol_registry_->InterceptProtocol(type, scheme, handler);
return added ? Error::kOK : Error::kIntercepted;
}
bool Protocol::UninterceptProtocol(const std::string& scheme,
gin::Arguments* args) {
bool removed = protocol_registry_->UninterceptProtocol(scheme);
HandleOptionalCallback(args, removed ? Error::kOK : Error::kNotIntercepted);
return removed;
}
bool Protocol::IsProtocolIntercepted(const std::string& scheme) {
return protocol_registry_->FindIntercepted(scheme) != nullptr;
}
void Protocol::HandleOptionalCallback(gin::Arguments* args, Error error) {
base::RepeatingCallback<void(v8::Local<v8::Value>)> callback;
if (args->GetNext(&callback)) {
util::EmitWarning(
args->isolate(),
"The callback argument of protocol module APIs is no longer needed.",
"ProtocolDeprecateCallback");
if (error == Error::kOK)
callback.Run(v8::Null(args->isolate()));
else
callback.Run(v8::Exception::Error(
gin::StringToV8(args->isolate(), ErrorCodeToString(error))));
}
}
// static
Protocol* Protocol::Create(v8::Isolate* isolate,
ProtocolRegistry* protocol_registry) {
return cppgc::MakeGarbageCollected<Protocol>(
isolate->GetCppHeap()->GetAllocationHandle(), protocol_registry);
}
// static
Protocol* Protocol::New(gin_helper::ErrorThrower thrower) {
thrower.ThrowError("Protocol cannot be created from JS");
return {};
}
// static
void Protocol::FillObjectTemplate(v8::Isolate* isolate,
v8::Local<v8::ObjectTemplate> tmpl) {
gin::ObjectTemplateBuilder(isolate, GetClassName(), tmpl)
.SetMethod("registerStringProtocol",
&Protocol::RegisterProtocolFor<ProtocolType::kString>)
.SetMethod("registerBufferProtocol",
&Protocol::RegisterProtocolFor<ProtocolType::kBuffer>)
.SetMethod("registerFileProtocol",
&Protocol::RegisterProtocolFor<ProtocolType::kFile>)
.SetMethod("registerHttpProtocol",
&Protocol::RegisterProtocolFor<ProtocolType::kHttp>)
.SetMethod("registerStreamProtocol",
&Protocol::RegisterProtocolFor<ProtocolType::kStream>)
.SetMethod("registerProtocol",
&Protocol::RegisterProtocolFor<ProtocolType::kFree>)
.SetMethod("unregisterProtocol", &Protocol::UnregisterProtocol)
.SetMethod("isProtocolRegistered", &Protocol::IsProtocolRegistered)
.SetMethod("registerSource", &Protocol::RegisterSource)
.SetMethod("unregisterSource", &Protocol::UnregisterSource)
.SetMethod("getSource", &Protocol::GetSource)
.SetMethod("interceptStringProtocol",
&Protocol::InterceptProtocolFor<ProtocolType::kString>)
.SetMethod("interceptBufferProtocol",
&Protocol::InterceptProtocolFor<ProtocolType::kBuffer>)
.SetMethod("interceptFileProtocol",
&Protocol::InterceptProtocolFor<ProtocolType::kFile>)
.SetMethod("interceptHttpProtocol",
&Protocol::InterceptProtocolFor<ProtocolType::kHttp>)
.SetMethod("interceptStreamProtocol",
&Protocol::InterceptProtocolFor<ProtocolType::kStream>)
.SetMethod("interceptProtocol",
&Protocol::InterceptProtocolFor<ProtocolType::kFree>)
.SetMethod("uninterceptProtocol", &Protocol::UninterceptProtocol)
.SetMethod("isProtocolIntercepted", &Protocol::IsProtocolIntercepted)
.Build();
}
const gin::WrapperInfo* Protocol::wrapper_info() const {
return &kWrapperInfo;
}
const char* Protocol::GetHumanReadableName() const {
return "Electron / Protocol";
}
} // namespace electron::api
namespace {
void RegisterSchemesAsPrivileged(gin_helper::ErrorThrower thrower,
v8::Local<v8::Value> val) {
if (electron::Browser::Get()->is_ready()) {
thrower.ThrowError(
"protocol.registerSchemesAsPrivileged should be called before "
"app is ready");
return;
}
electron::api::RegisterSchemesAsPrivileged(thrower, val);
}
void Initialize(v8::Local<v8::Object> exports,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context,
void* priv) {
v8::Isolate* const isolate = electron::JavascriptEnvironment::GetIsolate();
gin_helper::Dictionary dict{isolate, exports};
dict.Set("Protocol",
electron::api::Protocol::GetConstructor(
isolate, context, &electron::api::Protocol::kWrapperInfo));
dict.SetMethod<&RegisterSchemesAsPrivileged>("registerSchemesAsPrivileged");
dict.SetMethod<&electron::api::GetStandardSchemes>("getStandardSchemes");
}
} // namespace
NODE_LINKED_BINDING_CONTEXT_AWARE(electron_browser_protocol, Initialize)