build: enable JS semicolons (#22783)

This commit is contained in:
Samuel Attard 2020-03-20 13:28:31 -07:00 committed by GitHub
parent 24e21467b9
commit 5d657dece4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
354 changed files with 21512 additions and 21510 deletions

View file

@ -1,20 +1,20 @@
const { hasSwitch } = process.electronBinding('command_line')
const binding = process.electronBinding('context_bridge')
const { hasSwitch } = process.electronBinding('command_line');
const binding = process.electronBinding('context_bridge');
const contextIsolationEnabled = hasSwitch('context-isolation')
const contextIsolationEnabled = hasSwitch('context-isolation');
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');
};
const contextBridge = {
exposeInMainWorld: (key: string, api: Record<string, any>) => {
checkContextIsolationEnabled()
return binding.exposeAPIInMainWorld(key, api)
checkContextIsolationEnabled();
return binding.exposeAPIInMainWorld(key, api);
},
debugGC: () => binding._debugGCMaps({})
}
};
if (!binding._debugGCMaps) delete contextBridge.debugGC
if (!binding._debugGCMaps) delete contextBridge.debugGC;
export default contextBridge
export default contextBridge;

View file

@ -1,12 +1,12 @@
'use strict'
'use strict';
const CrashReporter = require('@electron/internal/common/crash-reporter')
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils')
const CrashReporter = require('@electron/internal/common/crash-reporter');
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils');
class CrashReporterRenderer extends CrashReporter {
init (options) {
return ipcRendererUtils.invokeSync('ELECTRON_CRASH_REPORTER_INIT', options)
return ipcRendererUtils.invokeSync('ELECTRON_CRASH_REPORTER_INIT', options);
}
}
module.exports = new CrashReporterRenderer()
module.exports = new CrashReporterRenderer();

View file

@ -1,39 +1,39 @@
import { nativeImage } from 'electron'
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'
import { nativeImage } from 'electron';
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
const { hasSwitch } = process.electronBinding('command_line')
const { hasSwitch } = process.electronBinding('command_line');
// |options.types| can't be empty and must be an array
function isValid (options: Electron.SourcesOptions) {
const types = options ? options.types : undefined
return Array.isArray(types)
const types = options ? options.types : undefined;
return Array.isArray(types);
}
const enableStacks = hasSwitch('enable-api-filtering-logging')
const enableStacks = hasSwitch('enable-api-filtering-logging');
function getCurrentStack () {
const target = {}
const target = {};
if (enableStacks) {
Error.captureStackTrace(target, getCurrentStack)
Error.captureStackTrace(target, getCurrentStack);
}
return (target as any).stack
return (target as any).stack;
}
export async function getSources (options: Electron.SourcesOptions) {
if (!isValid(options)) throw new Error('Invalid options')
if (!isValid(options)) throw new Error('Invalid options');
const captureWindow = options.types.includes('window')
const captureScreen = options.types.includes('screen')
const captureWindow = options.types.includes('window');
const captureScreen = options.types.includes('screen');
const { thumbnailSize = { width: 150, height: 150 } } = options
const { fetchWindowIcons = false } = options
const { thumbnailSize = { width: 150, height: 150 } } = options;
const { fetchWindowIcons = false } = options;
const sources = await ipcRendererInternal.invoke<ElectronInternal.GetSourcesResult[]>('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', {
captureWindow,
captureScreen,
thumbnailSize,
fetchWindowIcons
} as ElectronInternal.GetSourcesOptions, getCurrentStack())
} as ElectronInternal.GetSourcesOptions, getCurrentStack());
return sources.map(source => ({
id: source.id,
@ -41,5 +41,5 @@ export async function getSources (options: Electron.SourcesOptions) {
thumbnail: nativeImage.createFromDataURL(source.thumbnail),
display_id: source.display_id,
appIcon: source.appIcon ? nativeImage.createFromDataURL(source.appIcon) : null
}))
}));
}

View file

@ -1,8 +1,8 @@
import { defineProperties } from '@electron/internal/common/define-properties'
import { commonModuleList } from '@electron/internal/common/api/module-list'
import { rendererModuleList } from '@electron/internal/renderer/api/module-list'
import { defineProperties } from '@electron/internal/common/define-properties';
import { commonModuleList } from '@electron/internal/common/api/module-list';
import { rendererModuleList } from '@electron/internal/renderer/api/module-list';
module.exports = {}
module.exports = {};
defineProperties(module.exports, commonModuleList)
defineProperties(module.exports, rendererModuleList)
defineProperties(module.exports, commonModuleList);
defineProperties(module.exports, rendererModuleList);

View file

@ -1,36 +1,36 @@
const { ipc } = process.electronBinding('ipc')
const v8Util = process.electronBinding('v8_util')
const { ipc } = process.electronBinding('ipc');
const v8Util = process.electronBinding('v8_util');
// Created by init.js.
const ipcRenderer = v8Util.getHiddenValue<Electron.IpcRenderer>(global, 'ipc')
const internal = false
const ipcRenderer = v8Util.getHiddenValue<Electron.IpcRenderer>(global, 'ipc');
const internal = false;
ipcRenderer.send = function (channel, ...args) {
return ipc.send(internal, channel, args)
}
return ipc.send(internal, channel, args);
};
ipcRenderer.sendSync = function (channel, ...args) {
return ipc.sendSync(internal, channel, args)[0]
}
return ipc.sendSync(internal, channel, args)[0];
};
ipcRenderer.sendToHost = function (channel, ...args) {
return ipc.sendToHost(channel, args)
}
return ipc.sendToHost(channel, args);
};
ipcRenderer.sendTo = function (webContentsId, channel, ...args) {
return ipc.sendTo(internal, false, webContentsId, channel, args)
}
return ipc.sendTo(internal, false, webContentsId, channel, args);
};
ipcRenderer.invoke = async function (channel, ...args) {
const { error, result } = await ipc.invoke(internal, channel, args)
const { error, result } = await ipc.invoke(internal, channel, args);
if (error) {
throw new Error(`Error invoking remote method '${channel}': ${error}`)
throw new Error(`Error invoking remote method '${channel}': ${error}`);
}
return result
}
return result;
};
ipcRenderer.postMessage = function (channel: string, message: any, transferables: any) {
return ipc.postMessage(channel, message, transferables)
}
return ipc.postMessage(channel, message, transferables);
};
export default ipcRenderer
export default ipcRenderer;

View file

@ -1,7 +1,7 @@
const features = process.electronBinding('features')
const v8Util = process.electronBinding('v8_util')
const features = process.electronBinding('features');
const v8Util = process.electronBinding('v8_util');
const enableRemoteModule = v8Util.getHiddenValue<boolean>(global, 'enableRemoteModule')
const enableRemoteModule = v8Util.getHiddenValue<boolean>(global, 'enableRemoteModule');
// Renderer side modules, please sort alphabetically.
export const rendererModuleList: ElectronInternal.ModuleEntry[] = [
@ -9,12 +9,12 @@ export const rendererModuleList: ElectronInternal.ModuleEntry[] = [
{ name: 'crashReporter', loader: () => require('./crash-reporter') },
{ name: 'ipcRenderer', loader: () => require('./ipc-renderer') },
{ name: 'webFrame', loader: () => require('./web-frame') }
]
];
if (features.isDesktopCapturerEnabled()) {
rendererModuleList.push({ name: 'desktopCapturer', loader: () => require('./desktop-capturer') })
rendererModuleList.push({ name: 'desktopCapturer', loader: () => require('./desktop-capturer') });
}
if (features.isRemoteModuleEnabled() && enableRemoteModule) {
rendererModuleList.push({ name: 'remote', loader: () => require('./remote') })
rendererModuleList.push({ name: 'remote', loader: () => require('./remote') });
}

View file

@ -1,26 +1,26 @@
'use strict'
'use strict';
const v8Util = process.electronBinding('v8_util')
const { hasSwitch } = process.electronBinding('command_line')
const v8Util = process.electronBinding('v8_util');
const { hasSwitch } = process.electronBinding('command_line');
const { CallbacksRegistry } = require('@electron/internal/renderer/remote/callbacks-registry')
const { isPromise, isSerializableObject } = require('@electron/internal/common/type-utils')
const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal')
const { CallbacksRegistry } = require('@electron/internal/renderer/remote/callbacks-registry');
const { isPromise, isSerializableObject } = require('@electron/internal/common/type-utils');
const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal');
const callbacksRegistry = new CallbacksRegistry()
const remoteObjectCache = v8Util.createIDWeakMap()
const callbacksRegistry = new CallbacksRegistry();
const remoteObjectCache = v8Util.createIDWeakMap();
// An unique ID that can represent current context.
const contextId = v8Util.getHiddenValue(global, 'contextId')
const contextId = v8Util.getHiddenValue(global, 'contextId');
// Notify the main process when current context is going to be released.
// Note that when the renderer process is destroyed, the message may not be
// sent, we also listen to the "render-view-deleted" event in the main process
// to guard that situation.
process.on('exit', () => {
const command = 'ELECTRON_BROWSER_CONTEXT_RELEASE'
ipcRendererInternal.send(command, contextId)
})
const command = 'ELECTRON_BROWSER_CONTEXT_RELEASE';
ipcRendererInternal.send(command, contextId);
});
// Convert the arguments object into an array of meta data.
function wrapArgs (args, visited = new Set()) {
@ -30,182 +30,182 @@ function wrapArgs (args, visited = new Set()) {
return {
type: 'value',
value: null
}
};
}
if (Array.isArray(value)) {
visited.add(value)
visited.add(value);
const meta = {
type: 'array',
value: wrapArgs(value, visited)
}
visited.delete(value)
return meta
};
visited.delete(value);
return meta;
} else if (value instanceof Buffer) {
return {
type: 'buffer',
value
}
};
} else if (isSerializableObject(value)) {
return {
type: 'value',
value
}
};
} else if (typeof value === 'object') {
if (isPromise(value)) {
return {
type: 'promise',
then: valueToMeta(function (onFulfilled, onRejected) {
value.then(onFulfilled, onRejected)
value.then(onFulfilled, onRejected);
})
}
};
} else if (v8Util.getHiddenValue(value, 'atomId')) {
return {
type: 'remote-object',
id: v8Util.getHiddenValue(value, 'atomId')
}
};
}
const meta = {
type: 'object',
name: value.constructor ? value.constructor.name : '',
members: []
}
visited.add(value)
};
visited.add(value);
for (const prop in value) { // eslint-disable-line guard-for-in
meta.members.push({
name: prop,
value: valueToMeta(value[prop])
})
});
}
visited.delete(value)
return meta
visited.delete(value);
return meta;
} else if (typeof value === 'function' && v8Util.getHiddenValue(value, 'returnValue')) {
return {
type: 'function-with-return-value',
value: valueToMeta(value())
}
};
} else if (typeof value === 'function') {
return {
type: 'function',
id: callbacksRegistry.add(value),
location: v8Util.getHiddenValue(value, 'location'),
length: value.length
}
};
} else {
return {
type: 'value',
value
}
};
}
}
return args.map(valueToMeta)
};
return args.map(valueToMeta);
}
// Populate object's members from descriptors.
// The |ref| will be kept referenced by |members|.
// This matches |getObjectMemebers| in rpc-server.
function setObjectMembers (ref, object, metaId, members) {
if (!Array.isArray(members)) return
if (!Array.isArray(members)) return;
for (const member of members) {
if (Object.prototype.hasOwnProperty.call(object, member.name)) continue
if (Object.prototype.hasOwnProperty.call(object, member.name)) continue;
const descriptor = { enumerable: member.enumerable }
const descriptor = { enumerable: member.enumerable };
if (member.type === 'method') {
const remoteMemberFunction = function (...args) {
let command
let command;
if (this && this.constructor === remoteMemberFunction) {
command = 'ELECTRON_BROWSER_MEMBER_CONSTRUCTOR'
command = 'ELECTRON_BROWSER_MEMBER_CONSTRUCTOR';
} else {
command = 'ELECTRON_BROWSER_MEMBER_CALL'
command = 'ELECTRON_BROWSER_MEMBER_CALL';
}
const ret = ipcRendererInternal.sendSync(command, contextId, metaId, member.name, wrapArgs(args))
return metaToValue(ret)
}
const ret = ipcRendererInternal.sendSync(command, contextId, metaId, member.name, wrapArgs(args));
return metaToValue(ret);
};
let descriptorFunction = proxyFunctionProperties(remoteMemberFunction, metaId, member.name)
let descriptorFunction = proxyFunctionProperties(remoteMemberFunction, metaId, member.name);
descriptor.get = () => {
descriptorFunction.ref = ref // The member should reference its object.
return descriptorFunction
}
descriptorFunction.ref = ref; // The member should reference its object.
return descriptorFunction;
};
// Enable monkey-patch the method
descriptor.set = (value) => {
descriptorFunction = value
return value
}
descriptor.configurable = true
descriptorFunction = value;
return value;
};
descriptor.configurable = true;
} else if (member.type === 'get') {
descriptor.get = () => {
const command = 'ELECTRON_BROWSER_MEMBER_GET'
const meta = ipcRendererInternal.sendSync(command, contextId, metaId, member.name)
return metaToValue(meta)
}
const command = 'ELECTRON_BROWSER_MEMBER_GET';
const meta = ipcRendererInternal.sendSync(command, contextId, metaId, member.name);
return metaToValue(meta);
};
if (member.writable) {
descriptor.set = (value) => {
const args = wrapArgs([value])
const command = 'ELECTRON_BROWSER_MEMBER_SET'
const meta = ipcRendererInternal.sendSync(command, contextId, metaId, member.name, args)
if (meta != null) metaToValue(meta)
return value
}
const args = wrapArgs([value]);
const command = 'ELECTRON_BROWSER_MEMBER_SET';
const meta = ipcRendererInternal.sendSync(command, contextId, metaId, member.name, args);
if (meta != null) metaToValue(meta);
return value;
};
}
}
Object.defineProperty(object, member.name, descriptor)
Object.defineProperty(object, member.name, descriptor);
}
}
// Populate object's prototype from descriptor.
// This matches |getObjectPrototype| in rpc-server.
function setObjectPrototype (ref, object, metaId, descriptor) {
if (descriptor === null) return
const proto = {}
setObjectMembers(ref, proto, metaId, descriptor.members)
setObjectPrototype(ref, proto, metaId, descriptor.proto)
Object.setPrototypeOf(object, proto)
if (descriptor === null) return;
const proto = {};
setObjectMembers(ref, proto, metaId, descriptor.members);
setObjectPrototype(ref, proto, metaId, descriptor.proto);
Object.setPrototypeOf(object, proto);
}
// Wrap function in Proxy for accessing remote properties
function proxyFunctionProperties (remoteMemberFunction, metaId, name) {
let loaded = false
let loaded = false;
// Lazily load function properties
const loadRemoteProperties = () => {
if (loaded) return
loaded = true
const command = 'ELECTRON_BROWSER_MEMBER_GET'
const meta = ipcRendererInternal.sendSync(command, contextId, metaId, name)
setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members)
}
if (loaded) return;
loaded = true;
const command = 'ELECTRON_BROWSER_MEMBER_GET';
const meta = ipcRendererInternal.sendSync(command, contextId, metaId, name);
setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members);
};
return new Proxy(remoteMemberFunction, {
set: (target, property, value, receiver) => {
if (property !== 'ref') loadRemoteProperties()
target[property] = value
return true
if (property !== 'ref') loadRemoteProperties();
target[property] = value;
return true;
},
get: (target, property, receiver) => {
if (!Object.prototype.hasOwnProperty.call(target, property)) loadRemoteProperties()
const value = target[property]
if (!Object.prototype.hasOwnProperty.call(target, property)) loadRemoteProperties();
const value = target[property];
if (property === 'toString' && typeof value === 'function') {
return value.bind(target)
return value.bind(target);
}
return value
return value;
},
ownKeys: (target) => {
loadRemoteProperties()
return Object.getOwnPropertyNames(target)
loadRemoteProperties();
return Object.getOwnPropertyNames(target);
},
getOwnPropertyDescriptor: (target, property) => {
const descriptor = Object.getOwnPropertyDescriptor(target, property)
if (descriptor) return descriptor
loadRemoteProperties()
return Object.getOwnPropertyDescriptor(target, property)
const descriptor = Object.getOwnPropertyDescriptor(target, property);
if (descriptor) return descriptor;
loadRemoteProperties();
return Object.getOwnPropertyDescriptor(target, property);
}
})
});
}
// Convert meta data from browser into real value.
@ -216,143 +216,143 @@ function metaToValue (meta) {
buffer: () => Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength),
promise: () => Promise.resolve({ then: metaToValue(meta.then) }),
error: () => metaToError(meta),
exception: () => { throw metaToError(meta.value) }
}
exception: () => { throw metaToError(meta.value); }
};
if (Object.prototype.hasOwnProperty.call(types, meta.type)) {
return types[meta.type]()
return types[meta.type]();
} else {
let ret
let ret;
if (remoteObjectCache.has(meta.id)) {
v8Util.addRemoteObjectRef(contextId, meta.id)
return remoteObjectCache.get(meta.id)
v8Util.addRemoteObjectRef(contextId, meta.id);
return remoteObjectCache.get(meta.id);
}
// A shadow class to represent the remote function object.
if (meta.type === 'function') {
const remoteFunction = function (...args) {
let command
let command;
if (this && this.constructor === remoteFunction) {
command = 'ELECTRON_BROWSER_CONSTRUCTOR'
command = 'ELECTRON_BROWSER_CONSTRUCTOR';
} else {
command = 'ELECTRON_BROWSER_FUNCTION_CALL'
command = 'ELECTRON_BROWSER_FUNCTION_CALL';
}
const obj = ipcRendererInternal.sendSync(command, contextId, meta.id, wrapArgs(args))
return metaToValue(obj)
}
ret = remoteFunction
const obj = ipcRendererInternal.sendSync(command, contextId, meta.id, wrapArgs(args));
return metaToValue(obj);
};
ret = remoteFunction;
} else {
ret = {}
ret = {};
}
setObjectMembers(ret, ret, meta.id, meta.members)
setObjectPrototype(ret, ret, meta.id, meta.proto)
Object.defineProperty(ret.constructor, 'name', { value: meta.name })
setObjectMembers(ret, ret, meta.id, meta.members);
setObjectPrototype(ret, ret, meta.id, meta.proto);
Object.defineProperty(ret.constructor, 'name', { value: meta.name });
// Track delegate obj's lifetime & tell browser to clean up when object is GCed.
v8Util.setRemoteObjectFreer(ret, contextId, meta.id)
v8Util.setHiddenValue(ret, 'atomId', meta.id)
v8Util.addRemoteObjectRef(contextId, meta.id)
remoteObjectCache.set(meta.id, ret)
return ret
v8Util.setRemoteObjectFreer(ret, contextId, meta.id);
v8Util.setHiddenValue(ret, 'atomId', meta.id);
v8Util.addRemoteObjectRef(contextId, meta.id);
remoteObjectCache.set(meta.id, ret);
return ret;
}
}
function metaToError (meta) {
const obj = meta.value
const obj = meta.value;
for (const { name, value } of meta.members) {
obj[name] = metaToValue(value)
obj[name] = metaToValue(value);
}
return obj
return obj;
}
function handleMessage (channel, handler) {
ipcRendererInternal.on(channel, (event, passedContextId, id, ...args) => {
if (passedContextId === contextId) {
handler(id, ...args)
handler(id, ...args);
} else {
// Message sent to an un-exist context, notify the error to main process.
ipcRendererInternal.send('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', contextId, passedContextId, id)
ipcRendererInternal.send('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', contextId, passedContextId, id);
}
})
});
}
const enableStacks = hasSwitch('enable-api-filtering-logging')
const enableStacks = hasSwitch('enable-api-filtering-logging');
function getCurrentStack () {
const target = {}
const target = {};
if (enableStacks) {
Error.captureStackTrace(target, getCurrentStack)
Error.captureStackTrace(target, getCurrentStack);
}
return target.stack
return target.stack;
}
// Browser calls a callback in renderer.
handleMessage('ELECTRON_RENDERER_CALLBACK', (id, args) => {
callbacksRegistry.apply(id, metaToValue(args))
})
callbacksRegistry.apply(id, metaToValue(args));
});
// A callback in browser is released.
handleMessage('ELECTRON_RENDERER_RELEASE_CALLBACK', (id) => {
callbacksRegistry.remove(id)
})
callbacksRegistry.remove(id);
});
exports.require = (module) => {
const command = 'ELECTRON_BROWSER_REQUIRE'
const meta = ipcRendererInternal.sendSync(command, contextId, module, getCurrentStack())
return metaToValue(meta)
}
const command = 'ELECTRON_BROWSER_REQUIRE';
const meta = ipcRendererInternal.sendSync(command, contextId, module, getCurrentStack());
return metaToValue(meta);
};
// Alias to remote.require('electron').xxx.
exports.getBuiltin = (module) => {
const command = 'ELECTRON_BROWSER_GET_BUILTIN'
const meta = ipcRendererInternal.sendSync(command, contextId, module, getCurrentStack())
return metaToValue(meta)
}
const command = 'ELECTRON_BROWSER_GET_BUILTIN';
const meta = ipcRendererInternal.sendSync(command, contextId, module, getCurrentStack());
return metaToValue(meta);
};
exports.getCurrentWindow = () => {
const command = 'ELECTRON_BROWSER_CURRENT_WINDOW'
const meta = ipcRendererInternal.sendSync(command, contextId, getCurrentStack())
return metaToValue(meta)
}
const command = 'ELECTRON_BROWSER_CURRENT_WINDOW';
const meta = ipcRendererInternal.sendSync(command, contextId, getCurrentStack());
return metaToValue(meta);
};
// Get current WebContents object.
exports.getCurrentWebContents = () => {
const command = 'ELECTRON_BROWSER_CURRENT_WEB_CONTENTS'
const meta = ipcRendererInternal.sendSync(command, contextId, getCurrentStack())
return metaToValue(meta)
}
const command = 'ELECTRON_BROWSER_CURRENT_WEB_CONTENTS';
const meta = ipcRendererInternal.sendSync(command, contextId, getCurrentStack());
return metaToValue(meta);
};
// Get a global object in browser.
exports.getGlobal = (name) => {
const command = 'ELECTRON_BROWSER_GLOBAL'
const meta = ipcRendererInternal.sendSync(command, contextId, name, getCurrentStack())
return metaToValue(meta)
}
const command = 'ELECTRON_BROWSER_GLOBAL';
const meta = ipcRendererInternal.sendSync(command, contextId, name, getCurrentStack());
return metaToValue(meta);
};
// Get the process object in browser.
Object.defineProperty(exports, 'process', {
get: () => exports.getGlobal('process')
})
});
// Create a function that will return the specified value when called in browser.
exports.createFunctionWithReturnValue = (returnValue) => {
const func = () => returnValue
v8Util.setHiddenValue(func, 'returnValue', true)
return func
}
const func = () => returnValue;
v8Util.setHiddenValue(func, 'returnValue', true);
return func;
};
const addBuiltinProperty = (name) => {
Object.defineProperty(exports, name, {
get: () => exports.getBuiltin(name)
})
}
});
};
const { commonModuleList } = require('@electron/internal/common/api/module-list')
const browserModules = commonModuleList.concat(require('@electron/internal/browser/api/module-keys'))
const { commonModuleList } = require('@electron/internal/common/api/module-list');
const browserModules = commonModuleList.concat(require('@electron/internal/browser/api/module-keys'));
// And add a helper receiver for each one.
browserModules
.filter((m) => !m.private)
.map((m) => m.name)
.forEach(addBuiltinProperty)
.forEach(addBuiltinProperty);

