refactor: clean up webFrame implementation to use gin wrappers (#28497)

* refactor: clean up webFrame implementation to use gin wrappers

The previous implementation of webFrame in the renderer process leaked
sub-frame contexts and global objects across the context boundaries thus
making it possible for apps to either maliciously or accidentally
violate the contextIsolation boundary.

This re-implementation binds all methods in native code directly to
content::RenderFrame instances instead of relying on JS to provide a
"window" with every method request.  This is much more consistent with
the rest of the Electron codebase and is substantially safer.

* chore: un-re-order for ease of review

* chore: pass isolate around instead of ErrorThrower

* chore: fix rebase typo

* chore: remove unused variables
This commit is contained in:
Samuel Attard 2021-04-12 16:35:18 -07:00 committed by GitHub
parent e775467e9c
commit 6df2680cb6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 604 additions and 632 deletions

View file

@ -1,7 +1,7 @@
const { getWebPreference } = process._linkedBinding('electron_renderer_web_frame'); const { mainFrame } = process._linkedBinding('electron_renderer_web_frame');
const binding = process._linkedBinding('electron_renderer_context_bridge'); const binding = process._linkedBinding('electron_renderer_context_bridge');
const contextIsolationEnabled = getWebPreference(window, 'contextIsolation'); const contextIsolationEnabled = mainFrame.getWebPreference('contextIsolation');
const checkContextIsolationEnabled = () => { const checkContextIsolationEnabled = () => {
if (!contextIsolationEnabled) throw new Error('contextBridge API can only be used when contextIsolation is enabled'); if (!contextIsolationEnabled) throw new Error('contextBridge API can only be used when contextIsolation is enabled');

View file

@ -1,69 +1,3 @@
import { EventEmitter } from 'events'; const { mainFrame } = process._linkedBinding('electron_renderer_web_frame');
const binding = process._linkedBinding('electron_renderer_web_frame'); export default mainFrame;
class WebFrame extends EventEmitter {
constructor (public context: Window) {
super();
// Lots of webview would subscribe to webFrame's events.
this.setMaxListeners(0);
}
findFrameByRoutingId (routingId: number) {
return getWebFrame(binding._findFrameByRoutingId(this.context, routingId));
}
getFrameForSelector (selector: string) {
return getWebFrame(binding._getFrameForSelector(this.context, selector));
}
findFrameByName (name: string) {
return getWebFrame(binding._findFrameByName(this.context, name));
}
get opener () {
return getWebFrame(binding._getOpener(this.context));
}
get parent () {
return getWebFrame(binding._getParent(this.context));
}
get top () {
return getWebFrame(binding._getTop(this.context));
}
get firstChild () {
return getWebFrame(binding._getFirstChild(this.context));
}
get nextSibling () {
return getWebFrame(binding._getNextSibling(this.context));
}
get routingId () {
return binding._getRoutingId(this.context);
}
}
// Populate the methods.
for (const name in binding) {
if (!name.startsWith('_')) { // some methods are manually populated above
// TODO(felixrieseberg): Once we can type web_frame natives, we could
// use a neat `keyof` here
(WebFrame as any).prototype[name] = function (...args: Array<any>) {
return (binding as any)[name](this.context, ...args);
};
}
}
// Helper to return WebFrame or null depending on context.
// TODO(zcbenz): Consider returning same WebFrame for the same frame.
function getWebFrame (context: Window) {
return context ? new WebFrame(context) : null;
}
const _webFrame = new WebFrame(window);
export default _webFrame;

View file

@ -63,18 +63,18 @@ webFrameInit();
// Process command line arguments. // Process command line arguments.
const { hasSwitch, getSwitchValue } = process._linkedBinding('electron_common_command_line'); const { hasSwitch, getSwitchValue } = process._linkedBinding('electron_common_command_line');
const { getWebPreference } = process._linkedBinding('electron_renderer_web_frame'); const { mainFrame } = process._linkedBinding('electron_renderer_web_frame');
const contextIsolation = getWebPreference(window, 'contextIsolation'); const contextIsolation = mainFrame.getWebPreference('contextIsolation');
const nodeIntegration = getWebPreference(window, 'nodeIntegration'); const nodeIntegration = mainFrame.getWebPreference('nodeIntegration');
const webviewTag = getWebPreference(window, 'webviewTag'); const webviewTag = mainFrame.getWebPreference('webviewTag');
const isHiddenPage = getWebPreference(window, 'hiddenPage'); const isHiddenPage = mainFrame.getWebPreference('hiddenPage');
const usesNativeWindowOpen = getWebPreference(window, 'nativeWindowOpen'); const usesNativeWindowOpen = mainFrame.getWebPreference('nativeWindowOpen');
const rendererProcessReuseEnabled = getWebPreference(window, 'disableElectronSiteInstanceOverrides'); const rendererProcessReuseEnabled = mainFrame.getWebPreference('disableElectronSiteInstanceOverrides');
const preloadScript = getWebPreference(window, 'preload'); const preloadScript = mainFrame.getWebPreference('preload');
const preloadScripts = getWebPreference(window, 'preloadScripts'); const preloadScripts = mainFrame.getWebPreference('preloadScripts');
const guestInstanceId = getWebPreference(window, 'guestInstanceId') || null; const guestInstanceId = mainFrame.getWebPreference('guestInstanceId') || null;
const openerId = getWebPreference(window, 'openerId') || null; const openerId = mainFrame.getWebPreference('openerId') || null;
const appPath = hasSwitch('app-path') ? getSwitchValue('app-path') : null; const appPath = hasSwitch('app-path') ? getSwitchValue('app-path') : null;
// The webContents preload script is loaded after the session preload scripts. // The webContents preload script is loaded after the session preload scripts.

View file

@ -113,7 +113,7 @@ function preloadRequire (module: string) {
// Process command line arguments. // Process command line arguments.
const { hasSwitch } = process._linkedBinding('electron_common_command_line'); const { hasSwitch } = process._linkedBinding('electron_common_command_line');
const { getWebPreference } = process._linkedBinding('electron_renderer_web_frame'); const { mainFrame } = process._linkedBinding('electron_renderer_web_frame');
// Similar to nodes --expose-internals flag, this exposes _linkedBinding so // Similar to nodes --expose-internals flag, this exposes _linkedBinding so
// that tests can call it to get access to some test only bindings // that tests can call it to get access to some test only bindings
@ -121,13 +121,13 @@ if (hasSwitch('unsafely-expose-electron-internals-for-testing')) {
preloadProcess._linkedBinding = process._linkedBinding; preloadProcess._linkedBinding = process._linkedBinding;
} }
const contextIsolation = getWebPreference(window, 'contextIsolation'); const contextIsolation = mainFrame.getWebPreference('contextIsolation');
const webviewTag = getWebPreference(window, 'webviewTag'); const webviewTag = mainFrame.getWebPreference('webviewTag');
const isHiddenPage = getWebPreference(window, 'hiddenPage'); const isHiddenPage = mainFrame.getWebPreference('hiddenPage');
const rendererProcessReuseEnabled = getWebPreference(window, 'disableElectronSiteInstanceOverrides'); const rendererProcessReuseEnabled = mainFrame.getWebPreference('disableElectronSiteInstanceOverrides');
const usesNativeWindowOpen = true; const usesNativeWindowOpen = true;
const guestInstanceId = getWebPreference(window, 'guestInstanceId') || null; const guestInstanceId = mainFrame.getWebPreference('guestInstanceId') || null;
const openerId = getWebPreference(window, 'openerId') || null; const openerId = mainFrame.getWebPreference('openerId') || null;
switch (window.location.protocol) { switch (window.location.protocol) {
case 'devtools:': { case 'devtools:': {

View file

@ -17,27 +17,27 @@ ErrorThrower::ErrorThrower() : isolate_(v8::Isolate::GetCurrent()) {}
ErrorThrower::~ErrorThrower() = default; ErrorThrower::~ErrorThrower() = default;
void ErrorThrower::ThrowError(base::StringPiece err_msg) { void ErrorThrower::ThrowError(base::StringPiece err_msg) const {
Throw(v8::Exception::Error, err_msg); Throw(v8::Exception::Error, err_msg);
} }
void ErrorThrower::ThrowTypeError(base::StringPiece err_msg) { void ErrorThrower::ThrowTypeError(base::StringPiece err_msg) const {
Throw(v8::Exception::TypeError, err_msg); Throw(v8::Exception::TypeError, err_msg);
} }
void ErrorThrower::ThrowRangeError(base::StringPiece err_msg) { void ErrorThrower::ThrowRangeError(base::StringPiece err_msg) const {
Throw(v8::Exception::RangeError, err_msg); Throw(v8::Exception::RangeError, err_msg);
} }
void ErrorThrower::ThrowReferenceError(base::StringPiece err_msg) { void ErrorThrower::ThrowReferenceError(base::StringPiece err_msg) const {
Throw(v8::Exception::ReferenceError, err_msg); Throw(v8::Exception::ReferenceError, err_msg);
} }
void ErrorThrower::ThrowSyntaxError(base::StringPiece err_msg) { void ErrorThrower::ThrowSyntaxError(base::StringPiece err_msg) const {
Throw(v8::Exception::SyntaxError, err_msg); Throw(v8::Exception::SyntaxError, err_msg);
} }
void ErrorThrower::Throw(ErrorGenerator gen, base::StringPiece err_msg) { void ErrorThrower::Throw(ErrorGenerator gen, base::StringPiece err_msg) const {
v8::Local<v8::Value> exception = gen(gin::StringToV8(isolate_, err_msg)); v8::Local<v8::Value> exception = gen(gin::StringToV8(isolate_, err_msg));
if (!isolate_->IsExecutionTerminating()) if (!isolate_->IsExecutionTerminating())
isolate_->ThrowException(exception); isolate_->ThrowException(exception);

View file

@ -17,18 +17,18 @@ class ErrorThrower {
~ErrorThrower(); ~ErrorThrower();
void ThrowError(base::StringPiece err_msg); void ThrowError(base::StringPiece err_msg) const;
void ThrowTypeError(base::StringPiece err_msg); void ThrowTypeError(base::StringPiece err_msg) const;
void ThrowRangeError(base::StringPiece err_msg); void ThrowRangeError(base::StringPiece err_msg) const;
void ThrowReferenceError(base::StringPiece err_msg); void ThrowReferenceError(base::StringPiece err_msg) const;
void ThrowSyntaxError(base::StringPiece err_msg); void ThrowSyntaxError(base::StringPiece err_msg) const;
v8::Isolate* isolate() const { return isolate_; } v8::Isolate* isolate() const { return isolate_; }
private: private:
using ErrorGenerator = using ErrorGenerator =
v8::Local<v8::Value> (*)(v8::Local<v8::String> err_msg); v8::Local<v8::Value> (*)(v8::Local<v8::String> err_msg);
void Throw(ErrorGenerator gen, base::StringPiece err_msg); void Throw(ErrorGenerator gen, base::StringPiece err_msg) const;
v8::Isolate* isolate_; v8::Isolate* isolate_;
}; };

View file

@ -16,6 +16,9 @@
#include "content/public/renderer/render_frame_observer.h" #include "content/public/renderer/render_frame_observer.h"
#include "content/public/renderer/render_frame_visitor.h" #include "content/public/renderer/render_frame_visitor.h"
#include "content/public/renderer/render_view.h" #include "content/public/renderer/render_view.h"
#include "gin/handle.h"
#include "gin/object_template_builder.h"
#include "gin/wrappable.h"
#include "services/service_manager/public/cpp/interface_provider.h" #include "services/service_manager/public/cpp/interface_provider.h"
#include "shell/common/api/api.mojom.h" #include "shell/common/api/api.mojom.h"
#include "shell/common/gin_converters/blink_converter.h" #include "shell/common/gin_converters/blink_converter.h"
@ -23,6 +26,7 @@
#include "shell/common/gin_converters/file_path_converter.h" #include "shell/common/gin_converters/file_path_converter.h"
#include "shell/common/gin_helper/dictionary.h" #include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/error_thrower.h" #include "shell/common/gin_helper/error_thrower.h"
#include "shell/common/gin_helper/function_template_extensions.h"
#include "shell/common/gin_helper/promise.h" #include "shell/common/gin_helper/promise.h"
#include "shell/common/node_includes.h" #include "shell/common/node_includes.h"
#include "shell/common/options_switches.h" #include "shell/common/options_switches.h"
@ -111,17 +115,13 @@ content::RenderFrame* GetRenderFrame(v8::Local<v8::Value> value) {
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER) #if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
bool SpellCheckWord(v8::Isolate* isolate, bool SpellCheckWord(content::RenderFrame* render_frame,
v8::Local<v8::Value> window,
const std::string& word, const std::string& word,
std::vector<std::u16string>* optional_suggestions) { std::vector<std::u16string>* optional_suggestions) {
size_t start; size_t start;
size_t length; size_t length;
ElectronRendererClient* client = ElectronRendererClient::Get(); ElectronRendererClient* client = ElectronRendererClient::Get();
auto* render_frame = GetRenderFrame(window);
if (!render_frame)
return true;
std::u16string w = base::UTF8ToUTF16(word); std::u16string w = base::UTF8ToUTF16(word);
int id = render_frame->GetRoutingID(); int id = render_frame->GetRoutingID();
@ -206,9 +206,9 @@ class ScriptExecutionCallback : public blink::WebScriptExecutionCallback {
if (!result.empty()) { if (!result.empty()) {
if (!result[0].IsEmpty()) { if (!result[0].IsEmpty()) {
v8::Local<v8::Value> value = result[0]; v8::Local<v8::Value> value = result[0];
// Either world safe results are disabled or the result was created in // Either the result was created in the same world as the caller
// the same world as the caller or the result is not an object and // or the result is not an object and therefore does not have a
// therefore does not have a prototype chain to protect // prototype chain to protect
bool should_clone_value = bool should_clone_value =
!(value->IsObject() && !(value->IsObject() &&
promise_.GetContext() == promise_.GetContext() ==
@ -344,41 +344,121 @@ class SpellCheckerHolder final : public content::RenderFrameObserver {
} // namespace } // namespace
// static class WebFrameRenderer : public gin::Wrappable<WebFrameRenderer>,
std::set<SpellCheckerHolder*> SpellCheckerHolder::instances_; public content::RenderFrameObserver {
public:
static gin::WrapperInfo kWrapperInfo;
void SetName(v8::Local<v8::Value> window, const std::string& name) { static gin::Handle<WebFrameRenderer> Create(
GetRenderFrame(window)->GetWebFrame()->SetName( v8::Isolate* isolate,
blink::WebString::FromUTF8(name)); content::RenderFrame* render_frame) {
return gin::CreateHandle(isolate, new WebFrameRenderer(render_frame));
} }
void SetZoomLevel(gin_helper::ErrorThrower thrower, explicit WebFrameRenderer(content::RenderFrame* render_frame)
v8::Local<v8::Value> window, : content::RenderFrameObserver(render_frame) {
double level) { DCHECK(render_frame);
content::RenderFrame* render_frame = GetRenderFrame(window); }
if (!render_frame) {
thrower.ThrowError( // gin::Wrappable:
"Render frame was torn down before webFrame.setZoomLevel could be " gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
"executed"); v8::Isolate* isolate) override {
return gin::Wrappable<WebFrameRenderer>::GetObjectTemplateBuilder(isolate)
.SetMethod("getWebFrameId", &WebFrameRenderer::GetWebFrameId)
.SetMethod("setName", &WebFrameRenderer::SetName)
.SetMethod("setZoomLevel", &WebFrameRenderer::SetZoomLevel)
.SetMethod("getZoomLevel", &WebFrameRenderer::GetZoomLevel)
.SetMethod("setZoomFactor", &WebFrameRenderer::SetZoomFactor)
.SetMethod("getZoomFactor", &WebFrameRenderer::GetZoomFactor)
.SetMethod("getWebPreference", &WebFrameRenderer::GetWebPreference)
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
.SetMethod("isWordMisspelled", &WebFrameRenderer::IsWordMisspelled)
.SetMethod("getWordSuggestions", &WebFrameRenderer::GetWordSuggestions)
#endif
.SetMethod("setVisualZoomLevelLimits",
&WebFrameRenderer::SetVisualZoomLevelLimits)
.SetMethod("allowGuestViewElementDefinition",
&WebFrameRenderer::AllowGuestViewElementDefinition)
.SetMethod("insertText", &WebFrameRenderer::InsertText)
.SetMethod("insertCSS", &WebFrameRenderer::InsertCSS)
.SetMethod("removeInsertedCSS", &WebFrameRenderer::RemoveInsertedCSS)
.SetMethod("executeJavaScript", &WebFrameRenderer::ExecuteJavaScript)
.SetMethod("executeJavaScriptInIsolatedWorld",
&WebFrameRenderer::ExecuteJavaScriptInIsolatedWorld)
.SetMethod("setIsolatedWorldInfo",
&WebFrameRenderer::SetIsolatedWorldInfo)
.SetMethod("getResourceUsage", &WebFrameRenderer::GetResourceUsage)
.SetMethod("clearCache", &WebFrameRenderer::ClearCache)
.SetMethod("setSpellCheckProvider",
&WebFrameRenderer::SetSpellCheckProvider)
// Frame navigators
.SetMethod("findFrameByRoutingId",
&WebFrameRenderer::FindFrameByRoutingId)
.SetMethod("getFrameForSelector",
&WebFrameRenderer::GetFrameForSelector)
.SetMethod("findFrameByName", &WebFrameRenderer::FindFrameByName)
.SetProperty("opener", &WebFrameRenderer::GetOpener)
.SetProperty("parent", &WebFrameRenderer::GetFrameParent)
.SetProperty("top", &WebFrameRenderer::GetTop)
.SetProperty("firstChild", &WebFrameRenderer::GetFirstChild)
.SetProperty("nextSibling", &WebFrameRenderer::GetNextSibling)
.SetProperty("routingId", &WebFrameRenderer::GetRoutingId);
}
const char* GetTypeName() override { return "WebFrameRenderer"; }
void OnDestruct() override {}
private:
bool MaybeGetRenderFrame(v8::Isolate* isolate,
const std::string& method_name,
content::RenderFrame** render_frame_ptr) {
std::string error_msg;
if (!MaybeGetRenderFrame(&error_msg, method_name, render_frame_ptr)) {
gin_helper::ErrorThrower(isolate).ThrowError(error_msg);
return false;
}
return true;
}
bool MaybeGetRenderFrame(std::string* error_msg,
const std::string& method_name,
content::RenderFrame** render_frame_ptr) {
auto* frame = render_frame();
if (!frame) {
*error_msg = "Render frame was torn down before webFrame." + method_name +
" could be "
"executed";
return false;
}
*render_frame_ptr = frame;
return true;
}
void SetName(v8::Isolate* isolate, const std::string& name) {
content::RenderFrame* render_frame;
if (!MaybeGetRenderFrame(isolate, "setName", &render_frame))
return; return;
render_frame->GetWebFrame()->SetName(blink::WebString::FromUTF8(name));
} }
void SetZoomLevel(v8::Isolate* isolate, double level) {
content::RenderFrame* render_frame;
if (!MaybeGetRenderFrame(isolate, "setZoomLevel", &render_frame))
return;
mojo::Remote<mojom::ElectronBrowser> browser_remote; mojo::Remote<mojom::ElectronBrowser> browser_remote;
render_frame->GetBrowserInterfaceBroker()->GetInterface( render_frame->GetBrowserInterfaceBroker()->GetInterface(
browser_remote.BindNewPipeAndPassReceiver()); browser_remote.BindNewPipeAndPassReceiver());
browser_remote->SetTemporaryZoomLevel(level); browser_remote->SetTemporaryZoomLevel(level);
} }
double GetZoomLevel(gin_helper::ErrorThrower thrower, double GetZoomLevel(v8::Isolate* isolate) {
v8::Local<v8::Value> window) {
double result = 0.0; double result = 0.0;
content::RenderFrame* render_frame = GetRenderFrame(window); content::RenderFrame* render_frame;
if (!render_frame) { if (!MaybeGetRenderFrame(isolate, "getZoomLevel", &render_frame))
thrower.ThrowError(
"Render frame was torn down before webFrame.getZoomLevel could be "
"executed");
return result; return result;
}
mojo::Remote<mojom::ElectronBrowser> browser_remote; mojo::Remote<mojom::ElectronBrowser> browser_remote;
render_frame->GetBrowserInterfaceBroker()->GetInterface( render_frame->GetBrowserInterfaceBroker()->GetInterface(
@ -387,27 +467,26 @@ double GetZoomLevel(gin_helper::ErrorThrower thrower,
return result; return result;
} }
void SetZoomFactor(gin_helper::ErrorThrower thrower, void SetZoomFactor(gin_helper::ErrorThrower thrower, double factor) {
v8::Local<v8::Value> window,
double factor) {
if (factor < std::numeric_limits<double>::epsilon()) { if (factor < std::numeric_limits<double>::epsilon()) {
thrower.ThrowError("'zoomFactor' must be a double greater than 0.0"); thrower.ThrowError("'zoomFactor' must be a double greater than 0.0");
return; return;
} }
SetZoomLevel(thrower, window, blink::PageZoomFactorToZoomLevel(factor)); SetZoomLevel(thrower.isolate(), blink::PageZoomFactorToZoomLevel(factor));
} }
double GetZoomFactor(gin_helper::ErrorThrower thrower, double GetZoomFactor(v8::Isolate* isolate) {
v8::Local<v8::Value> window) { double zoom_level = GetZoomLevel(isolate);
double zoom_level = GetZoomLevel(thrower, window);
return blink::PageZoomLevelToZoomFactor(zoom_level); return blink::PageZoomLevelToZoomFactor(zoom_level);
} }
v8::Local<v8::Value> GetWebPreference(v8::Isolate* isolate, v8::Local<v8::Value> GetWebPreference(v8::Isolate* isolate,
v8::Local<v8::Value> window,
std::string pref_name) { std::string pref_name) {
content::RenderFrame* render_frame = GetRenderFrame(window); content::RenderFrame* render_frame;
if (!MaybeGetRenderFrame(isolate, "getWebPreference", &render_frame))
return v8::Undefined(isolate);
const auto& prefs = render_frame->GetBlinkPreferences(); const auto& prefs = render_frame->GetBlinkPreferences();
if (pref_name == options::kPreloadScripts) { if (pref_name == options::kPreloadScripts) {
@ -457,45 +536,36 @@ v8::Local<v8::Value> GetWebPreference(v8::Isolate* isolate,
return v8::Null(isolate); return v8::Null(isolate);
} }
void SetVisualZoomLevelLimits(gin_helper::ErrorThrower thrower, void SetVisualZoomLevelLimits(v8::Isolate* isolate,
v8::Local<v8::Value> window,
double min_level, double min_level,
double max_level) { double max_level) {
auto* render_frame = GetRenderFrame(window); content::RenderFrame* render_frame;
if (!render_frame) { if (!MaybeGetRenderFrame(isolate, "setVisualZoomLevelLimits",
thrower.ThrowError( &render_frame))
"Render frame was torn down before webFrame.setVisualZoomLevelLimits "
"could be executed");
return; return;
}
blink::WebFrame* web_frame = render_frame->GetWebFrame(); blink::WebFrame* web_frame = render_frame->GetWebFrame();
web_frame->View()->SetDefaultPageScaleLimits(min_level, max_level); web_frame->View()->SetDefaultPageScaleLimits(min_level, max_level);
} }
void AllowGuestViewElementDefinition(gin_helper::ErrorThrower thrower, void AllowGuestViewElementDefinition(v8::Isolate* isolate,
v8::Local<v8::Value> window,
v8::Local<v8::Object> context, v8::Local<v8::Object> context,
v8::Local<v8::Function> register_cb) { v8::Local<v8::Function> register_cb) {
v8::HandleScope handle_scope(thrower.isolate()); v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(context->CreationContext()); v8::Context::Scope context_scope(context->CreationContext());
blink::WebCustomElement::EmbedderNamesAllowedScope embedder_names_scope; blink::WebCustomElement::EmbedderNamesAllowedScope embedder_names_scope;
auto* render_frame = GetRenderFrame(window); content::RenderFrame* render_frame;
if (!render_frame) { if (!MaybeGetRenderFrame(isolate, "allowGuestViewElementDefinition",
thrower.ThrowError( &render_frame))
"Render frame was torn down before "
"webFrame.allowGuestViewElementDefinition could be executed");
return; return;
}
render_frame->GetWebFrame()->RequestExecuteV8Function( render_frame->GetWebFrame()->RequestExecuteV8Function(
context->CreationContext(), register_cb, v8::Null(thrower.isolate()), 0, context->CreationContext(), register_cb, v8::Null(isolate), 0, nullptr,
nullptr, nullptr); nullptr);
} }
int GetWebFrameId(v8::Local<v8::Value> window, static int GetWebFrameId(v8::Local<v8::Value> content_window) {
v8::Local<v8::Value> content_window) {
// Get the WebLocalFrame before (possibly) executing any user-space JS while // Get the WebLocalFrame before (possibly) executing any user-space JS while
// getting the |params|. We track the status of the RenderFrame via an // getting the |params|. We track the status of the RenderFrame via an
// observer in case it is deleted during user code execution. // observer in case it is deleted during user code execution.
@ -514,25 +584,21 @@ int GetWebFrameId(v8::Local<v8::Value> window,
return render_frame->GetRoutingID(); return render_frame->GetRoutingID();
} }
void SetSpellCheckProvider(gin_helper::Arguments* args, void SetSpellCheckProvider(gin_helper::ErrorThrower thrower,
v8::Local<v8::Value> window, v8::Isolate* isolate,
const std::string& language, const std::string& language,
v8::Local<v8::Object> provider) { v8::Local<v8::Object> provider) {
auto context = args->isolate()->GetCurrentContext(); auto context = isolate->GetCurrentContext();
if (!provider->Has(context, gin::StringToV8(args->isolate(), "spellCheck")) if (!provider->Has(context, gin::StringToV8(isolate, "spellCheck"))
.ToChecked()) { .ToChecked()) {
args->ThrowError("\"spellCheck\" has to be defined"); thrower.ThrowError("\"spellCheck\" has to be defined");
return; return;
} }
// Remove the old client. // Remove the old client.
content::RenderFrame* render_frame = GetRenderFrame(window); content::RenderFrame* render_frame;
if (!render_frame) { if (!MaybeGetRenderFrame(isolate, "setSpellCheckProvider", &render_frame))
args->ThrowError(
"Render frame was torn down before webFrame.setSpellCheckProvider "
"could be executed");
return; return;
}
auto* existing = SpellCheckerHolder::FromRenderFrame(render_frame); auto* existing = SpellCheckerHolder::FromRenderFrame(render_frame);
if (existing) if (existing)
@ -541,23 +607,17 @@ void SetSpellCheckProvider(gin_helper::Arguments* args,
// Set spellchecker for all live frames in the same process or // Set spellchecker for all live frames in the same process or
// in the sandbox mode for all live sub frames to this WebFrame. // in the sandbox mode for all live sub frames to this WebFrame.
auto spell_check_client = auto spell_check_client =
std::make_unique<SpellCheckClient>(language, args->isolate(), provider); std::make_unique<SpellCheckClient>(language, isolate, provider);
FrameSetSpellChecker spell_checker(spell_check_client.get(), render_frame); FrameSetSpellChecker spell_checker(spell_check_client.get(), render_frame);
// Attach the spell checker to RenderFrame. // Attach the spell checker to RenderFrame.
new SpellCheckerHolder(render_frame, std::move(spell_check_client)); new SpellCheckerHolder(render_frame, std::move(spell_check_client));
} }
void InsertText(gin_helper::ErrorThrower thrower, void InsertText(v8::Isolate* isolate, const std::string& text) {
v8::Local<v8::Value> window, content::RenderFrame* render_frame;
const std::string& text) { if (!MaybeGetRenderFrame(isolate, "insertText", &render_frame))
auto* render_frame = GetRenderFrame(window);
if (!render_frame) {
thrower.ThrowError(
"Render frame was torn down before webFrame.insertText could be "
"executed");
return; return;
}
blink::WebFrame* web_frame = render_frame->GetWebFrame(); blink::WebFrame* web_frame = render_frame->GetWebFrame();
if (web_frame->IsWebLocalFrame()) { if (web_frame->IsWebLocalFrame()) {
@ -565,13 +625,14 @@ void InsertText(gin_helper::ErrorThrower thrower,
->FrameWidget() ->FrameWidget()
->GetActiveWebInputMethodController() ->GetActiveWebInputMethodController()
->CommitText(blink::WebString::FromUTF8(text), ->CommitText(blink::WebString::FromUTF8(text),
blink::WebVector<ui::ImeTextSpan>(), blink::WebRange(), 0); blink::WebVector<ui::ImeTextSpan>(), blink::WebRange(),
0);
} }
} }
std::u16string InsertCSS(v8::Local<v8::Value> window, std::u16string InsertCSS(v8::Isolate* isolate,
const std::string& css, const std::string& css,
gin_helper::Arguments* args) { gin::Arguments* args) {
blink::WebDocument::CSSOrigin css_origin = blink::WebDocument::CSSOrigin css_origin =
blink::WebDocument::CSSOrigin::kAuthorOrigin; blink::WebDocument::CSSOrigin::kAuthorOrigin;
@ -579,34 +640,25 @@ std::u16string InsertCSS(v8::Local<v8::Value> window,
if (args->GetNext(&options)) if (args->GetNext(&options))
options.Get("cssOrigin", &css_origin); options.Get("cssOrigin", &css_origin);
auto* render_frame = GetRenderFrame(window); content::RenderFrame* render_frame;
if (!render_frame) { if (!MaybeGetRenderFrame(isolate, "insertCSS", &render_frame))
args->ThrowError(
"Render frame was torn down before webFrame.insertCSS could be "
"executed");
return std::u16string(); return std::u16string();
}
blink::WebFrame* web_frame = render_frame->GetWebFrame(); blink::WebFrame* web_frame = render_frame->GetWebFrame();
if (web_frame->IsWebLocalFrame()) { if (web_frame->IsWebLocalFrame()) {
return web_frame->ToWebLocalFrame() return web_frame->ToWebLocalFrame()
->GetDocument() ->GetDocument()
.InsertStyleSheet(blink::WebString::FromUTF8(css), nullptr, css_origin) .InsertStyleSheet(blink::WebString::FromUTF8(css), nullptr,
css_origin)
.Utf16(); .Utf16();
} }
return std::u16string(); return std::u16string();
} }
void RemoveInsertedCSS(gin_helper::ErrorThrower thrower, void RemoveInsertedCSS(v8::Isolate* isolate, const std::u16string& key) {
v8::Local<v8::Value> window, content::RenderFrame* render_frame;
const std::u16string& key) { if (!MaybeGetRenderFrame(isolate, "removeInsertedCSS", &render_frame))
auto* render_frame = GetRenderFrame(window);
if (!render_frame) {
thrower.ThrowError(
"Render frame was torn down before webFrame.removeInsertedCSS could be "
"executed");
return; return;
}
blink::WebFrame* web_frame = render_frame->GetWebFrame(); blink::WebFrame* web_frame = render_frame->GetWebFrame();
if (web_frame->IsWebLocalFrame()) { if (web_frame->IsWebLocalFrame()) {
@ -615,18 +667,18 @@ void RemoveInsertedCSS(gin_helper::ErrorThrower thrower,
} }
} }
v8::Local<v8::Promise> ExecuteJavaScript(gin_helper::Arguments* args, v8::Local<v8::Promise> ExecuteJavaScript(gin::Arguments* gin_args,
v8::Local<v8::Value> window,
const std::u16string& code) { const std::u16string& code) {
gin_helper::Arguments* args = static_cast<gin_helper::Arguments*>(gin_args);
v8::Isolate* isolate = args->isolate(); v8::Isolate* isolate = args->isolate();
gin_helper::Promise<v8::Local<v8::Value>> promise(isolate); gin_helper::Promise<v8::Local<v8::Value>> promise(isolate);
v8::Local<v8::Promise> handle = promise.GetHandle(); v8::Local<v8::Promise> handle = promise.GetHandle();
auto* render_frame = GetRenderFrame(window); content::RenderFrame* render_frame;
if (!render_frame) { std::string error_msg;
promise.RejectWithErrorMessage( if (!MaybeGetRenderFrame(&error_msg, "executeJavaScript", &render_frame)) {
"Render frame was torn down before webFrame.executeJavaScript could be " promise.RejectWithErrorMessage(error_msg);
"executed");
return handle; return handle;
} }
@ -646,19 +698,20 @@ v8::Local<v8::Promise> ExecuteJavaScript(gin_helper::Arguments* args,
} }
v8::Local<v8::Promise> ExecuteJavaScriptInIsolatedWorld( v8::Local<v8::Promise> ExecuteJavaScriptInIsolatedWorld(
gin_helper::Arguments* args, gin::Arguments* gin_args,
v8::Local<v8::Value> window,
int world_id, int world_id,
const std::vector<gin_helper::Dictionary>& scripts) { const std::vector<gin_helper::Dictionary>& scripts) {
gin_helper::Arguments* args = static_cast<gin_helper::Arguments*>(gin_args);
v8::Isolate* isolate = args->isolate(); v8::Isolate* isolate = args->isolate();
gin_helper::Promise<v8::Local<v8::Value>> promise(isolate); gin_helper::Promise<v8::Local<v8::Value>> promise(isolate);
v8::Local<v8::Promise> handle = promise.GetHandle(); v8::Local<v8::Promise> handle = promise.GetHandle();
auto* render_frame = GetRenderFrame(window); content::RenderFrame* render_frame;
if (!render_frame) { std::string error_msg;
promise.RejectWithErrorMessage( if (!MaybeGetRenderFrame(&error_msg, "executeJavaScriptInIsolatedWorld",
"Render frame was torn down before " &render_frame)) {
"webFrame.executeJavaScriptInIsolatedWorld could be executed"); promise.RejectWithErrorMessage(error_msg);
return handle; return handle;
} }
@ -708,17 +761,12 @@ v8::Local<v8::Promise> ExecuteJavaScriptInIsolatedWorld(
return handle; return handle;
} }
void SetIsolatedWorldInfo(v8::Local<v8::Value> window, void SetIsolatedWorldInfo(v8::Isolate* isolate,
int world_id, int world_id,
const gin_helper::Dictionary& options, const gin_helper::Dictionary& options) {
gin_helper::Arguments* args) { content::RenderFrame* render_frame;
auto* render_frame = GetRenderFrame(window); if (!MaybeGetRenderFrame(isolate, "setIsolatedWorldInfo", &render_frame))
if (!render_frame) {
args->ThrowError(
"Render frame was torn down before webFrame.setIsolatedWorldInfo could "
"be executed");
return; return;
}
std::string origin_url, security_policy, name; std::string origin_url, security_policy, name;
options.Get("securityOrigin", &origin_url); options.Get("securityOrigin", &origin_url);
@ -726,7 +774,7 @@ void SetIsolatedWorldInfo(v8::Local<v8::Value> window,
options.Get("name", &name); options.Get("name", &name);
if (!security_policy.empty() && origin_url.empty()) { if (!security_policy.empty() && origin_url.empty()) {
args->ThrowError( gin_helper::ErrorThrower(isolate).ThrowError(
"If csp is specified, securityOrigin should also be specified"); "If csp is specified, securityOrigin should also be specified");
return; return;
} }
@ -746,21 +794,24 @@ blink::WebCacheResourceTypeStats GetResourceUsage(v8::Isolate* isolate) {
} }
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER) #if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
bool IsWordMisspelled(v8::Isolate* isolate, const std::string& word) {
content::RenderFrame* render_frame;
if (!MaybeGetRenderFrame(isolate, "isWordMisspelled", &render_frame))
return false;
bool IsWordMisspelled(v8::Isolate* isolate, return !SpellCheckWord(render_frame, word, nullptr);
v8::Local<v8::Value> window,
const std::string& word) {
return !SpellCheckWord(isolate, window, word, nullptr);
} }
std::vector<std::u16string> GetWordSuggestions(v8::Isolate* isolate, std::vector<std::u16string> GetWordSuggestions(v8::Isolate* isolate,
v8::Local<v8::Value> window,
const std::string& word) { const std::string& word) {
content::RenderFrame* render_frame;
std::vector<std::u16string> suggestions; std::vector<std::u16string> suggestions;
SpellCheckWord(isolate, window, word, &suggestions); if (!MaybeGetRenderFrame(isolate, "getWordSuggestions", &render_frame))
return suggestions;
SpellCheckWord(render_frame, word, &suggestions);
return suggestions; return suggestions;
} }
#endif #endif
void ClearCache(v8::Isolate* isolate) { void ClearCache(v8::Isolate* isolate) {
@ -771,120 +822,143 @@ void ClearCache(v8::Isolate* isolate) {
} }
v8::Local<v8::Value> FindFrameByRoutingId(v8::Isolate* isolate, v8::Local<v8::Value> FindFrameByRoutingId(v8::Isolate* isolate,
v8::Local<v8::Value> window,
int routing_id) { int routing_id) {
content::RenderFrame* render_frame = content::RenderFrame* render_frame =
content::RenderFrame::FromRoutingID(routing_id); content::RenderFrame::FromRoutingID(routing_id);
if (render_frame) if (render_frame)
return render_frame->GetWebFrame()->MainWorldScriptContext()->Global(); return WebFrameRenderer::Create(isolate, render_frame).ToV8();
else else
return v8::Null(isolate); return v8::Null(isolate);
} }
v8::Local<v8::Value> GetOpener(v8::Isolate* isolate, v8::Local<v8::Value> GetOpener(v8::Isolate* isolate) {
v8::Local<v8::Value> window) { content::RenderFrame* render_frame;
auto* render_frame = GetRenderFrame(window); if (!MaybeGetRenderFrame(isolate, "opener", &render_frame))
if (!render_frame)
return v8::Null(isolate); return v8::Null(isolate);
blink::WebFrame* frame = render_frame->GetWebFrame()->Opener(); blink::WebFrame* frame = render_frame->GetWebFrame()->Opener();
if (frame && frame->IsWebLocalFrame()) if (frame && frame->IsWebLocalFrame())
return frame->ToWebLocalFrame()->MainWorldScriptContext()->Global(); return WebFrameRenderer::Create(
isolate,
content::RenderFrame::FromWebFrame(frame->ToWebLocalFrame()))
.ToV8();
else else
return v8::Null(isolate); return v8::Null(isolate);
} }
// Don't name it as GetParent, Windows has API with same name. // Don't name it as GetParent, Windows has API with same name.
v8::Local<v8::Value> GetFrameParent(v8::Isolate* isolate, v8::Local<v8::Value> GetFrameParent(v8::Isolate* isolate) {
v8::Local<v8::Value> window) { content::RenderFrame* render_frame;
blink::WebFrame* frame = GetRenderFrame(window)->GetWebFrame()->Parent(); if (!MaybeGetRenderFrame(isolate, "parent", &render_frame))
return v8::Null(isolate);
blink::WebFrame* frame = render_frame->GetWebFrame()->Parent();
if (frame && frame->IsWebLocalFrame()) if (frame && frame->IsWebLocalFrame())
return frame->ToWebLocalFrame()->MainWorldScriptContext()->Global(); return WebFrameRenderer::Create(
isolate,
content::RenderFrame::FromWebFrame(frame->ToWebLocalFrame()))
.ToV8();
else else
return v8::Null(isolate); return v8::Null(isolate);
} }
v8::Local<v8::Value> GetTop(v8::Isolate* isolate, v8::Local<v8::Value> window) { v8::Local<v8::Value> GetTop(v8::Isolate* isolate) {
auto* render_frame = GetRenderFrame(window); content::RenderFrame* render_frame;
if (!render_frame) if (!MaybeGetRenderFrame(isolate, "top", &render_frame))
return v8::Null(isolate); return v8::Null(isolate);
blink::WebFrame* frame = render_frame->GetWebFrame()->Top(); blink::WebFrame* frame = render_frame->GetWebFrame()->Top();
if (frame && frame->IsWebLocalFrame()) if (frame && frame->IsWebLocalFrame())
return frame->ToWebLocalFrame()->MainWorldScriptContext()->Global(); return WebFrameRenderer::Create(
isolate,
content::RenderFrame::FromWebFrame(frame->ToWebLocalFrame()))
.ToV8();
else else
return v8::Null(isolate); return v8::Null(isolate);
} }
v8::Local<v8::Value> GetFirstChild(v8::Isolate* isolate, v8::Local<v8::Value> GetFirstChild(v8::Isolate* isolate) {
v8::Local<v8::Value> window) { content::RenderFrame* render_frame;
auto* render_frame = GetRenderFrame(window); if (!MaybeGetRenderFrame(isolate, "firstChild", &render_frame))
if (!render_frame)
return v8::Null(isolate); return v8::Null(isolate);
blink::WebFrame* frame = render_frame->GetWebFrame()->FirstChild(); blink::WebFrame* frame = render_frame->GetWebFrame()->FirstChild();
if (frame && frame->IsWebLocalFrame()) if (frame && frame->IsWebLocalFrame())
return frame->ToWebLocalFrame()->MainWorldScriptContext()->Global(); return WebFrameRenderer::Create(
isolate,
content::RenderFrame::FromWebFrame(frame->ToWebLocalFrame()))
.ToV8();
else else
return v8::Null(isolate); return v8::Null(isolate);
} }
v8::Local<v8::Value> GetNextSibling(v8::Isolate* isolate, v8::Local<v8::Value> GetNextSibling(v8::Isolate* isolate) {
v8::Local<v8::Value> window) { content::RenderFrame* render_frame;
auto* render_frame = GetRenderFrame(window); if (!MaybeGetRenderFrame(isolate, "nextSibling", &render_frame))
if (!render_frame)
return v8::Null(isolate); return v8::Null(isolate);
blink::WebFrame* frame = render_frame->GetWebFrame()->NextSibling(); blink::WebFrame* frame = render_frame->GetWebFrame()->NextSibling();
if (frame && frame->IsWebLocalFrame()) if (frame && frame->IsWebLocalFrame())
return frame->ToWebLocalFrame()->MainWorldScriptContext()->Global(); return WebFrameRenderer::Create(
isolate,
content::RenderFrame::FromWebFrame(frame->ToWebLocalFrame()))
.ToV8();
else else
return v8::Null(isolate); return v8::Null(isolate);
} }
v8::Local<v8::Value> GetFrameForSelector(v8::Isolate* isolate, v8::Local<v8::Value> GetFrameForSelector(v8::Isolate* isolate,
v8::Local<v8::Value> window,
const std::string& selector) { const std::string& selector) {
content::RenderFrame* render_frame;
if (!MaybeGetRenderFrame(isolate, "getFrameForSelector", &render_frame))
return v8::Null(isolate);
blink::WebElement element = blink::WebElement element =
GetRenderFrame(window)->GetWebFrame()->GetDocument().QuerySelector( render_frame->GetWebFrame()->GetDocument().QuerySelector(
blink::WebString::FromUTF8(selector)); blink::WebString::FromUTF8(selector));
if (element.IsNull()) // not found if (element.IsNull()) // not found
return v8::Null(isolate); return v8::Null(isolate);
blink::WebFrame* frame = blink::WebFrame::FromFrameOwnerElement(element); blink::WebFrame* frame = blink::WebFrame::FromFrameOwnerElement(element);
if (frame && frame->IsWebLocalFrame()) if (frame && frame->IsWebLocalFrame())
return frame->ToWebLocalFrame()->MainWorldScriptContext()->Global(); return WebFrameRenderer::Create(
isolate,
content::RenderFrame::FromWebFrame(frame->ToWebLocalFrame()))
.ToV8();
else else
return v8::Null(isolate); return v8::Null(isolate);
} }
v8::Local<v8::Value> FindFrameByName(v8::Isolate* isolate, v8::Local<v8::Value> FindFrameByName(v8::Isolate* isolate,
v8::Local<v8::Value> window,
const std::string& name) { const std::string& name) {
auto* render_frame = GetRenderFrame(window); content::RenderFrame* render_frame;
if (!render_frame) if (!MaybeGetRenderFrame(isolate, "getFrameForSelector", &render_frame))
return v8::Null(isolate); return v8::Null(isolate);
blink::WebFrame* frame = render_frame->GetWebFrame()->FindFrameByName( blink::WebFrame* frame = render_frame->GetWebFrame()->FindFrameByName(
blink::WebString::FromUTF8(name)); blink::WebString::FromUTF8(name));
if (frame && frame->IsWebLocalFrame()) if (frame && frame->IsWebLocalFrame())
return frame->ToWebLocalFrame()->MainWorldScriptContext()->Global(); return WebFrameRenderer::Create(
isolate,
content::RenderFrame::FromWebFrame(frame->ToWebLocalFrame()))
.ToV8();
else else
return v8::Null(isolate); return v8::Null(isolate);
} }
int GetRoutingId(gin_helper::ErrorThrower thrower, int GetRoutingId(v8::Isolate* isolate) {
v8::Local<v8::Value> window) { content::RenderFrame* render_frame;
auto* render_frame = GetRenderFrame(window); if (!MaybeGetRenderFrame(isolate, "routingId", &render_frame))
if (!render_frame) {
thrower.ThrowError(
"Render frame was torn down before webFrame.getRoutingId could be "
"executed");
return 0; return 0;
}
return render_frame->GetRoutingID(); return render_frame->GetRoutingID();
} }
};
gin::WrapperInfo WebFrameRenderer::kWrapperInfo = {gin::kEmbedderNativeGin};
// static
std::set<SpellCheckerHolder*> SpellCheckerHolder::instances_;
} // namespace api } // namespace api
@ -900,39 +974,8 @@ void Initialize(v8::Local<v8::Object> exports,
v8::Isolate* isolate = context->GetIsolate(); v8::Isolate* isolate = context->GetIsolate();
gin_helper::Dictionary dict(isolate, exports); gin_helper::Dictionary dict(isolate, exports);
dict.SetMethod("setName", &SetName); dict.Set("mainFrame",
dict.SetMethod("setZoomLevel", &SetZoomLevel); WebFrameRenderer::Create(isolate, GetRenderFrame(exports)));
dict.SetMethod("getZoomLevel", &GetZoomLevel);
dict.SetMethod("setZoomFactor", &SetZoomFactor);
dict.SetMethod("getZoomFactor", &GetZoomFactor);
dict.SetMethod("setVisualZoomLevelLimits", &SetVisualZoomLevelLimits);
dict.SetMethod("allowGuestViewElementDefinition",
&AllowGuestViewElementDefinition);
dict.SetMethod("getWebFrameId", &GetWebFrameId);
dict.SetMethod("getWebPreference", &GetWebPreference);
dict.SetMethod("setSpellCheckProvider", &SetSpellCheckProvider);
dict.SetMethod("insertText", &InsertText);
dict.SetMethod("insertCSS", &InsertCSS);
dict.SetMethod("removeInsertedCSS", &RemoveInsertedCSS);
dict.SetMethod("executeJavaScript", &ExecuteJavaScript);
dict.SetMethod("executeJavaScriptInIsolatedWorld",
&ExecuteJavaScriptInIsolatedWorld);
dict.SetMethod("setIsolatedWorldInfo", &SetIsolatedWorldInfo);
dict.SetMethod("getResourceUsage", &GetResourceUsage);
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
dict.SetMethod("isWordMisspelled", &IsWordMisspelled);
dict.SetMethod("getWordSuggestions", &GetWordSuggestions);
#endif
dict.SetMethod("clearCache", &ClearCache);
dict.SetMethod("_findFrameByRoutingId", &FindFrameByRoutingId);
dict.SetMethod("_getFrameForSelector", &GetFrameForSelector);
dict.SetMethod("_findFrameByName", &FindFrameByName);
dict.SetMethod("_getOpener", &GetOpener);
dict.SetMethod("_getParent", &GetFrameParent);
dict.SetMethod("_getTop", &GetTop);
dict.SetMethod("_getFirstChild", &GetFirstChild);
dict.SetMethod("_getNextSibling", &GetNextSibling);
dict.SetMethod("_getRoutingId", &GetRoutingId);
} }
} // namespace } // namespace

View file

@ -114,17 +114,12 @@ declare namespace NodeJS {
webviewTag: boolean; webviewTag: boolean;
} }
interface InternalWebFrame extends Electron.WebFrame {
getWebPreference<K extends keyof InternalWebPreferences>(name: K): InternalWebPreferences[K];
}
interface WebFrameBinding { interface WebFrameBinding {
_findFrameByRoutingId(window: Window, routingId: number): Window; mainFrame: InternalWebFrame;
_getFrameForSelector(window: Window, selector: string): Window;
_findFrameByName(window: Window, name: string): Window;
_getOpener(window: Window): Window;
_getParent(window: Window): Window;
_getTop(window: Window): Window;
_getFirstChild(window: Window): Window;
_getNextSibling(window: Window): Window;
_getRoutingId(window: Window): number;
getWebPreference<K extends keyof InternalWebPreferences>(window: Window, name: K): InternalWebPreferences[K];
} }
type DataPipe = { type DataPipe = {