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:
parent
e775467e9c
commit
6df2680cb6
8 changed files with 604 additions and 632 deletions
|
@ -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');
|
||||||
|
|
|
@ -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;
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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:': {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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_;
|
||||||
};
|
};
|
||||||
|
|
File diff suppressed because it is too large
Load diff
15
typings/internal-ambient.d.ts
vendored
15
typings/internal-ambient.d.ts
vendored
|
@ -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 = {
|
||||||
|
|
Loading…
Reference in a new issue