View file

@ -1,49 +1,49 @@
import { EventEmitter } from 'events'
import { EventEmitter } from 'events';
const binding = process.electronBinding('web_frame')
const binding = process.electronBinding('web_frame');
class WebFrame extends EventEmitter {
constructor (public context: Window) {
super()
super();
// Lots of webview would subscribe to webFrame's events.
this.setMaxListeners(0)
this.setMaxListeners(0);
}
findFrameByRoutingId (...args: Array<any>) {
return getWebFrame(binding._findFrameByRoutingId(this.context, ...args))
return getWebFrame(binding._findFrameByRoutingId(this.context, ...args));
}
getFrameForSelector (...args: Array<any>) {
return getWebFrame(binding._getFrameForSelector(this.context, ...args))
return getWebFrame(binding._getFrameForSelector(this.context, ...args));
}
findFrameByName (...args: Array<any>) {
return getWebFrame(binding._findFrameByName(this.context, ...args))
return getWebFrame(binding._findFrameByName(this.context, ...args));
}
get opener () {
return getWebFrame(binding._getOpener(this.context))
return getWebFrame(binding._getOpener(this.context));
}
get parent () {
return getWebFrame(binding._getParent(this.context))
return getWebFrame(binding._getParent(this.context));
}
get top () {
return getWebFrame(binding._getTop(this.context))
return getWebFrame(binding._getTop(this.context));
}
get firstChild () {
return getWebFrame(binding._getFirstChild(this.context))
return getWebFrame(binding._getFirstChild(this.context));
}
get nextSibling () {
return getWebFrame(binding._getNextSibling(this.context))
return getWebFrame(binding._getNextSibling(this.context));
}
get routingId () {
return binding._getRoutingId(this.context)
return binding._getRoutingId(this.context);
}
}
@ -53,17 +53,17 @@ for (const name in binding) {
// 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[name](this.context, ...args)
}
return binding[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
return context ? new WebFrame(context) : null;
}
const _webFrame = new WebFrame(window)
const _webFrame = new WebFrame(window);
export default _webFrame
export default _webFrame;

View file

@ -1,14 +1,14 @@
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils'
import * as url from 'url'
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
import * as url from 'url';
import { Event } from '@electron/internal/renderer/extensions/event'
import { Event } from '@electron/internal/renderer/extensions/event';
class Tab {
public id: number
constructor (tabId: number) {
this.id = tabId
this.id = tabId;
}
}
@ -18,9 +18,9 @@ class MessageSender {
public url: string
constructor (tabId: number, extensionId: string) {
this.tab = tabId ? new Tab(tabId) : null
this.id = extensionId
this.url = `chrome-extension://${extensionId}`
this.tab = tabId ? new Tab(tabId) : null;
this.id = extensionId;
this.url = `chrome-extension://${extensionId}`;
}
}
@ -31,69 +31,69 @@ class Port {
public sender: MessageSender
constructor (public tabId: number, public portId: number, extensionId: string, public name: string) {
this.onDisconnect = new Event()
this.onMessage = new Event()
this.sender = new MessageSender(tabId, extensionId)
this.onDisconnect = new Event();
this.onMessage = new Event();
this.sender = new MessageSender(tabId, extensionId);
ipcRendererInternal.once(`CHROME_PORT_DISCONNECT_${portId}`, () => {
this._onDisconnect()
})
this._onDisconnect();
});
ipcRendererInternal.on(`CHROME_PORT_POSTMESSAGE_${portId}`, (
_event: Electron.Event, message: any
) => {
const sendResponse = function () { console.error('sendResponse is not implemented') }
this.onMessage.emit(JSON.parse(message), this.sender, sendResponse)
})
const sendResponse = function () { console.error('sendResponse is not implemented'); };
this.onMessage.emit(JSON.parse(message), this.sender, sendResponse);
});
}
disconnect () {
if (this.disconnected) return
if (this.disconnected) return;
ipcRendererInternal.sendToAll(this.tabId, `CHROME_PORT_DISCONNECT_${this.portId}`)
this._onDisconnect()
ipcRendererInternal.sendToAll(this.tabId, `CHROME_PORT_DISCONNECT_${this.portId}`);
this._onDisconnect();
}
postMessage (message: any) {
ipcRendererInternal.sendToAll(this.tabId, `CHROME_PORT_POSTMESSAGE_${this.portId}`, JSON.stringify(message))
ipcRendererInternal.sendToAll(this.tabId, `CHROME_PORT_POSTMESSAGE_${this.portId}`, JSON.stringify(message));
}
_onDisconnect () {
this.disconnected = true
ipcRendererInternal.removeAllListeners(`CHROME_PORT_POSTMESSAGE_${this.portId}`)
this.onDisconnect.emit()
this.disconnected = true;
ipcRendererInternal.removeAllListeners(`CHROME_PORT_POSTMESSAGE_${this.portId}`);
this.onDisconnect.emit();
}
}
// Inject chrome API to the |context|
export function injectTo (extensionId: string, context: any) {
if (process.electronBinding('features').isExtensionsEnabled()) {
throw new Error('Attempted to load JS chrome-extension polyfill with //extensions support enabled')
throw new Error('Attempted to load JS chrome-extension polyfill with //extensions support enabled');
}
const chrome = context.chrome = context.chrome || {}
const chrome = context.chrome = context.chrome || {};
ipcRendererInternal.on(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, (
_event: Electron.Event, tabId: number, portId: number, connectInfo: { name: string }
) => {
chrome.runtime.onConnect.emit(new Port(tabId, portId, extensionId, connectInfo.name))
})
chrome.runtime.onConnect.emit(new Port(tabId, portId, extensionId, connectInfo.name));
});
ipcRendererUtils.handle(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, (
_event: Electron.Event, tabId: number, message: string
) => {
return new Promise(resolve => {
chrome.runtime.onMessage.emit(message, new MessageSender(tabId, extensionId), resolve)
})
})
chrome.runtime.onMessage.emit(message, new MessageSender(tabId, extensionId), resolve);
});
});
ipcRendererInternal.on('CHROME_TABS_ONCREATED', (_event: Electron.Event, tabId: number) => {
chrome.tabs.onCreated.emit(new Tab(tabId))
})
chrome.tabs.onCreated.emit(new Tab(tabId));
});
ipcRendererInternal.on('CHROME_TABS_ONREMOVED', (_event: Electron.Event, tabId: number) => {
chrome.tabs.onRemoved.emit(tabId)
})
chrome.tabs.onRemoved.emit(tabId);
});
chrome.runtime = {
id: extensionId,
@ -105,69 +105,69 @@ export function injectTo (extensionId: string, context: any) {
slashes: true,
hostname: extensionId,
pathname: path
})
});
},
// https://developer.chrome.com/extensions/runtime#method-getManifest
getManifest: function () {
const manifest = ipcRendererUtils.invokeSync('CHROME_EXTENSION_MANIFEST', extensionId)
return manifest
const manifest = ipcRendererUtils.invokeSync('CHROME_EXTENSION_MANIFEST', extensionId);
return manifest;
},
// https://developer.chrome.com/extensions/runtime#method-connect
connect (...args: Array<any>) {
// Parse the optional args.
let targetExtensionId = extensionId
let connectInfo = { name: '' }
let targetExtensionId = extensionId;
let connectInfo = { name: '' };
if (args.length === 1) {
if (typeof args[0] === 'string') {
targetExtensionId = args[0]
targetExtensionId = args[0];
} else {
connectInfo = args[0]
connectInfo = args[0];
}
} else if (args.length === 2) {
[targetExtensionId, connectInfo] = args
[targetExtensionId, connectInfo] = args;
}
const { tabId, portId } = ipcRendererUtils.invokeSync('CHROME_RUNTIME_CONNECT', targetExtensionId, connectInfo)
return new Port(tabId, portId, extensionId, connectInfo.name)
const { tabId, portId } = ipcRendererUtils.invokeSync('CHROME_RUNTIME_CONNECT', targetExtensionId, connectInfo);
return new Port(tabId, portId, extensionId, connectInfo.name);
},
// https://developer.chrome.com/extensions/runtime#method-sendMessage
sendMessage (...args: Array<any>) {
// Parse the optional args.
const targetExtensionId = extensionId
let message: string
let options: Object | undefined
let responseCallback: Chrome.Tabs.SendMessageCallback = () => {}
const targetExtensionId = extensionId;
let message: string;
let options: Object | undefined;
let responseCallback: Chrome.Tabs.SendMessageCallback = () => {};
if (typeof args[args.length - 1] === 'function') {
responseCallback = args.pop()
responseCallback = args.pop();
}
if (args.length === 1) {
[message] = args
[message] = args;
} else if (args.length === 2) {
if (typeof args[0] === 'string') {
[extensionId, message] = args
[extensionId, message] = args;
} else {
[message, options] = args
[message, options] = args;
}
} else {
[extensionId, message, options] = args
[extensionId, message, options] = args;
}
if (options) {
console.error('options are not supported')
console.error('options are not supported');
}
ipcRendererInternal.invoke('CHROME_RUNTIME_SEND_MESSAGE', targetExtensionId, message).then(responseCallback)
ipcRendererInternal.invoke('CHROME_RUNTIME_SEND_MESSAGE', targetExtensionId, message).then(responseCallback);
},
onConnect: new Event(),
onMessage: new Event(),
onInstalled: new Event()
}
};
chrome.tabs = {
// https://developer.chrome.com/extensions/tabs#method-executeScript
@ -177,7 +177,7 @@ export function injectTo (extensionId: string, context: any) {
resultCallback: Chrome.Tabs.ExecuteScriptCallback = () => {}
) {
ipcRendererInternal.invoke('CHROME_TABS_EXECUTE_SCRIPT', tabId, extensionId, details)
.then((result: any) => resultCallback([result]))
.then((result: any) => resultCallback([result]));
},
// https://developer.chrome.com/extensions/tabs#method-sendMessage
@ -187,13 +187,13 @@ export function injectTo (extensionId: string, context: any) {
_options: Chrome.Tabs.SendMessageDetails,
responseCallback: Chrome.Tabs.SendMessageCallback = () => {}
) {
ipcRendererInternal.invoke('CHROME_TABS_SEND_MESSAGE', tabId, extensionId, message).then(responseCallback)
ipcRendererInternal.invoke('CHROME_TABS_SEND_MESSAGE', tabId, extensionId, message).then(responseCallback);
},
onUpdated: new Event(),
onCreated: new Event(),
onRemoved: new Event()
}
};
chrome.extension = {
getURL: chrome.runtime.getURL,
@ -201,9 +201,9 @@ export function injectTo (extensionId: string, context: any) {
onConnect: chrome.runtime.onConnect,
sendMessage: chrome.runtime.sendMessage,
onMessage: chrome.runtime.onMessage
}
};
chrome.storage = require('@electron/internal/renderer/extensions/storage').setup(extensionId)
chrome.storage = require('@electron/internal/renderer/extensions/storage').setup(extensionId);
chrome.pageAction = {
show () {},
@ -213,14 +213,14 @@ export function injectTo (extensionId: string, context: any) {
setIcon () {},
setPopup () {},
getPopup () {}
}
};
chrome.i18n = require('@electron/internal/renderer/extensions/i18n').setup(extensionId)
chrome.webNavigation = require('@electron/internal/renderer/extensions/web-navigation').setup()
chrome.i18n = require('@electron/internal/renderer/extensions/i18n').setup(extensionId);
chrome.webNavigation = require('@electron/internal/renderer/extensions/web-navigation').setup();
// Electron has no concept of a browserAction but we should stub these APIs for compatibility
chrome.browserAction = {
setIcon () {},
setPopup () {}
}
};
}

View file

@ -1,8 +1,8 @@
import { webFrame } from 'electron'
import { webFrame } from 'electron';
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils'
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
const v8Util = process.electronBinding('v8_util')
const v8Util = process.electronBinding('v8_util');
const IsolatedWorldIDs = {
/**
@ -10,93 +10,93 @@ const IsolatedWorldIDs = {
* electron_render_frame_observer.h
*/
ISOLATED_WORLD_EXTENSIONS: 1 << 20
}
};
let isolatedWorldIds = IsolatedWorldIDs.ISOLATED_WORLD_EXTENSIONS
const extensionWorldId: {[key: string]: number | undefined} = {}
let isolatedWorldIds = IsolatedWorldIDs.ISOLATED_WORLD_EXTENSIONS;
const extensionWorldId: {[key: string]: number | undefined} = {};
// https://cs.chromium.org/chromium/src/extensions/renderer/script_injection.cc?type=cs&sq=package:chromium&g=0&l=52
const getIsolatedWorldIdForInstance = () => {
// TODO(samuelmaddock): allocate and cleanup IDs
return isolatedWorldIds++
}
return isolatedWorldIds++;
};
const escapePattern = function (pattern: string) {
return pattern.replace(/[\\^$+?.()|[\]{}]/g, '\\$&')
}
return pattern.replace(/[\\^$+?.()|[\]{}]/g, '\\$&');
};
// Check whether pattern matches.
// https://developer.chrome.com/extensions/match_patterns
const matchesPattern = function (pattern: string) {
if (pattern === '<all_urls>') return true
const regexp = new RegExp(`^${pattern.split('*').map(escapePattern).join('.*')}$`)
const url = `${location.protocol}//${location.host}${location.pathname}`
return url.match(regexp)
}
if (pattern === '<all_urls>') return true;
const regexp = new RegExp(`^${pattern.split('*').map(escapePattern).join('.*')}$`);
const url = `${location.protocol}//${location.host}${location.pathname}`;
return url.match(regexp);
};
// Run the code with chrome API integrated.
const runContentScript = function (this: any, extensionId: string, url: string, code: string) {
// Assign unique world ID to each extension
const worldId = extensionWorldId[extensionId] ||
(extensionWorldId[extensionId] = getIsolatedWorldIdForInstance())
(extensionWorldId[extensionId] = getIsolatedWorldIdForInstance());
// store extension ID for content script to read in isolated world
v8Util.setHiddenValue(global, `extension-${worldId}`, extensionId)
v8Util.setHiddenValue(global, `extension-${worldId}`, extensionId);
webFrame.setIsolatedWorldInfo(worldId, {
name: `${extensionId} [${worldId}]`
// TODO(samuelmaddock): read `content_security_policy` from extension manifest
// csp: manifest.content_security_policy,
})
});
const sources = [{ code, url }]
return webFrame.executeJavaScriptInIsolatedWorld(worldId, sources)
}
const sources = [{ code, url }];
return webFrame.executeJavaScriptInIsolatedWorld(worldId, sources);
};
const runAllContentScript = function (scripts: Array<Electron.InjectionBase>, extensionId: string) {
for (const { url, code } of scripts) {
runContentScript.call(window, extensionId, url, code)
runContentScript.call(window, extensionId, url, code);
}
}
};
const runStylesheet = function (this: any, url: string, code: string) {
webFrame.insertCSS(code)
}
webFrame.insertCSS(code);
};
const runAllStylesheet = function (css: Array<Electron.InjectionBase>) {
for (const { url, code } of css) {
runStylesheet.call(window, url, code)
runStylesheet.call(window, url, code);
}
}
};
// Run injected scripts.
// https://developer.chrome.com/extensions/content_scripts
const injectContentScript = function (extensionId: string, script: Electron.ContentScript) {
if (!process.isMainFrame && !script.allFrames) return
if (!script.matches.some(matchesPattern)) return
if (!process.isMainFrame && !script.allFrames) return;
if (!script.matches.some(matchesPattern)) return;
if (script.js) {
const fire = runAllContentScript.bind(window, script.js, extensionId)
const fire = runAllContentScript.bind(window, script.js, extensionId);
if (script.runAt === 'document_start') {
process.once('document-start', fire)
process.once('document-start', fire);
} else if (script.runAt === 'document_end') {
process.once('document-end', fire)
process.once('document-end', fire);
} else {
document.addEventListener('DOMContentLoaded', fire)
document.addEventListener('DOMContentLoaded', fire);
}
}
if (script.css) {
const fire = runAllStylesheet.bind(window, script.css)
const fire = runAllStylesheet.bind(window, script.css);
if (script.runAt === 'document_start') {
process.once('document-start', fire)
process.once('document-start', fire);
} else if (script.runAt === 'document_end') {
process.once('document-end', fire)
process.once('document-end', fire);
} else {
document.addEventListener('DOMContentLoaded', fire)
document.addEventListener('DOMContentLoaded', fire);
}
}
}
};
// Handle the request of chrome.tabs.executeJavaScript.
ipcRendererUtils.handle('CHROME_TABS_EXECUTE_SCRIPT', function (
@ -105,15 +105,15 @@ ipcRendererUtils.handle('CHROME_TABS_EXECUTE_SCRIPT', function (
url: string,
code: string
) {
return runContentScript.call(window, extensionId, url, code)
})
return runContentScript.call(window, extensionId, url, code);
});
module.exports = (entries: Electron.ContentScriptEntry[]) => {
for (const entry of entries) {
if (entry.contentScripts) {
for (const script of entry.contentScripts) {
injectContentScript(entry.extensionId, script)
injectContentScript(entry.extensionId, script);
}
}
}
}
};

View file

@ -2,19 +2,19 @@ export class Event {
private listeners: Function[] = []
addListener (callback: Function) {
this.listeners.push(callback)
this.listeners.push(callback);
}
removeListener (callback: Function) {
const index = this.listeners.indexOf(callback)
const index = this.listeners.indexOf(callback);
if (index !== -1) {
this.listeners.splice(index, 1)
this.listeners.splice(index, 1);
}
}
emit (...args: any[]) {
for (const listener of this.listeners) {
listener(...args)
listener(...args);
}
}
}

View file

@ -4,7 +4,7 @@
// Does not implement predefined messages:
// https://developer.chrome.com/extensions/i18n#overview-predefined
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils'
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
interface Placeholder {
content: string;
@ -13,48 +13,48 @@ interface Placeholder {
const getMessages = (extensionId: number) => {
try {
const data = ipcRendererUtils.invokeSync<string>('CHROME_GET_MESSAGES', extensionId)
return JSON.parse(data) || {}
const data = ipcRendererUtils.invokeSync<string>('CHROME_GET_MESSAGES', extensionId);
return JSON.parse(data) || {};
} catch {
return {}
return {};
}
}
};
const replaceNumberedSubstitutions = (message: string, substitutions: string[]) => {
return message.replace(/\$(\d+)/, (_, number) => {
const index = parseInt(number, 10) - 1
return substitutions[index] || ''
})
}
const index = parseInt(number, 10) - 1;
return substitutions[index] || '';
});
};
const replacePlaceholders = (message: string, placeholders: Record<string, Placeholder>, substitutions: string[] | string) => {
if (typeof substitutions === 'string') substitutions = [substitutions]
if (!Array.isArray(substitutions)) substitutions = []
if (typeof substitutions === 'string') substitutions = [substitutions];
if (!Array.isArray(substitutions)) substitutions = [];
if (placeholders) {
Object.keys(placeholders).forEach((name: string) => {
let { content } = placeholders[name]
const substitutionsArray = Array.isArray(substitutions) ? substitutions : []
content = replaceNumberedSubstitutions(content, substitutionsArray)
message = message.replace(new RegExp(`\\$${name}\\$`, 'gi'), content)
})
let { content } = placeholders[name];
const substitutionsArray = Array.isArray(substitutions) ? substitutions : [];
content = replaceNumberedSubstitutions(content, substitutionsArray);
message = message.replace(new RegExp(`\\$${name}\\$`, 'gi'), content);
});
}
return replaceNumberedSubstitutions(message, substitutions)
}
return replaceNumberedSubstitutions(message, substitutions);
};
const getMessage = (extensionId: number, messageName: string, substitutions: string[]) => {
const messages = getMessages(extensionId)
const messages = getMessages(extensionId);
if (Object.prototype.hasOwnProperty.call(messages, messageName)) {
const { message, placeholders } = messages[messageName]
return replacePlaceholders(message, placeholders, substitutions)
const { message, placeholders } = messages[messageName];
return replacePlaceholders(message, placeholders, substitutions);
}
}
};
exports.setup = (extensionId: number) => {
return {
getMessage (messageName: string, substitutions: string[]) {
return getMessage(extensionId, messageName, substitutions)
return getMessage(extensionId, messageName, substitutions);
}
}
}
};
};

View file

@ -1,86 +1,86 @@
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
const getStorage = (storageType: string, extensionId: number, callback: Function) => {
if (typeof callback !== 'function') throw new TypeError('No callback provided')
if (typeof callback !== 'function') throw new TypeError('No callback provided');
ipcRendererInternal.invoke<string>('CHROME_STORAGE_READ', storageType, extensionId)
.then(data => {
if (data !== null) {
callback(JSON.parse(data))
callback(JSON.parse(data));
} else {
// Disabled due to false positive in StandardJS
// eslint-disable-next-line standard/no-callback-literal
callback({})
callback({});
}
})
}
});
};
const setStorage = (storageType: string, extensionId: number, storage: Record<string, any>, callback: Function) => {
const json = JSON.stringify(storage)
const json = JSON.stringify(storage);
ipcRendererInternal.invoke('CHROME_STORAGE_WRITE', storageType, extensionId, json)
.then(() => {
if (callback) callback()
})
}
if (callback) callback();
});
};
const getStorageManager = (storageType: string, extensionId: number) => {
return {
get (keys: string[], callback: Function) {
getStorage(storageType, extensionId, (storage: Record<string, any>) => {
if (keys == null) return callback(storage)
if (keys == null) return callback(storage);
let defaults: Record<string, any> = {}
let defaults: Record<string, any> = {};
switch (typeof keys) {
case 'string':
keys = [keys]
break
keys = [keys];
break;
case 'object':
if (!Array.isArray(keys)) {
defaults = keys
keys = Object.keys(keys)
defaults = keys;
keys = Object.keys(keys);
}
break
break;
}
// Disabled due to false positive in StandardJS
// eslint-disable-next-line standard/no-callback-literal
if (keys.length === 0) return callback({})
if (keys.length === 0) return callback({});
const items: Record<string, any> = {}
const items: Record<string, any> = {};
keys.forEach((key: string) => {
let value = storage[key]
if (value == null) value = defaults[key]
items[key] = value
})
callback(items)
})
let value = storage[key];
if (value == null) value = defaults[key];
items[key] = value;
});
callback(items);
});
},
set (items: Record<string, any>, callback: Function) {
getStorage(storageType, extensionId, (storage: Record<string, any>) => {
Object.keys(items).forEach(name => { storage[name] = items[name] })
setStorage(storageType, extensionId, storage, callback)
})
Object.keys(items).forEach(name => { storage[name] = items[name]; });
setStorage(storageType, extensionId, storage, callback);
});
},
remove (keys: string[], callback: Function) {
getStorage(storageType, extensionId, (storage: Record<string, any>) => {
if (!Array.isArray(keys)) keys = [keys]
if (!Array.isArray(keys)) keys = [keys];
keys.forEach((key: string) => {
delete storage[key]
})
delete storage[key];
});
setStorage(storageType, extensionId, storage, callback)
})
setStorage(storageType, extensionId, storage, callback);
});
},
clear (callback: Function) {
setStorage(storageType, extensionId, {}, callback)
setStorage(storageType, extensionId, {}, callback);
}
}
}
};
};
export const setup = (extensionId: number) => ({
sync: getStorageManager('sync', extensionId),
local: getStorageManager('local', extensionId)
})
});

View file

@ -1,5 +1,5 @@
import { Event } from '@electron/internal/renderer/extensions/event'
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'
import { Event } from '@electron/internal/renderer/extensions/event';
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
class WebNavigation {
private onBeforeNavigate = new Event()
@ -7,13 +7,13 @@ class WebNavigation {
constructor () {
ipcRendererInternal.on('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', (event: Electron.IpcRendererEvent, details: any) => {
this.onBeforeNavigate.emit(details)
})
this.onBeforeNavigate.emit(details);
});
ipcRendererInternal.on('CHROME_WEBNAVIGATION_ONCOMPLETED', (event: Electron.IpcRendererEvent, details: any) => {
this.onCompleted.emit(details)
})
this.onCompleted.emit(details);
});
}
}
export const setup = () => new WebNavigation()
export const setup = () => new WebNavigation();

View file

@ -1,7 +1,7 @@
import { EventEmitter } from 'events'
import * as path from 'path'
import { EventEmitter } from 'events';
import * as path from 'path';
const Module = require('module')
const Module = require('module');
// Make sure globals like "process" and "global" are always available in preload
// scripts even after they are deleted in "loaded" script.
@ -23,42 +23,42 @@ Module.wrapper = [
// code to override "process" and "Buffer" with local variables.
'return function (exports, require, module, __filename, __dirname) { ',
'\n}.call(this, exports, require, module, __filename, __dirname); });'
]
];
// We modified the original process.argv to let node.js load the
// init.js, we need to restore it here.
process.argv.splice(1, 1)
process.argv.splice(1, 1);
// Clear search paths.
require('../common/reset-search-paths')
require('../common/reset-search-paths');
// Import common settings.
require('@electron/internal/common/init')
require('@electron/internal/common/init');
// The global variable will be used by ipc for event dispatching
const v8Util = process.electronBinding('v8_util')
const v8Util = process.electronBinding('v8_util');
const ipcEmitter = new EventEmitter()
const ipcInternalEmitter = new EventEmitter()
v8Util.setHiddenValue(global, 'ipc', ipcEmitter)
v8Util.setHiddenValue(global, 'ipc-internal', ipcInternalEmitter)
const ipcEmitter = new EventEmitter();
const ipcInternalEmitter = new EventEmitter();
v8Util.setHiddenValue(global, 'ipc', ipcEmitter);
v8Util.setHiddenValue(global, 'ipc-internal', ipcInternalEmitter);
v8Util.setHiddenValue(global, 'ipcNative', {
onMessage (internal: boolean, channel: string, ports: any[], args: any[], senderId: number) {
const sender = internal ? ipcInternalEmitter : ipcEmitter
sender.emit(channel, { sender, senderId, ports }, ...args)
const sender = internal ? ipcInternalEmitter : ipcEmitter;
sender.emit(channel, { sender, senderId, ports }, ...args);
}
})
});
// Use electron module after everything is ready.
const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal')
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils')
const { webFrameInit } = require('@electron/internal/renderer/web-frame-init')
webFrameInit()
const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal');
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils');
const { webFrameInit } = require('@electron/internal/renderer/web-frame-init');
webFrameInit();
// Process command line arguments.
const { hasSwitch, getSwitchValue } = process.electronBinding('command_line')
const { hasSwitch, getSwitchValue } = process.electronBinding('command_line');
const parseOption = function<T> (
name: string, defaultValue: T, converter?: (value: string) => T
@ -69,105 +69,105 @@ const parseOption = function<T> (
? converter(getSwitchValue(name))
: getSwitchValue(name)
)
: defaultValue
}
: defaultValue;
};
const contextIsolation = hasSwitch('context-isolation')
const nodeIntegration = hasSwitch('node-integration')
const webviewTag = hasSwitch('webview-tag')
const isHiddenPage = hasSwitch('hidden-page')
const usesNativeWindowOpen = hasSwitch('native-window-open')
const rendererProcessReuseEnabled = hasSwitch('disable-electron-site-instance-overrides')
const contextIsolation = hasSwitch('context-isolation');
const nodeIntegration = hasSwitch('node-integration');
const webviewTag = hasSwitch('webview-tag');
const isHiddenPage = hasSwitch('hidden-page');
const usesNativeWindowOpen = hasSwitch('native-window-open');
const rendererProcessReuseEnabled = hasSwitch('disable-electron-site-instance-overrides');
const preloadScript = parseOption('preload', null)
const preloadScripts = parseOption('preload-scripts', [], value => value.split(path.delimiter)) as string[]
const appPath = parseOption('app-path', null)
const guestInstanceId = parseOption('guest-instance-id', null, value => parseInt(value))
const openerId = parseOption('opener-id', null, value => parseInt(value))
const preloadScript = parseOption('preload', null);
const preloadScripts = parseOption('preload-scripts', [], value => value.split(path.delimiter)) as string[];
const appPath = parseOption('app-path', null);
const guestInstanceId = parseOption('guest-instance-id', null, value => parseInt(value));
const openerId = parseOption('opener-id', null, value => parseInt(value));
// The arguments to be passed to isolated world.
const isolatedWorldArgs = { ipcRendererInternal, guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen, rendererProcessReuseEnabled }
const isolatedWorldArgs = { ipcRendererInternal, guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen, rendererProcessReuseEnabled };
// The webContents preload script is loaded after the session preload scripts.
if (preloadScript) {
preloadScripts.push(preloadScript)
preloadScripts.push(preloadScript);
}
switch (window.location.protocol) {
case 'devtools:': {
// Override some inspector APIs.
require('@electron/internal/renderer/inspector')
break
require('@electron/internal/renderer/inspector');
break;
}
case 'chrome-extension:': {
// Inject the chrome.* APIs that chrome extensions require
if (!process.electronBinding('features').isExtensionsEnabled()) {
require('@electron/internal/renderer/chrome-api').injectTo(window.location.hostname, window)
require('@electron/internal/renderer/chrome-api').injectTo(window.location.hostname, window);
}
break
break;
}
case 'chrome:':
break
break;
default: {
// Override default web functions.
const { windowSetup } = require('@electron/internal/renderer/window-setup')
windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen, rendererProcessReuseEnabled)
const { windowSetup } = require('@electron/internal/renderer/window-setup');
windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen, rendererProcessReuseEnabled);
// Inject content scripts.
if (!process.electronBinding('features').isExtensionsEnabled()) {
const contentScripts = ipcRendererUtils.invokeSync('ELECTRON_GET_CONTENT_SCRIPTS') as Electron.ContentScriptEntry[]
require('@electron/internal/renderer/content-scripts-injector')(contentScripts)
const contentScripts = ipcRendererUtils.invokeSync('ELECTRON_GET_CONTENT_SCRIPTS') as Electron.ContentScriptEntry[];
require('@electron/internal/renderer/content-scripts-injector')(contentScripts);
}
}
}
// Load webview tag implementation.
if (process.isMainFrame) {
const { webViewInit } = require('@electron/internal/renderer/web-view/web-view-init')
webViewInit(contextIsolation, webviewTag, guestInstanceId)
const { webViewInit } = require('@electron/internal/renderer/web-view/web-view-init');
webViewInit(contextIsolation, webviewTag, guestInstanceId);
}
// Pass the arguments to isolatedWorld.
if (contextIsolation) {
v8Util.setHiddenValue(global, 'isolated-world-args', isolatedWorldArgs)
v8Util.setHiddenValue(global, 'isolated-world-args', isolatedWorldArgs);
}
if (nodeIntegration) {
// Export node bindings to global.
const { makeRequireFunction } = __non_webpack_require__('internal/modules/cjs/helpers') // eslint-disable-line
global.module = new Module('electron/js2c/renderer_init')
global.require = makeRequireFunction(global.module)
global.module = new Module('electron/js2c/renderer_init');
global.require = makeRequireFunction(global.module);
// Set the __filename to the path of html file if it is file: protocol.
if (window.location.protocol === 'file:') {
const location = window.location
let pathname = location.pathname
const location = window.location;
let pathname = location.pathname;
if (process.platform === 'win32') {
if (pathname[0] === '/') pathname = pathname.substr(1)
if (pathname[0] === '/') pathname = pathname.substr(1);
const isWindowsNetworkSharePath = location.hostname.length > 0 && process.resourcesPath.startsWith('\\')
const isWindowsNetworkSharePath = location.hostname.length > 0 && process.resourcesPath.startsWith('\\');
if (isWindowsNetworkSharePath) {
pathname = `//${location.host}/${pathname}`
pathname = `//${location.host}/${pathname}`;
}
}
global.__filename = path.normalize(decodeURIComponent(pathname))
global.__dirname = path.dirname(global.__filename)
global.__filename = path.normalize(decodeURIComponent(pathname));
global.__dirname = path.dirname(global.__filename);
// Set module's filename so relative require can work as expected.
global.module.filename = global.__filename
global.module.filename = global.__filename;
// Also search for module under the html file.
global.module.paths = Module._nodeModulePaths(global.__dirname)
global.module.paths = Module._nodeModulePaths(global.__dirname);
} else {
// For backwards compatibility we fake these two paths here
global.__filename = path.join(process.resourcesPath, 'electron.asar', 'renderer', 'init.js')
global.__dirname = path.join(process.resourcesPath, 'electron.asar', 'renderer')
global.__filename = path.join(process.resourcesPath, 'electron.asar', 'renderer', 'init.js');
global.__dirname = path.join(process.resourcesPath, 'electron.asar', 'renderer');
if (appPath) {
// Search for module under the app directory
global.module.paths = Module._nodeModulePaths(appPath)
global.module.paths = Module._nodeModulePaths(appPath);
}
}
@ -177,42 +177,42 @@ if (nodeIntegration) {
// We do not want to add `uncaughtException` to our definitions
// because we don't want anyone else (anywhere) to throw that kind
// of error.
global.process.emit('uncaughtException' as any, error as any)
return true
global.process.emit('uncaughtException' as any, error as any);
return true;
} else {
return false
return false;
}
}
};
} else {
// Delete Node's symbols after the Environment has been loaded in a
// non context-isolated environment
if (!contextIsolation) {
process.once('loaded', function () {
delete global.process
delete global.Buffer
delete global.setImmediate
delete global.clearImmediate
delete global.global
delete global.root
delete global.GLOBAL
})
delete global.process;
delete global.Buffer;
delete global.setImmediate;
delete global.clearImmediate;
delete global.global;
delete global.root;
delete global.GLOBAL;
});
}
}
// Load the preload scripts.
for (const preloadScript of preloadScripts) {
try {
Module._load(preloadScript)
Module._load(preloadScript);
} catch (error) {
console.error(`Unable to load preload script: ${preloadScript}`)
console.error(error)
console.error(`Unable to load preload script: ${preloadScript}`);
console.error(error);
ipcRendererInternal.send('ELECTRON_BROWSER_PRELOAD_ERROR', preloadScript, error)
ipcRendererInternal.send('ELECTRON_BROWSER_PRELOAD_ERROR', preloadScript, error);
}
}
// Warn about security issues
if (process.isMainFrame) {
const { securityWarnings } = require('@electron/internal/renderer/security-warnings')
securityWarnings(nodeIntegration)
const { securityWarnings } = require('@electron/internal/renderer/security-warnings');
securityWarnings(nodeIntegration);
}

View file

@ -1,61 +1,61 @@
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils'
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
window.onload = function () {
// Use menu API to show context menu.
window.InspectorFrontendHost!.showContextMenuAtPoint = createMenu
window.InspectorFrontendHost!.showContextMenuAtPoint = createMenu;
// correct for Chromium returning undefined for filesystem
window.Persistence!.FileSystemWorkspaceBinding.completeURL = completeURL
window.Persistence!.FileSystemWorkspaceBinding.completeURL = completeURL;
// Use dialog API to override file chooser dialog.
window.UI!.createFileSelectorElement = createFileSelectorElement
}
window.UI!.createFileSelectorElement = createFileSelectorElement;
};
// Extra / is needed as a result of MacOS requiring absolute paths
function completeURL (project: string, path: string) {
project = 'file:///'
return `${project}${path}`
project = 'file:///';
return `${project}${path}`;
}
// The DOM implementation expects (message?: string) => boolean
(window.confirm as any) = function (message: string, title: string) {
return ipcRendererUtils.invokeSync('ELECTRON_INSPECTOR_CONFIRM', message, title) as boolean
}
return ipcRendererUtils.invokeSync('ELECTRON_INSPECTOR_CONFIRM', message, title) as boolean;
};
const useEditMenuItems = function (x: number, y: number, items: ContextMenuItem[]) {
return items.length === 0 && document.elementsFromPoint(x, y).some(function (element) {
return element.nodeName === 'INPUT' ||
element.nodeName === 'TEXTAREA' ||
(element as HTMLElement).isContentEditable
})
}
(element as HTMLElement).isContentEditable;
});
};
const createMenu = function (x: number, y: number, items: ContextMenuItem[]) {
const isEditMenu = useEditMenuItems(x, y, items)
const isEditMenu = useEditMenuItems(x, y, items);
ipcRendererInternal.invoke<number>('ELECTRON_INSPECTOR_CONTEXT_MENU', items, isEditMenu).then(id => {
if (typeof id === 'number') {
window.DevToolsAPI!.contextMenuItemSelected(id)
window.DevToolsAPI!.contextMenuItemSelected(id);
}
window.DevToolsAPI!.contextMenuCleared()
})
}
window.DevToolsAPI!.contextMenuCleared();
});
};
const showFileChooserDialog = function (callback: (blob: File) => void) {
ipcRendererInternal.invoke<[ string, any ]>('ELECTRON_INSPECTOR_SELECT_FILE').then(([path, data]) => {
if (path && data) {
callback(dataToHtml5FileObject(path, data))
callback(dataToHtml5FileObject(path, data));
}
})
}
});
};
const dataToHtml5FileObject = function (path: string, data: any) {
return new File([data], path)
}
return new File([data], path);
};
const createFileSelectorElement = function (this: any, callback: () => void) {
const fileSelectorElement = document.createElement('span')
fileSelectorElement.style.display = 'none'
fileSelectorElement.click = showFileChooserDialog.bind(this, callback)
return fileSelectorElement
}
const fileSelectorElement = document.createElement('span');
fileSelectorElement.style.display = 'none';
fileSelectorElement.click = showFileChooserDialog.bind(this, callback);
return fileSelectorElement;
};

View file

@ -1,24 +1,24 @@
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
type IPCHandler = (event: Electron.IpcRendererEvent, ...args: any[]) => any
export const handle = function <T extends IPCHandler> (channel: string, handler: T) {
ipcRendererInternal.on(channel, async (event, requestId, ...args) => {
const replyChannel = `${channel}_RESPONSE_${requestId}`
const replyChannel = `${channel}_RESPONSE_${requestId}`;
try {
event.sender.send(replyChannel, null, await handler(event, ...args))
event.sender.send(replyChannel, null, await handler(event, ...args));
} catch (error) {
event.sender.send(replyChannel, error)
event.sender.send(replyChannel, error);
}
})
}
});
};
export function invokeSync<T> (command: string, ...args: any[]): T {
const [error, result] = ipcRendererInternal.sendSync(command, ...args)
const [error, result] = ipcRendererInternal.sendSync(command, ...args);
if (error) {
throw error
throw error;
} else {
return result
return result;
}
}

View file

@ -1,30 +1,30 @@
const { ipc } = process.electronBinding('ipc')
const v8Util = process.electronBinding('v8_util')
const { ipc } = process.electronBinding('ipc');
const v8Util = process.electronBinding('v8_util');
// Created by init.js.
export const ipcRendererInternal = v8Util.getHiddenValue<Electron.IpcRendererInternal>(global, 'ipc-internal')
const internal = true
export const ipcRendererInternal = v8Util.getHiddenValue<Electron.IpcRendererInternal>(global, 'ipc-internal');
const internal = true;
ipcRendererInternal.send = function (channel, ...args) {
return ipc.send(internal, channel, args)
}
return ipc.send(internal, channel, args);
};
ipcRendererInternal.sendSync = function (channel, ...args) {
return ipc.sendSync(internal, channel, args)[0]
}
return ipc.sendSync(internal, channel, args)[0];
};
ipcRendererInternal.sendTo = function (webContentsId, channel, ...args) {
return ipc.sendTo(internal, false, webContentsId, channel, args)
}
return ipc.sendTo(internal, false, webContentsId, channel, args);
};
ipcRendererInternal.sendToAll = function (webContentsId, channel, ...args) {
return ipc.sendTo(internal, true, webContentsId, channel, args)
}
return ipc.sendTo(internal, true, webContentsId, channel, args);
};
ipcRendererInternal.invoke = async function<T> (channel: string, ...args: any[]) {
const { error, result } = await ipc.invoke<T>(internal, channel, args)
const { error, result } = await ipc.invoke<T>(internal, channel, args);
if (error) {
throw new Error(`Error invoking remote method '${channel}': ${error}`)
throw new Error(`Error invoking remote method '${channel}': ${error}`);
}
return result
}
return result;
};

View file

@ -1,4 +1,4 @@
const v8Util = process.electronBinding('v8_util')
const v8Util = process.electronBinding('v8_util');
export class CallbacksRegistry {
private nextId: number = 0
@ -6,50 +6,50 @@ export class CallbacksRegistry {
add (callback: Function) {
// The callback is already added.
let id = v8Util.getHiddenValue<number>(callback, 'callbackId')
if (id != null) return id
let id = v8Util.getHiddenValue<number>(callback, 'callbackId');
if (id != null) return id;
id = this.nextId += 1
id = this.nextId += 1;
// Capture the location of the function and put it in the ID string,
// so that release errors can be tracked down easily.
const regexp = /at (.*)/gi
const stackString = (new Error()).stack
if (!stackString) return
const regexp = /at (.*)/gi;
const stackString = (new Error()).stack;
if (!stackString) return;
let filenameAndLine
let match
let filenameAndLine;
let match;
while ((match = regexp.exec(stackString)) !== null) {
const location = match[1]
if (location.includes('(native)')) continue
if (location.includes('(<anonymous>)')) continue
if (location.includes('electron/js2c')) continue
const location = match[1];
if (location.includes('(native)')) continue;
if (location.includes('(<anonymous>)')) continue;
if (location.includes('electron/js2c')) continue;
const ref = /([^/^)]*)\)?$/gi.exec(location)
if (ref) filenameAndLine = ref![1]
break
const ref = /([^/^)]*)\)?$/gi.exec(location);
if (ref) filenameAndLine = ref![1];
break;
}
this.callbacks.set(id, callback)
v8Util.setHiddenValue(callback, 'callbackId', id)
v8Util.setHiddenValue(callback, 'location', filenameAndLine)
return id
this.callbacks.set(id, callback);
v8Util.setHiddenValue(callback, 'callbackId', id);
v8Util.setHiddenValue(callback, 'location', filenameAndLine);
return id;
}
get (id: number) {
return this.callbacks.get(id) || function () {}
return this.callbacks.get(id) || function () {};
}
apply (id: number, ...args: any[]) {
return this.get(id).apply(global, ...args)
return this.get(id).apply(global, ...args);
}
remove (id: number) {
const callback = this.callbacks.get(id)
const callback = this.callbacks.get(id);
if (callback) {
v8Util.deleteHiddenValue(callback, 'callbackId')
this.callbacks.delete(id)
v8Util.deleteHiddenValue(callback, 'callbackId');
this.callbacks.delete(id);
}
}
}

View file

@ -1,9 +1,9 @@
import { webFrame } from 'electron'
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'
import { webFrame } from 'electron';
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
let shouldLog: boolean | null = null
let shouldLog: boolean | null = null;
const { platform, execPath, env } = process
const { platform, execPath, env } = process;
/**
* This method checks if a security message should be logged.
@ -15,37 +15,37 @@ const { platform, execPath, env } = process
*/
const shouldLogSecurityWarnings = function (): boolean {
if (shouldLog !== null) {
return shouldLog
return shouldLog;
}
switch (platform) {
case 'darwin':
shouldLog = execPath.endsWith('MacOS/Electron') ||
execPath.includes('Electron.app/Contents/Frameworks/')
break
execPath.includes('Electron.app/Contents/Frameworks/');
break;
case 'freebsd':
case 'linux':
shouldLog = execPath.endsWith('/electron')
break
shouldLog = execPath.endsWith('/electron');
break;
case 'win32':
shouldLog = execPath.endsWith('\\electron.exe')
break
shouldLog = execPath.endsWith('\\electron.exe');
break;
default:
shouldLog = false
shouldLog = false;
}
if ((env && env.ELECTRON_DISABLE_SECURITY_WARNINGS) ||
(window && window.ELECTRON_DISABLE_SECURITY_WARNINGS)) {
shouldLog = false
shouldLog = false;
}
if ((env && env.ELECTRON_ENABLE_SECURITY_WARNINGS) ||
(window && window.ELECTRON_ENABLE_SECURITY_WARNINGS)) {
shouldLog = true
shouldLog = true;
}
return shouldLog
}
return shouldLog;
};
/**
* Checks if the current window is remote.
@ -54,9 +54,9 @@ const shouldLogSecurityWarnings = function (): boolean {
*/
const getIsRemoteProtocol = function () {
if (window && window.location && window.location.protocol) {
return /^(http|ftp)s?/gi.test(window.location.protocol)
return /^(http|ftp)s?/gi.test(window.location.protocol);
}
}
};
/**
* Checks if the current window is from localhost.
@ -65,11 +65,11 @@ const getIsRemoteProtocol = function () {
*/
const isLocalhost = function () {
if (!window || !window.location) {
return false
return false;
}
return window.location.hostname === 'localhost'
}
return window.location.hostname === 'localhost';
};
/**
* Tries to determine whether a CSP without `unsafe-eval` is set.
@ -79,17 +79,17 @@ const isLocalhost = function () {
const isUnsafeEvalEnabled = function () {
return webFrame.executeJavaScript(`(${(() => {
try {
new Function('') // eslint-disable-line no-new,no-new-func
new Function(''); // eslint-disable-line no-new,no-new-func
} catch {
return false
return false;
}
return true
}).toString()})()`, false)
}
return true;
}).toString()})()`, false);
};
const moreInformation = `\nFor more information and help, consult
https://electronjs.org/docs/tutorial/security.\nThis warning will not show up
once the app is packaged.`
once the app is packaged.`;
/**
* #1 Only load secure content
@ -99,7 +99,7 @@ once the app is packaged.`
*/
const warnAboutInsecureResources = function () {
if (!window || !window.performance || !window.performance.getEntriesByType) {
return
return;
}
const resources = window.performance
@ -107,20 +107,20 @@ const warnAboutInsecureResources = function () {
.filter(({ name }) => /^(http|ftp):/gi.test(name || ''))
.filter(({ name }) => new URL(name).hostname !== 'localhost')
.map(({ name }) => `- ${name}`)
.join('\n')
.join('\n');
if (!resources || resources.length === 0) {
return
return;
}
const warning = `This renderer process loads resources using insecure
protocols. This exposes users of this app to unnecessary security risks.
Consider loading the following resources over HTTPS or FTPS. \n${resources}
\n${moreInformation}`
\n${moreInformation}`;
console.warn('%cElectron Security Warning (Insecure Resources)',
'font-weight: bold;', warning)
}
'font-weight: bold;', warning);
};
/**
* #2 on the checklist: Disable the Node.js integration in all renderers that
@ -129,17 +129,17 @@ const warnAboutInsecureResources = function () {
* Logs a warning message about Node integration.
*/
const warnAboutNodeWithRemoteContent = function (nodeIntegration: boolean) {
if (!nodeIntegration || isLocalhost()) return
if (!nodeIntegration || isLocalhost()) return;
if (getIsRemoteProtocol()) {
const warning = `This renderer process has Node.js integration enabled
and attempted to load remote content from '${window.location}'. This
exposes users of this app to severe security risks.\n${moreInformation}`
exposes users of this app to severe security risks.\n${moreInformation}`;
console.warn('%cElectron Security Warning (Node.js Integration with Remote Content)',
'font-weight: bold;', warning)
'font-weight: bold;', warning);
}
}
};
// Currently missing since it has ramifications and is still experimental:
// #3 Enable context isolation in all renderers that display remote content
@ -153,14 +153,14 @@ const warnAboutNodeWithRemoteContent = function (nodeIntegration: boolean) {
* Logs a warning message about disabled webSecurity.
*/
const warnAboutDisabledWebSecurity = function (webPreferences?: Electron.WebPreferences) {
if (!webPreferences || webPreferences.webSecurity !== false) return
if (!webPreferences || webPreferences.webSecurity !== false) return;
const warning = `This renderer process has "webSecurity" disabled. This
exposes users of this app to severe security risks.\n${moreInformation}`
exposes users of this app to severe security risks.\n${moreInformation}`;
console.warn('%cElectron Security Warning (Disabled webSecurity)',
'font-weight: bold;', warning)
}
'font-weight: bold;', warning);
};
/**
* #6 on the checklist: Define a Content-Security-Policy and use restrictive
@ -170,16 +170,16 @@ const warnAboutDisabledWebSecurity = function (webPreferences?: Electron.WebPref
*/
const warnAboutInsecureCSP = function () {
isUnsafeEvalEnabled().then((enabled) => {
if (!enabled) return
if (!enabled) return;
const warning = `This renderer process has either no Content Security
Policy set or a policy with "unsafe-eval" enabled. This exposes users of
this app to unnecessary security risks.\n${moreInformation}`
this app to unnecessary security risks.\n${moreInformation}`;
console.warn('%cElectron Security Warning (Insecure Content-Security-Policy)',
'font-weight: bold;', warning)
})
}
'font-weight: bold;', warning);
});
};
/**
* #7 on the checklist: Do not set allowRunningInsecureContent to true
@ -187,15 +187,15 @@ const warnAboutInsecureCSP = function () {
* Logs a warning message about disabled webSecurity.
*/
const warnAboutInsecureContentAllowed = function (webPreferences?: Electron.WebPreferences) {
if (!webPreferences || !webPreferences.allowRunningInsecureContent) return
if (!webPreferences || !webPreferences.allowRunningInsecureContent) return;
const warning = `This renderer process has "allowRunningInsecureContent"
enabled. This exposes users of this app to severe security risks.\n
${moreInformation}`
${moreInformation}`;
console.warn('%cElectron Security Warning (allowRunningInsecureContent)',
'font-weight: bold;', warning)
}
'font-weight: bold;', warning);
};
/**
* #8 on the checklist: Do not enable experimental features
@ -204,16 +204,16 @@ const warnAboutInsecureContentAllowed = function (webPreferences?: Electron.WebP
*/
const warnAboutExperimentalFeatures = function (webPreferences?: Electron.WebPreferences) {
if (!webPreferences || (!webPreferences.experimentalFeatures)) {
return
return;
}
const warning = `This renderer process has "experimentalFeatures" enabled.
This exposes users of this app to some security risk. If you do not need
this feature, you should disable it.\n${moreInformation}`
this feature, you should disable it.\n${moreInformation}`;
console.warn('%cElectron Security Warning (experimentalFeatures)',
'font-weight: bold;', warning)
}
'font-weight: bold;', warning);
};
/**
* #9 on the checklist: Do not use enableBlinkFeatures
@ -224,16 +224,16 @@ const warnAboutEnableBlinkFeatures = function (webPreferences?: Electron.WebPref
if (!webPreferences ||
!Object.prototype.hasOwnProperty.call(webPreferences, 'enableBlinkFeatures') ||
(webPreferences.enableBlinkFeatures && webPreferences.enableBlinkFeatures.length === 0)) {
return
return;
}
const warning = `This renderer process has additional "enableBlinkFeatures"
enabled. This exposes users of this app to some security risk. If you do not
need this feature, you should disable it.\n${moreInformation}`
need this feature, you should disable it.\n${moreInformation}`;
console.warn('%cElectron Security Warning (enableBlinkFeatures)',
'font-weight: bold;', warning)
}
'font-weight: bold;', warning);
};
/**
* #10 on the checklist: Do Not Use allowpopups
@ -242,21 +242,21 @@ const warnAboutEnableBlinkFeatures = function (webPreferences?: Electron.WebPref
*/
const warnAboutAllowedPopups = function () {
if (document && document.querySelectorAll) {
const domElements = document.querySelectorAll('[allowpopups]')
const domElements = document.querySelectorAll('[allowpopups]');
if (!domElements || domElements.length === 0) {
return
return;
}
const warning = `A <webview> has "allowpopups" set to true. This exposes
users of this app to some security risk, since popups are just
BrowserWindows. If you do not need this feature, you should disable it.\n
${moreInformation}`
${moreInformation}`;
console.warn('%cElectron Security Warning (allowpopups)',
'font-weight: bold;', warning)
'font-weight: bold;', warning);
}
}
};
// Currently missing since we can't easily programmatically check for it:
// #11 Verify WebView Options Before Creation
@ -268,19 +268,19 @@ const warnAboutAllowedPopups = function () {
// Logs a warning message about the remote module
const warnAboutRemoteModuleWithRemoteContent = function (webPreferences?: Electron.WebPreferences) {
if (!webPreferences || isLocalhost()) return
const remoteModuleEnabled = webPreferences.enableRemoteModule != null ? !!webPreferences.enableRemoteModule : true
if (!remoteModuleEnabled) return
if (!webPreferences || isLocalhost()) return;
const remoteModuleEnabled = webPreferences.enableRemoteModule != null ? !!webPreferences.enableRemoteModule : true;
if (!remoteModuleEnabled) return;
if (getIsRemoteProtocol()) {
const warning = `This renderer process has "enableRemoteModule" enabled
and attempted to load remote content from '${window.location}'. This
exposes users of this app to unnecessary security risks.\n${moreInformation}`
exposes users of this app to unnecessary security risks.\n${moreInformation}`;
console.warn('%cElectron Security Warning (enableRemoteModule)',
'font-weight: bold;', warning)
'font-weight: bold;', warning);
}
}
};
// Currently missing since we can't easily programmatically check for it:
// #16 Filter the `remote` module
@ -288,31 +288,31 @@ const warnAboutRemoteModuleWithRemoteContent = function (webPreferences?: Electr
const logSecurityWarnings = function (
webPreferences: Electron.WebPreferences | undefined, nodeIntegration: boolean
) {
warnAboutNodeWithRemoteContent(nodeIntegration)
warnAboutDisabledWebSecurity(webPreferences)
warnAboutInsecureResources()
warnAboutInsecureContentAllowed(webPreferences)
warnAboutExperimentalFeatures(webPreferences)
warnAboutEnableBlinkFeatures(webPreferences)
warnAboutInsecureCSP()
warnAboutAllowedPopups()
warnAboutRemoteModuleWithRemoteContent(webPreferences)
}
warnAboutNodeWithRemoteContent(nodeIntegration);
warnAboutDisabledWebSecurity(webPreferences);
warnAboutInsecureResources();
warnAboutInsecureContentAllowed(webPreferences);
warnAboutExperimentalFeatures(webPreferences);
warnAboutEnableBlinkFeatures(webPreferences);
warnAboutInsecureCSP();
warnAboutAllowedPopups();
warnAboutRemoteModuleWithRemoteContent(webPreferences);
};
const getWebPreferences = async function () {
try {
return ipcRendererInternal.invoke<Electron.WebPreferences>('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES')
return ipcRendererInternal.invoke<Electron.WebPreferences>('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES');
} catch (error) {
console.warn(`getLastWebPreferences() failed: ${error}`)
console.warn(`getLastWebPreferences() failed: ${error}`);
}
}
};
export function securityWarnings (nodeIntegration: boolean) {
const loadHandler = async function () {
if (shouldLogSecurityWarnings()) {
const webPreferences = await getWebPreferences()
logSecurityWarnings(webPreferences, nodeIntegration)
const webPreferences = await getWebPreferences();
logSecurityWarnings(webPreferences, nodeIntegration);
}
}
window.addEventListener('load', loadHandler, { once: true })
};
window.addEventListener('load', loadHandler, { once: true });
}

View file

@ -1,5 +1,5 @@
import { webFrame, WebFrame } from 'electron'
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils'
import { webFrame, WebFrame } from 'electron';
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
// All keys of WebFrame that extend Function
type WebFrameMethod = {
@ -15,6 +15,6 @@ export const webFrameInit = () => {
// The TypeScript compiler cannot handle the sheer number of
// call signatures here and simply gives up. Incorrect invocations
// will be caught by "keyof WebFrameMethod" though.
return (webFrame[method] as any)(...args)
})
}
return (webFrame[method] as any)(...args);
});
};

View file

@ -1,7 +1,7 @@
import { webFrame, IpcMessageEvent } from 'electron'
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'
import { webFrame, IpcMessageEvent } from 'electron';
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
import { WebViewImpl } from '@electron/internal/renderer/web-view/web-view-impl'
import { WebViewImpl } from '@electron/internal/renderer/web-view/web-view-impl';
const WEB_VIEW_EVENTS: Record<string, Array<string>> = {
'load-commit': ['url', 'isMainFrame'],
@ -37,76 +37,76 @@ const WEB_VIEW_EVENTS: Record<string, Array<string>> = {
'found-in-page': ['result'],
'did-change-theme-color': ['themeColor'],
'update-target-url': ['url']
}
};
const DEPRECATED_EVENTS: Record<string, string> = {
'page-title-updated': 'page-title-set'
}
};
const dispatchEvent = function (
webView: WebViewImpl, eventName: string, eventKey: string, ...args: Array<any>
) {
if (DEPRECATED_EVENTS[eventName] != null) {
dispatchEvent(webView, DEPRECATED_EVENTS[eventName], eventKey, ...args)
dispatchEvent(webView, DEPRECATED_EVENTS[eventName], eventKey, ...args);
}
const domEvent = new Event(eventName) as ElectronInternal.WebViewEvent
const domEvent = new Event(eventName) as ElectronInternal.WebViewEvent;
WEB_VIEW_EVENTS[eventKey].forEach((prop, index) => {
(domEvent as any)[prop] = args[index]
})
(domEvent as any)[prop] = args[index];
});
webView.dispatchEvent(domEvent)
webView.dispatchEvent(domEvent);
if (eventName === 'load-commit') {
webView.onLoadCommit(domEvent)
webView.onLoadCommit(domEvent);
} else if (eventName === 'focus-change') {
webView.onFocusChange()
webView.onFocusChange();
}
}
};
export function registerEvents (webView: WebViewImpl, viewInstanceId: number) {
ipcRendererInternal.on(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${viewInstanceId}`, function () {
webView.guestInstanceId = undefined
webView.reset()
const domEvent = new Event('destroyed')
webView.dispatchEvent(domEvent)
})
webView.guestInstanceId = undefined;
webView.reset();
const domEvent = new Event('destroyed');
webView.dispatchEvent(domEvent);
});
ipcRendererInternal.on(`ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-${viewInstanceId}`, function (event, eventName, ...args) {
dispatchEvent(webView, eventName, eventName, ...args)
})
dispatchEvent(webView, eventName, eventName, ...args);
});
ipcRendererInternal.on(`ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-${viewInstanceId}`, function (event, channel, ...args) {
const domEvent = new Event('ipc-message') as IpcMessageEvent
domEvent.channel = channel
domEvent.args = args
const domEvent = new Event('ipc-message') as IpcMessageEvent;
domEvent.channel = channel;
domEvent.args = args;
webView.dispatchEvent(domEvent)
})
webView.dispatchEvent(domEvent);
});
}
export function deregisterEvents (viewInstanceId: number) {
ipcRendererInternal.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${viewInstanceId}`)
ipcRendererInternal.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-${viewInstanceId}`)
ipcRendererInternal.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-${viewInstanceId}`)
ipcRendererInternal.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${viewInstanceId}`);
ipcRendererInternal.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-${viewInstanceId}`);
ipcRendererInternal.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-${viewInstanceId}`);
}
export function createGuest (params: Record<string, any>): Promise<number> {
return ipcRendererInternal.invoke('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', params)
return ipcRendererInternal.invoke('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', params);
}
export function attachGuest (
elementInstanceId: number, guestInstanceId: number, params: Record<string, any>, contentWindow: Window
) {
const embedderFrameId = (webFrame as ElectronInternal.WebFrameInternal).getWebFrameId(contentWindow)
const embedderFrameId = (webFrame as ElectronInternal.WebFrameInternal).getWebFrameId(contentWindow);
if (embedderFrameId < 0) { // this error should not happen.
throw new Error('Invalid embedder frame')
throw new Error('Invalid embedder frame');
}
ipcRendererInternal.invoke('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', embedderFrameId, elementInstanceId, guestInstanceId, params)
ipcRendererInternal.invoke('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', embedderFrameId, elementInstanceId, guestInstanceId, params);
}
export const guestViewInternalModule = {
deregisterEvents,
createGuest,
attachGuest
}
};

View file

@ -1,15 +1,15 @@
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'
import { WebViewImpl } from '@electron/internal/renderer/web-view/web-view-impl'
import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants'
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
import { WebViewImpl } from '@electron/internal/renderer/web-view/web-view-impl';
import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants';
// Helper function to resolve url set in attribute.
const a = document.createElement('a')
const a = document.createElement('a');
const resolveURL = function (url?: string | null) {
if (!url) return ''
a.href = url
return a.href
}
if (!url) return '';
a.href = url;
return a.href;
};
interface MutationHandler {
handleMutation (_oldValue: any, _newValue: any): any;
@ -22,40 +22,40 @@ class WebViewAttribute implements MutationHandler {
public ignoreMutation = false;
constructor (public name: string, public webViewImpl: WebViewImpl) {
this.name = name
this.value = (webViewImpl.webviewNode as Record<string, any>)[name] || ''
this.webViewImpl = webViewImpl
this.defineProperty()
this.name = name;
this.value = (webViewImpl.webviewNode as Record<string, any>)[name] || '';
this.webViewImpl = webViewImpl;
this.defineProperty();
}
// Retrieves and returns the attribute's value.
public getValue () {
return this.webViewImpl.webviewNode.getAttribute(this.name) || this.value
return this.webViewImpl.webviewNode.getAttribute(this.name) || this.value;
}
// Sets the attribute's value.
public setValue (value: any) {
this.webViewImpl.webviewNode.setAttribute(this.name, value || '')
this.webViewImpl.webviewNode.setAttribute(this.name, value || '');
}
// Changes the attribute's value without triggering its mutation handler.
public setValueIgnoreMutation (value: any) {
this.ignoreMutation = true
this.setValue(value)
this.ignoreMutation = false
this.ignoreMutation = true;
this.setValue(value);
this.ignoreMutation = false;
}
// Defines this attribute as a property on the webview node.
public defineProperty () {
return Object.defineProperty(this.webViewImpl.webviewNode, this.name, {
get: () => {
return this.getValue()
return this.getValue();
},
set: (value) => {
return this.setValue(value)
return this.setValue(value);
},
enumerable: true
})
});
}
// Called when the attribute's value changes.
@ -65,14 +65,14 @@ class WebViewAttribute implements MutationHandler {
// An attribute that is treated as a Boolean.
class BooleanAttribute extends WebViewAttribute {
getValue () {
return this.webViewImpl.webviewNode.hasAttribute(this.name)
return this.webViewImpl.webviewNode.hasAttribute(this.name);
}
setValue (value: boolean) {
if (value) {
this.webViewImpl.webviewNode.setAttribute(this.name, '')
this.webViewImpl.webviewNode.setAttribute(this.name, '');
} else {
this.webViewImpl.webviewNode.removeAttribute(this.name)
this.webViewImpl.webviewNode.removeAttribute(this.name);
}
}
}
@ -82,21 +82,21 @@ class PartitionAttribute extends WebViewAttribute {
public validPartitionId = true
constructor (public webViewImpl: WebViewImpl) {
super(WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION, webViewImpl)
super(WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION, webViewImpl);
}
public handleMutation = (oldValue: any, newValue: any) => {
newValue = newValue || ''
newValue = newValue || '';
// The partition cannot change if the webview has already navigated.
if (!this.webViewImpl.beforeFirstNavigation) {
console.error(WEB_VIEW_CONSTANTS.ERROR_MSG_ALREADY_NAVIGATED)
this.setValueIgnoreMutation(oldValue)
return
console.error(WEB_VIEW_CONSTANTS.ERROR_MSG_ALREADY_NAVIGATED);
this.setValueIgnoreMutation(oldValue);
return;
}
if (newValue === 'persist:') {
this.validPartitionId = false
console.error(WEB_VIEW_CONSTANTS.ERROR_MSG_INVALID_PARTITION_ATTRIBUTE)
this.validPartitionId = false;
console.error(WEB_VIEW_CONSTANTS.ERROR_MSG_INVALID_PARTITION_ATTRIBUTE);
}
}
}
@ -106,26 +106,26 @@ class SrcAttribute extends WebViewAttribute {
public observer!: MutationObserver;
constructor (public webViewImpl: WebViewImpl) {
super(WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC, webViewImpl)
this.setupMutationObserver()
super(WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC, webViewImpl);
this.setupMutationObserver();
}
public getValue () {
if (this.webViewImpl.webviewNode.hasAttribute(this.name)) {
return resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name))
return resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name));
} else {
return this.value
return this.value;
}
}
public setValueIgnoreMutation (value: any) {
super.setValueIgnoreMutation(value)
super.setValueIgnoreMutation(value);
// takeRecords() is needed to clear queued up src mutations. Without it, it
// is possible for this change to get picked up asyncronously by src's
// mutation observer |observer|, and then get handled even though we do not
// want to handle this mutation.
this.observer.takeRecords()
this.observer.takeRecords();
}
public handleMutation = (oldValue: any, newValue: any) => {
@ -136,10 +136,10 @@ class SrcAttribute extends WebViewAttribute {
// src attribute changes normally initiate a navigation. We suppress
// the next src attribute handler call to avoid reloading the page
// on every guest-initiated navigation.
this.setValueIgnoreMutation(oldValue)
return
this.setValueIgnoreMutation(oldValue);
return;
}
this.parse()
this.parse();
}
// The purpose of this mutation observer is to catch assignment to the src
@ -149,144 +149,144 @@ class SrcAttribute extends WebViewAttribute {
public setupMutationObserver () {
this.observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
const { oldValue } = mutation
const newValue = this.getValue()
const { oldValue } = mutation;
const newValue = this.getValue();
if (oldValue !== newValue) {
return
return;
}
this.handleMutation(oldValue, newValue)
this.handleMutation(oldValue, newValue);
}
})
});
const params = {
attributes: true,
attributeOldValue: true,
attributeFilter: [this.name]
}
};
this.observer.observe(this.webViewImpl.webviewNode, params)
this.observer.observe(this.webViewImpl.webviewNode, params);
}
public parse () {
if (!this.webViewImpl.elementAttached || !this.webViewImpl.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION].validPartitionId || !this.getValue()) {
return
return;
}
if (this.webViewImpl.guestInstanceId == null) {
if (this.webViewImpl.beforeFirstNavigation) {
this.webViewImpl.beforeFirstNavigation = false
this.webViewImpl.createGuest()
this.webViewImpl.beforeFirstNavigation = false;
this.webViewImpl.createGuest();
}
return
return;
}
// Navigate to |this.src|.
const opts: Record<string, string> = {}
const opts: Record<string, string> = {};
const httpreferrer = this.webViewImpl.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_HTTPREFERRER].getValue()
const httpreferrer = this.webViewImpl.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_HTTPREFERRER].getValue();
if (httpreferrer) {
opts.httpReferrer = httpreferrer
opts.httpReferrer = httpreferrer;
}
const useragent = this.webViewImpl.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_USERAGENT].getValue()
const useragent = this.webViewImpl.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_USERAGENT].getValue();
if (useragent) {
opts.userAgent = useragent
opts.userAgent = useragent;
}
const guestInstanceId = this.webViewImpl.guestInstanceId
const method = 'loadURL'
const args = [this.getValue(), opts]
const guestInstanceId = this.webViewImpl.guestInstanceId;
const method = 'loadURL';
const args = [this.getValue(), opts];
ipcRendererInternal.invoke('ELECTRON_GUEST_VIEW_MANAGER_CALL', guestInstanceId, method, args)
ipcRendererInternal.invoke('ELECTRON_GUEST_VIEW_MANAGER_CALL', guestInstanceId, method, args);
}
}
// Attribute specifies HTTP referrer.
class HttpReferrerAttribute extends WebViewAttribute {
constructor (webViewImpl: WebViewImpl) {
super(WEB_VIEW_CONSTANTS.ATTRIBUTE_HTTPREFERRER, webViewImpl)
super(WEB_VIEW_CONSTANTS.ATTRIBUTE_HTTPREFERRER, webViewImpl);
}
}
// Attribute specifies user agent
class UserAgentAttribute extends WebViewAttribute {
constructor (webViewImpl: WebViewImpl) {
super(WEB_VIEW_CONSTANTS.ATTRIBUTE_USERAGENT, webViewImpl)
super(WEB_VIEW_CONSTANTS.ATTRIBUTE_USERAGENT, webViewImpl);
}
}
// Attribute that set preload script.
class PreloadAttribute extends WebViewAttribute {
constructor (webViewImpl: WebViewImpl) {
super(WEB_VIEW_CONSTANTS.ATTRIBUTE_PRELOAD, webViewImpl)
super(WEB_VIEW_CONSTANTS.ATTRIBUTE_PRELOAD, webViewImpl);
}
public getValue () {
if (!this.webViewImpl.webviewNode.hasAttribute(this.name)) {
return this.value
return this.value;
}
let preload = resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name))
const protocol = preload.substr(0, 5)
let preload = resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name));
const protocol = preload.substr(0, 5);
if (protocol !== 'file:') {
console.error(WEB_VIEW_CONSTANTS.ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE)
preload = ''
console.error(WEB_VIEW_CONSTANTS.ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE);
preload = '';
}
return preload
return preload;
}
}
// Attribute that specifies the blink features to be enabled.
class BlinkFeaturesAttribute extends WebViewAttribute {
constructor (webViewImpl: WebViewImpl) {
super(WEB_VIEW_CONSTANTS.ATTRIBUTE_BLINKFEATURES, webViewImpl)
super(WEB_VIEW_CONSTANTS.ATTRIBUTE_BLINKFEATURES, webViewImpl);
}
}
// Attribute that specifies the blink features to be disabled.
class DisableBlinkFeaturesAttribute extends WebViewAttribute {
constructor (webViewImpl: WebViewImpl) {
super(WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEBLINKFEATURES, webViewImpl)
super(WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEBLINKFEATURES, webViewImpl);
}
}
// Attribute that specifies the web preferences to be enabled.
class WebPreferencesAttribute extends WebViewAttribute {
constructor (webViewImpl: WebViewImpl) {
super(WEB_VIEW_CONSTANTS.ATTRIBUTE_WEBPREFERENCES, webViewImpl)
super(WEB_VIEW_CONSTANTS.ATTRIBUTE_WEBPREFERENCES, webViewImpl);
}
}
class EnableRemoteModuleAttribute extends WebViewAttribute {
constructor (webViewImpl: WebViewImpl) {
super(WEB_VIEW_CONSTANTS.ATTRIBUTE_ENABLEREMOTEMODULE, webViewImpl)
super(WEB_VIEW_CONSTANTS.ATTRIBUTE_ENABLEREMOTEMODULE, webViewImpl);
}
public getValue () {
return this.webViewImpl.webviewNode.getAttribute(this.name) !== 'false'
return this.webViewImpl.webviewNode.getAttribute(this.name) !== 'false';
}
public setValue (value: any) {
this.webViewImpl.webviewNode.setAttribute(this.name, value ? 'true' : 'false')
this.webViewImpl.webviewNode.setAttribute(this.name, value ? 'true' : 'false');
}
}
// Sets up all of the webview attributes.
WebViewImpl.prototype.setupWebViewAttributes = function () {
this.attributes = {}
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION] = new PartitionAttribute(this)
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC] = new SrcAttribute(this)
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_HTTPREFERRER] = new HttpReferrerAttribute(this)
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_USERAGENT] = new UserAgentAttribute(this)
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATION] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATION, this)
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATIONINSUBFRAMES] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATIONINSUBFRAMES, this)
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PLUGINS] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_PLUGINS, this)
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEWEBSECURITY, this)
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_ALLOWPOPUPS, this)
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_ENABLEREMOTEMODULE] = new EnableRemoteModuleAttribute(this)
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this)
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_BLINKFEATURES] = new BlinkFeaturesAttribute(this)
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEBLINKFEATURES] = new DisableBlinkFeaturesAttribute(this)
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_WEBPREFERENCES] = new WebPreferencesAttribute(this)
}
this.attributes = {};
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION] = new PartitionAttribute(this);
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC] = new SrcAttribute(this);
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_HTTPREFERRER] = new HttpReferrerAttribute(this);
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_USERAGENT] = new UserAgentAttribute(this);
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATION] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATION, this);
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATIONINSUBFRAMES] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATIONINSUBFRAMES, this);
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PLUGINS] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_PLUGINS, this);
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEWEBSECURITY, this);
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_ALLOWPOPUPS, this);
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_ENABLEREMOTEMODULE] = new EnableRemoteModuleAttribute(this);
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this);
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_BLINKFEATURES] = new BlinkFeaturesAttribute(this);
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEBLINKFEATURES] = new DisableBlinkFeaturesAttribute(this);
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_WEBPREFERENCES] = new WebPreferencesAttribute(this);
};

View file

@ -8,12 +8,12 @@
// which runs in browserify environment instead of Node environment, all native
// modules must be passed from outside, all included files must be plain JS.
import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants'
import { WebViewImpl as IWebViewImpl, webViewImplModule } from '@electron/internal/renderer/web-view/web-view-impl'
import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants';
import { WebViewImpl as IWebViewImpl, webViewImplModule } from '@electron/internal/renderer/web-view/web-view-impl';
// Return a WebViewElement class that is defined in this context.
const defineWebViewElement = (v8Util: NodeJS.V8UtilBinding, webViewImpl: typeof webViewImplModule) => {
const { guestViewInternal, WebViewImpl } = webViewImpl
const { guestViewInternal, WebViewImpl } = webViewImpl;
return class WebViewElement extends HTMLElement {
public internalInstanceId?: number;
@ -33,45 +33,45 @@ const defineWebViewElement = (v8Util: NodeJS.V8UtilBinding, webViewImpl: typeof
WEB_VIEW_CONSTANTS.ATTRIBUTE_BLINKFEATURES,
WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEBLINKFEATURES,
WEB_VIEW_CONSTANTS.ATTRIBUTE_WEBPREFERENCES
]
];
}
constructor () {
super()
v8Util.setHiddenValue(this, 'internal', new WebViewImpl(this))
super();
v8Util.setHiddenValue(this, 'internal', new WebViewImpl(this));
}
connectedCallback () {
const internal = v8Util.getHiddenValue<IWebViewImpl>(this, 'internal')
const internal = v8Util.getHiddenValue<IWebViewImpl>(this, 'internal');
if (!internal) {
return
return;
}
if (!internal.elementAttached) {
guestViewInternal.registerEvents(internal, internal.viewInstanceId)
internal.elementAttached = true
internal.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC].parse()
guestViewInternal.registerEvents(internal, internal.viewInstanceId);
internal.elementAttached = true;
internal.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC].parse();
}
}
attributeChangedCallback (name: string, oldValue: any, newValue: any) {
const internal = v8Util.getHiddenValue<IWebViewImpl>(this, 'internal')
const internal = v8Util.getHiddenValue<IWebViewImpl>(this, 'internal');
if (internal) {
internal.handleWebviewAttributeMutation(name, oldValue, newValue)
internal.handleWebviewAttributeMutation(name, oldValue, newValue);
}
}
disconnectedCallback () {
const internal = v8Util.getHiddenValue<IWebViewImpl>(this, 'internal')
const internal = v8Util.getHiddenValue<IWebViewImpl>(this, 'internal');
if (!internal) {
return
return;
}
guestViewInternal.deregisterEvents(internal.viewInstanceId)
internal.elementAttached = false
this.internalInstanceId = 0
internal.reset()
guestViewInternal.deregisterEvents(internal.viewInstanceId);
internal.elementAttached = false;
this.internalInstanceId = 0;
internal.reset();
}
}
}
};
};
// Register <webview> custom element.
const registerWebViewElement = (v8Util: NodeJS.V8UtilBinding, webViewImpl: typeof webViewImplModule) => {
@ -79,40 +79,40 @@ const registerWebViewElement = (v8Util: NodeJS.V8UtilBinding, webViewImpl: typeo
// eslint-disable-next-line
const WebViewElement = defineWebViewElement(v8Util, webViewImpl) as unknown as typeof ElectronInternal.WebViewElement
webViewImpl.setupMethods(WebViewElement)
webViewImpl.setupMethods(WebViewElement);
// The customElements.define has to be called in a special scope.
const webFrame = webViewImpl.webFrame as ElectronInternal.WebFrameInternal
const webFrame = webViewImpl.webFrame as ElectronInternal.WebFrameInternal;
webFrame.allowGuestViewElementDefinition(window, () => {
window.customElements.define('webview', WebViewElement);
(window as any).WebView = WebViewElement
(window as any).WebView = WebViewElement;
// Delete the callbacks so developers cannot call them and produce unexpected
// behavior.
delete WebViewElement.prototype.connectedCallback
delete WebViewElement.prototype.disconnectedCallback
delete WebViewElement.prototype.attributeChangedCallback
delete WebViewElement.prototype.connectedCallback;
delete WebViewElement.prototype.disconnectedCallback;
delete WebViewElement.prototype.attributeChangedCallback;
// Now that |observedAttributes| has been retrieved, we can hide it from
// user code as well.
// TypeScript is concerned that we're deleting a read-only attribute
delete (WebViewElement as any).observedAttributes
})
}
delete (WebViewElement as any).observedAttributes;
});
};
// Prepare to register the <webview> element.
export const setupWebView = (v8Util: NodeJS.V8UtilBinding, webViewImpl: typeof webViewImplModule) => {
const useCapture = true
const useCapture = true;
const listener = (event: Event) => {
if (document.readyState === 'loading') {
return
return;
}
webViewImpl.setupAttributes()
registerWebViewElement(v8Util, webViewImpl)
webViewImpl.setupAttributes();
registerWebViewElement(v8Util, webViewImpl);
window.removeEventListener(event.type, listener, useCapture)
}
window.removeEventListener(event.type, listener, useCapture);
};
window.addEventListener('readystatechange', listener, useCapture)
}
window.addEventListener('readystatechange', listener, useCapture);
};

View file

@ -1,21 +1,21 @@
import * as electron from 'electron'
import * as electron from 'electron';
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils'
import * as guestViewInternal from '@electron/internal/renderer/web-view/guest-view-internal'
import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants'
import { syncMethods, asyncMethods, properties } from '@electron/internal/common/web-view-methods'
import { deserialize } from '@electron/internal/common/type-utils'
const { webFrame } = electron
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
import * as guestViewInternal from '@electron/internal/renderer/web-view/guest-view-internal';
import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants';
import { syncMethods, asyncMethods, properties } from '@electron/internal/common/web-view-methods';
import { deserialize } from '@electron/internal/common/type-utils';
const { webFrame } = electron;
const v8Util = process.electronBinding('v8_util')
const v8Util = process.electronBinding('v8_util');
// ID generator.
let nextId = 0
let nextId = 0;
const getNextId = function () {
return ++nextId
}
return ++nextId;
};
// Represents the internal state of the WebView node.
export class WebViewImpl {
@ -38,29 +38,29 @@ export class WebViewImpl {
constructor (public webviewNode: HTMLElement) {
// Create internal iframe element.
this.internalElement = this.createInternalElement()
const shadowRoot = this.webviewNode.attachShadow({ mode: 'open' })
shadowRoot.innerHTML = '<!DOCTYPE html><style type="text/css">:host { display: flex; }</style>'
this.setupWebViewAttributes()
this.viewInstanceId = getNextId()
shadowRoot.appendChild(this.internalElement)
this.internalElement = this.createInternalElement();
const shadowRoot = this.webviewNode.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = '<!DOCTYPE html><style type="text/css">:host { display: flex; }</style>';
this.setupWebViewAttributes();
this.viewInstanceId = getNextId();
shadowRoot.appendChild(this.internalElement);
// Provide access to contentWindow.
Object.defineProperty(this.webviewNode, 'contentWindow', {
get: () => {
return this.internalElement.contentWindow
return this.internalElement.contentWindow;
},
enumerable: true
})
});
}
createInternalElement () {
const iframeElement = document.createElement('iframe')
iframeElement.style.flex = '1 1 auto'
iframeElement.style.width = '100%'
iframeElement.style.border = '0'
v8Util.setHiddenValue(iframeElement, 'internal', this)
return iframeElement
const iframeElement = document.createElement('iframe');
iframeElement.style.flex = '1 1 auto';
iframeElement.style.width = '100%';
iframeElement.style.border = '0';
v8Util.setHiddenValue(iframeElement, 'internal', this);
return iframeElement;
}
// Resets some state upon reattaching <webview> element to the DOM.
@ -72,20 +72,20 @@ export class WebViewImpl {
// heard back from createGuest yet. We will not reset the flag in this case so
// that we don't end up allocating a second guest.
if (this.guestInstanceId) {
this.guestInstanceId = undefined
this.guestInstanceId = undefined;
}
this.beforeFirstNavigation = true
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION].validPartitionId = true
this.beforeFirstNavigation = true;
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION].validPartitionId = true;
// Since attachment swaps a local frame for a remote frame, we need our
// internal iframe element to be local again before we can reattach.
const newFrame = this.createInternalElement()
const oldFrame = this.internalElement
this.internalElement = newFrame
const newFrame = this.createInternalElement();
const oldFrame = this.internalElement;
this.internalElement = newFrame;
if (oldFrame && oldFrame.parentNode) {
oldFrame.parentNode.replaceChild(newFrame, oldFrame)
oldFrame.parentNode.replaceChild(newFrame, oldFrame);
}
}
@ -96,179 +96,179 @@ export class WebViewImpl {
// details.
handleWebviewAttributeMutation (attributeName: string, oldValue: any, newValue: any) {
if (!this.attributes[attributeName] || this.attributes[attributeName].ignoreMutation) {
return
return;
}
// Let the changed attribute handle its own mutation
this.attributes[attributeName].handleMutation(oldValue, newValue)
this.attributes[attributeName].handleMutation(oldValue, newValue);
}
onElementResize () {
const resizeEvent = new Event('resize') as ElectronInternal.WebFrameResizeEvent
resizeEvent.newWidth = this.webviewNode.clientWidth
resizeEvent.newHeight = this.webviewNode.clientHeight
this.dispatchEvent(resizeEvent)
const resizeEvent = new Event('resize') as ElectronInternal.WebFrameResizeEvent;
resizeEvent.newWidth = this.webviewNode.clientWidth;
resizeEvent.newHeight = this.webviewNode.clientHeight;
this.dispatchEvent(resizeEvent);
}
createGuest () {
guestViewInternal.createGuest(this.buildParams()).then(guestInstanceId => {
this.attachGuestInstance(guestInstanceId)
})
this.attachGuestInstance(guestInstanceId);
});
}
dispatchEvent (webViewEvent: Electron.Event) {
this.webviewNode.dispatchEvent(webViewEvent)
this.webviewNode.dispatchEvent(webViewEvent);
}
// Adds an 'on<event>' property on the webview, which can be used to set/unset
// an event handler.
setupEventProperty (eventName: string) {
const propertyName = `on${eventName.toLowerCase()}`
const propertyName = `on${eventName.toLowerCase()}`;
return Object.defineProperty(this.webviewNode, propertyName, {
get: () => {
return this.on[propertyName]
return this.on[propertyName];
},
set: (value) => {
if (this.on[propertyName]) {
this.webviewNode.removeEventListener(eventName, this.on[propertyName])
this.webviewNode.removeEventListener(eventName, this.on[propertyName]);
}
this.on[propertyName] = value
this.on[propertyName] = value;
if (value) {
return this.webviewNode.addEventListener(eventName, value)
return this.webviewNode.addEventListener(eventName, value);
}
},
enumerable: true
})
});
}
// Updates state upon loadcommit.
onLoadCommit (webViewEvent: ElectronInternal.WebViewEvent) {
const oldValue = this.webviewNode.getAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC)
const newValue = webViewEvent.url
const oldValue = this.webviewNode.getAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC);
const newValue = webViewEvent.url;
if (webViewEvent.isMainFrame && (oldValue !== newValue)) {
// Touching the src attribute triggers a navigation. To avoid
// triggering a page reload on every guest-initiated navigation,
// we do not handle this mutation.
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC].setValueIgnoreMutation(newValue)
this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC].setValueIgnoreMutation(newValue);
}
}
// Emits focus/blur events.
onFocusChange () {
const hasFocus = document.activeElement === this.webviewNode
const hasFocus = document.activeElement === this.webviewNode;
if (hasFocus !== this.hasFocus) {
this.hasFocus = hasFocus
this.dispatchEvent(new Event(hasFocus ? 'focus' : 'blur'))
this.hasFocus = hasFocus;
this.dispatchEvent(new Event(hasFocus ? 'focus' : 'blur'));
}
}
onAttach (storagePartitionId: number) {
return this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION].setValue(storagePartitionId)
return this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION].setValue(storagePartitionId);
}
buildParams () {
const params: Record<string, any> = {
instanceId: this.viewInstanceId,
userAgentOverride: this.userAgentOverride
}
};
for (const attributeName in this.attributes) {
if (Object.prototype.hasOwnProperty.call(this.attributes, attributeName)) {
params[attributeName] = this.attributes[attributeName].getValue()
params[attributeName] = this.attributes[attributeName].getValue();
}
}
return params
return params;
}
attachGuestInstance (guestInstanceId: number) {
if (!this.elementAttached) {
// The element could be detached before we got response from browser.
return
return;
}
this.internalInstanceId = getNextId()
this.guestInstanceId = guestInstanceId
this.internalInstanceId = getNextId();
this.guestInstanceId = guestInstanceId;
guestViewInternal.attachGuest(
this.internalInstanceId,
this.guestInstanceId,
this.buildParams(),
this.internalElement.contentWindow!
)
);
// ResizeObserver is a browser global not recognized by "standard".
/* globals ResizeObserver */
// TODO(zcbenz): Should we deprecate the "resize" event? Wait, it is not
// even documented.
this.resizeObserver = new ResizeObserver(this.onElementResize.bind(this))
this.resizeObserver.observe(this.internalElement)
this.resizeObserver = new ResizeObserver(this.onElementResize.bind(this));
this.resizeObserver.observe(this.internalElement);
}
}
export const setupAttributes = () => {
require('@electron/internal/renderer/web-view/web-view-attributes')
}
require('@electron/internal/renderer/web-view/web-view-attributes');
};
// I wish eslint wasn't so stupid, but it is
// eslint-disable-next-line
export const setupMethods = (WebViewElement: typeof ElectronInternal.WebViewElement) => {
WebViewElement.prototype.getWebContentsId = function () {
const internal = v8Util.getHiddenValue<WebViewImpl>(this, 'internal')
const internal = v8Util.getHiddenValue<WebViewImpl>(this, 'internal');
if (!internal.guestInstanceId) {
throw new Error('The WebView must be attached to the DOM and the dom-ready event emitted before this method can be called.')
throw new Error('The WebView must be attached to the DOM and the dom-ready event emitted before this method can be called.');
}
return internal.guestInstanceId
}
return internal.guestInstanceId;
};
// Focusing the webview should move page focus to the underlying iframe.
WebViewElement.prototype.focus = function () {
this.contentWindow.focus()
}
this.contentWindow.focus();
};
// Forward proto.foo* method calls to WebViewImpl.foo*.
const createBlockHandler = function (method: string) {
return function (this: ElectronInternal.WebViewElement, ...args: Array<any>) {
return ipcRendererUtils.invokeSync('ELECTRON_GUEST_VIEW_MANAGER_CALL', this.getWebContentsId(), method, args)
}
}
return ipcRendererUtils.invokeSync('ELECTRON_GUEST_VIEW_MANAGER_CALL', this.getWebContentsId(), method, args);
};
};
for (const method of syncMethods) {
(WebViewElement.prototype as Record<string, any>)[method] = createBlockHandler(method)
(WebViewElement.prototype as Record<string, any>)[method] = createBlockHandler(method);
}
const createNonBlockHandler = function (method: string) {
return function (this: ElectronInternal.WebViewElement, ...args: Array<any>) {
return ipcRendererInternal.invoke('ELECTRON_GUEST_VIEW_MANAGER_CALL', this.getWebContentsId(), method, args)
}
}
return ipcRendererInternal.invoke('ELECTRON_GUEST_VIEW_MANAGER_CALL', this.getWebContentsId(), method, args);
};
};
for (const method of asyncMethods) {
(WebViewElement.prototype as Record<string, any>)[method] = createNonBlockHandler(method)
(WebViewElement.prototype as Record<string, any>)[method] = createNonBlockHandler(method);
}
WebViewElement.prototype.capturePage = async function (...args) {
return deserialize(await ipcRendererInternal.invoke('ELECTRON_GUEST_VIEW_MANAGER_CAPTURE_PAGE', this.getWebContentsId(), args))
}
return deserialize(await ipcRendererInternal.invoke('ELECTRON_GUEST_VIEW_MANAGER_CAPTURE_PAGE', this.getWebContentsId(), args));
};
const createPropertyGetter = function (property: string) {
return function (this: ElectronInternal.WebViewElement) {
return ipcRendererUtils.invokeSync('ELECTRON_GUEST_VIEW_MANAGER_PROPERTY_GET', this.getWebContentsId(), property)
}
}
return ipcRendererUtils.invokeSync('ELECTRON_GUEST_VIEW_MANAGER_PROPERTY_GET', this.getWebContentsId(), property);
};
};
const createPropertySetter = function (property: string) {
return function (this: ElectronInternal.WebViewElement, arg: any) {
return ipcRendererUtils.invokeSync('ELECTRON_GUEST_VIEW_MANAGER_PROPERTY_SET', this.getWebContentsId(), property, arg)
}
}
return ipcRendererUtils.invokeSync('ELECTRON_GUEST_VIEW_MANAGER_PROPERTY_SET', this.getWebContentsId(), property, arg);
};
};
for (const property of properties) {
Object.defineProperty(WebViewElement.prototype, property, {
get: createPropertyGetter(property) as any,
set: createPropertySetter(property)
})
});
}
}
};
export const webViewImplModule = {
setupAttributes,
@ -276,4 +276,4 @@ export const webViewImplModule = {
guestViewInternal,
webFrame,
WebViewImpl
}
};

View file

@ -1,18 +1,18 @@
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
const v8Util = process.electronBinding('v8_util')
const v8Util = process.electronBinding('v8_util');
function handleFocusBlur (guestInstanceId: number) {
// Note that while Chromium content APIs have observer for focus/blur, they
// unfortunately do not work for webview.
window.addEventListener('focus', () => {
ipcRendererInternal.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', true, guestInstanceId)
})
ipcRendererInternal.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', true, guestInstanceId);
});
window.addEventListener('blur', () => {
ipcRendererInternal.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', false, guestInstanceId)
})
ipcRendererInternal.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', false, guestInstanceId);
});
}
export function webViewInit (
@ -20,17 +20,17 @@ export function webViewInit (
) {
// Don't allow recursive `<webview>`.
if (webviewTag && guestInstanceId == null) {
const { webViewImplModule } = require('@electron/internal/renderer/web-view/web-view-impl')
const { webViewImplModule } = require('@electron/internal/renderer/web-view/web-view-impl');
if (contextIsolation) {
v8Util.setHiddenValue(window, 'web-view-impl', webViewImplModule)
v8Util.setHiddenValue(window, 'web-view-impl', webViewImplModule);
} else {
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element')
setupWebView(v8Util, webViewImplModule)
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element');
setupWebView(v8Util, webViewImplModule);
}
}
if (guestInstanceId) {
// Report focus/blur events of webview to browser.
handleFocusBlur(guestInstanceId)
handleFocusBlur(guestInstanceId);
}
}

View file

@ -7,12 +7,12 @@
// Rip global off of window (which is also global) so that webpack doesn't
// auto replace it with a looped reference to this file
const _global = (self as any || window as any).global as NodeJS.Global
const process = _global.process
const Buffer = _global.Buffer
const _global = (self as any || window as any).global as NodeJS.Global;
const process = _global.process;
const Buffer = _global.Buffer;
export {
_global,
process,
Buffer
}
};

View file

@ -1,5 +1,5 @@
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils'
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
// This file implements the following APIs:
// - window.history.back()
@ -19,29 +19,29 @@ import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-inte
// - document.visibilityState
// Helper function to resolve relative url.
const resolveURL = (url: string, base: string) => new URL(url, base).href
const resolveURL = (url: string, base: string) => new URL(url, base).href;
// Use this method to ensure values expected as strings in the main process
// are convertible to strings in the renderer process. This ensures exceptions
// converting values to strings are thrown in this process.
const toString = (value: any) => {
return value != null ? `${value}` : value
}
return value != null ? `${value}` : value;
};
const windowProxies = new Map<number, BrowserWindowProxy>()
const windowProxies = new Map<number, BrowserWindowProxy>();
const getOrCreateProxy = (guestId: number) => {
let proxy = windowProxies.get(guestId)
let proxy = windowProxies.get(guestId);
if (proxy == null) {
proxy = new BrowserWindowProxy(guestId)
windowProxies.set(guestId, proxy)
proxy = new BrowserWindowProxy(guestId);
windowProxies.set(guestId, proxy);
}
return proxy
}
return proxy;
};
const removeProxy = (guestId: number) => {
windowProxies.delete(guestId)
}
windowProxies.delete(guestId);
};
type LocationProperties = 'hash' | 'href' | 'host' | 'hostname' | 'origin' | 'pathname' | 'port' | 'protocol' | 'search'
@ -65,52 +65,52 @@ class LocationProxy {
private static ProxyProperty<T> (target: LocationProxy, propertyKey: LocationProperties) {
Object.defineProperty(target, propertyKey, {
get: function (this: LocationProxy): T | string {
const guestURL = this.getGuestURL()
const value = guestURL ? guestURL[propertyKey] : ''
return value === undefined ? '' : value
const guestURL = this.getGuestURL();
const value = guestURL ? guestURL[propertyKey] : '';
return value === undefined ? '' : value;
},
set: function (this: LocationProxy, newVal: T) {
const guestURL = this.getGuestURL()
const guestURL = this.getGuestURL();
if (guestURL) {
// TypeScript doesn't want us to assign to read-only variables.
// It's right, that's bad, but we're doing it anway.
(guestURL as any)[propertyKey] = newVal
(guestURL as any)[propertyKey] = newVal;
return this._invokeWebContentsMethod('loadURL', guestURL.toString())
return this._invokeWebContentsMethod('loadURL', guestURL.toString());
}
}
})
});
}
constructor (guestId: number) {
// eslint will consider the constructor "useless"
// unless we assign them in the body. It's fine, that's what
// TS would do anyway.
this.guestId = guestId
this.getGuestURL = this.getGuestURL.bind(this)
this.guestId = guestId;
this.getGuestURL = this.getGuestURL.bind(this);
}
public toString (): string {
return this.href
return this.href;
}
private getGuestURL (): URL | null {
const urlString = this._invokeWebContentsMethodSync('getURL') as string
const urlString = this._invokeWebContentsMethodSync('getURL') as string;
try {
return new URL(urlString)
return new URL(urlString);
} catch (e) {
console.error('LocationProxy: failed to parse string', urlString, e)
console.error('LocationProxy: failed to parse string', urlString, e);
}
return null
return null;
}
private _invokeWebContentsMethod (method: string, ...args: any[]) {
return ipcRendererInternal.invoke('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, method, ...args)
return ipcRendererInternal.invoke('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, method, ...args);
}
private _invokeWebContentsMethodSync (method: string, ...args: any[]) {
return ipcRendererUtils.invokeSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, method, ...args)
return ipcRendererUtils.invokeSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, method, ...args);
}
}
@ -124,54 +124,54 @@ class BrowserWindowProxy {
// so for now, we'll have to make do with an "any" in the mix.
// https://github.com/Microsoft/TypeScript/issues/2521
public get location (): LocationProxy | any {
return this._location
return this._location;
}
public set location (url: string | any) {
url = resolveURL(url, this.location.href)
this._invokeWebContentsMethod('loadURL', url)
url = resolveURL(url, this.location.href);
this._invokeWebContentsMethod('loadURL', url);
}
constructor (guestId: number) {
this.guestId = guestId
this._location = new LocationProxy(guestId)
this.guestId = guestId;
this._location = new LocationProxy(guestId);
ipcRendererInternal.once(`ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_${guestId}`, () => {
removeProxy(guestId)
this.closed = true
})
removeProxy(guestId);
this.closed = true;
});
}
public close () {
this._invokeWindowMethod('destroy')
this._invokeWindowMethod('destroy');
}
public focus () {
this._invokeWindowMethod('focus')
this._invokeWindowMethod('focus');
}
public blur () {
this._invokeWindowMethod('blur')
this._invokeWindowMethod('blur');
}
public print () {
this._invokeWebContentsMethod('print')
this._invokeWebContentsMethod('print');
}
public postMessage (message: any, targetOrigin: string) {
ipcRendererInternal.invoke('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', this.guestId, message, toString(targetOrigin), window.location.origin)
ipcRendererInternal.invoke('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', this.guestId, message, toString(targetOrigin), window.location.origin);
}
public eval (code: string) {
this._invokeWebContentsMethod('executeJavaScript', code)
this._invokeWebContentsMethod('executeJavaScript', code);
}
private _invokeWindowMethod (method: string, ...args: any[]) {
return ipcRendererInternal.invoke('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, method, ...args)
return ipcRendererInternal.invoke('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, method, ...args);
}
private _invokeWebContentsMethod (method: string, ...args: any[]) {
return ipcRendererInternal.invoke('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, method, ...args)
return ipcRendererInternal.invoke('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, method, ...args);
}
}
@ -181,33 +181,33 @@ export const windowSetup = (
if (!process.sandboxed && guestInstanceId == null) {
// Override default window.close.
window.close = function () {
ipcRendererInternal.send('ELECTRON_BROWSER_WINDOW_CLOSE')
}
ipcRendererInternal.send('ELECTRON_BROWSER_WINDOW_CLOSE');
};
}
if (!usesNativeWindowOpen) {
// Make the browser window or guest view emit "new-window" event.
(window as any).open = function (url?: string, frameName?: string, features?: string) {
if (url != null && url !== '') {
url = resolveURL(url, location.href)
url = resolveURL(url, location.href);
}
const guestId = ipcRendererInternal.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, toString(frameName), toString(features))
const guestId = ipcRendererInternal.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, toString(frameName), toString(features));
if (guestId != null) {
return getOrCreateProxy(guestId)
return getOrCreateProxy(guestId);
} else {
return null
return null;
}
}
};
}
if (openerId != null) {
window.opener = getOrCreateProxy(openerId)
window.opener = getOrCreateProxy(openerId);
}
// But we do not support prompt().
window.prompt = function () {
throw new Error('prompt() is and will not be supported.')
}
throw new Error('prompt() is and will not be supported.');
};
if (!usesNativeWindowOpen || openerId != null) {
ipcRendererInternal.on('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function (
@ -219,36 +219,36 @@ export const windowSetup = (
// Why any? We can't construct a MessageEvent and we can't
// use `as MessageEvent` because you're not supposed to override
// data, origin, and source
const event: any = document.createEvent('Event')
event.initEvent('message', false, false)
const event: any = document.createEvent('Event');
event.initEvent('message', false, false);
event.data = message
event.origin = sourceOrigin
event.source = getOrCreateProxy(sourceId)
event.data = message;
event.origin = sourceOrigin;
event.source = getOrCreateProxy(sourceId);
window.dispatchEvent(event as MessageEvent)
})
window.dispatchEvent(event as MessageEvent);
});
}
if (!process.sandboxed && !rendererProcessReuseEnabled) {
window.history.back = function () {
ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK')
}
ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK');
};
window.history.forward = function () {
ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_FORWARD')
}
ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_FORWARD');
};
window.history.go = function (offset: number) {
ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_TO_OFFSET', +offset)
}
ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_TO_OFFSET', +offset);
};
Object.defineProperty(window.history, 'length', {
get: function () {
return ipcRendererInternal.sendSync('ELECTRON_NAVIGATION_CONTROLLER_LENGTH')
return ipcRendererInternal.sendSync('ELECTRON_NAVIGATION_CONTROLLER_LENGTH');
},
set () {}
})
});
}
if (guestInstanceId != null) {
@ -259,27 +259,27 @@ export const windowSetup = (
// Note that this results in duplicate visibilitychange events (since
// Chromium also fires them) and potentially incorrect visibility change.
// We should reconsider this decision for Electron 2.0.
let cachedVisibilityState = isHiddenPage ? 'hidden' : 'visible'
let cachedVisibilityState = isHiddenPage ? 'hidden' : 'visible';
// Subscribe to visibilityState changes.
ipcRendererInternal.on('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', function (_event, visibilityState: VisibilityState) {
if (cachedVisibilityState !== visibilityState) {
cachedVisibilityState = visibilityState
document.dispatchEvent(new Event('visibilitychange'))
cachedVisibilityState = visibilityState;
document.dispatchEvent(new Event('visibilitychange'));
}
})
});
// Make document.hidden and document.visibilityState return the correct value.
Object.defineProperty(document, 'hidden', {
get: function () {
return cachedVisibilityState !== 'visible'
return cachedVisibilityState !== 'visible';
}
})
});
Object.defineProperty(document, 'visibilityState', {
get: function () {
return cachedVisibilityState
return cachedVisibilityState;
}
})
});
}
}
};