Changes of Linux development v0.0.64

This commit is contained in:
root 2019-04-05 01:07:03 +02:00
parent aea9939f13
commit efd2dc31ab
46 changed files with 985 additions and 26282 deletions

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,7 @@
'use strict'
const bindings = process.atomBinding('app')
const commandLine = process.atomBinding('command_line')
const path = require('path')
const { app, App } = bindings
@ -25,24 +26,21 @@ Object.assign(app, {
return Menu.getApplicationMenu()
},
commandLine: {
appendSwitch (...args) {
const castedArgs = args.map((arg) => {
return typeof arg !== 'string' ? `${arg}` : arg
})
return bindings.appendSwitch(...castedArgs)
},
appendArgument (...args) {
const castedArgs = args.map((arg) => {
return typeof arg !== 'string' ? `${arg}` : arg
})
return bindings.appendArgument(...castedArgs)
}
hasSwitch: (...args) => commandLine.hasSwitch(...args.map(String)),
getSwitchValue: (...args) => commandLine.getSwitchValue(...args.map(String)),
appendSwitch: (...args) => commandLine.appendSwitch(...args.map(String)),
appendArgument: (...args) => commandLine.appendArgument(...args.map(String))
},
enableMixedSandbox () {
deprecate.log(`'enableMixedSandbox' is deprecated. Mixed-sandbox mode is now enabled by default. You can safely remove the call to enableMixedSandbox().`)
}
})
const nativeFn = app.getAppMetrics
app.getFileIcon = deprecate.promisify(app.getFileIcon)
const nativeAppMetrics = app.getAppMetrics
app.getAppMetrics = () => {
const metrics = nativeFn.call(app)
const metrics = nativeAppMetrics.call(app)
for (const metric of metrics) {
if ('memory' in metric) {
deprecate.removeProperty(metric, 'memory')
@ -93,9 +91,7 @@ if (process.platform === 'linux') {
}
app.allowNTLMCredentialsForAllDomains = function (allow) {
if (!process.noDeprecations) {
deprecate.warn('app.allowNTLMCredentialsForAllDomains', 'session.allowNTLMCredentialsForDomains')
}
deprecate.warn('app.allowNTLMCredentialsForAllDomains', 'session.allowNTLMCredentialsForDomains')
const domains = allow ? '*' : ''
if (!this.isReady()) {
this.commandLine.appendSwitch('auth-server-whitelist', domains)

View file

@ -1,3 +1,9 @@
'use strict'
const { deprecate } = require('electron')
const contentTracing = process.atomBinding('content_tracing')
module.exports = process.atomBinding('content_tracing')
contentTracing.startRecording = deprecate.promisify(contentTracing.startRecording)
contentTracing.stopRecording = deprecate.promisify(contentTracing.stopRecording)
contentTracing.getCategories = deprecate.promisify(contentTracing.getCategories)
module.exports = contentTracing

View file

@ -1,13 +1,11 @@
'use strict'
const CrashReporter = require('@electron/internal/common/crash-reporter')
const ipcMain = require('@electron/internal/browser/ipc-main-internal')
const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init')
class CrashReporterMain extends CrashReporter {
sendSync (channel, ...args) {
const event = {}
ipcMain.emit(channel, event, ...args)
return event.returnValue
init (options) {
return crashReporterInit(options)
}
}

View file

@ -2,14 +2,18 @@
const { app } = require('electron')
const isMac = process.platform === 'darwin'
const isWindows = process.platform === 'win32'
const isLinux = process.platform === 'linux'
const roles = {
about: {
get label () {
return process.platform === 'linux' ? 'About' : `About ${app.getName()}`
return isLinux ? 'About' : `About ${app.getName()}`
}
},
close: {
label: process.platform === 'darwin' ? 'Close Window' : 'Close',
label: isMac ? 'Close Window' : 'Close',
accelerator: 'CommandOrControl+W',
windowMethod: 'close'
},
@ -78,12 +82,12 @@ const roles = {
default: return 'Quit'
}
},
accelerator: process.platform === 'win32' ? null : 'CommandOrControl+Q',
accelerator: isWindows ? null : 'CommandOrControl+Q',
appMethod: 'quit'
},
redo: {
label: 'Redo',
accelerator: process.platform === 'win32' ? 'Control+Y' : 'Shift+CommandOrControl+Z',
accelerator: isWindows ? 'Control+Y' : 'Shift+CommandOrControl+Z',
webContentsMethod: 'redo'
},
reload: {
@ -122,13 +126,13 @@ const roles = {
},
toggledevtools: {
label: 'Toggle Developer Tools',
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
accelerator: isMac ? 'Alt+Command+I' : 'Ctrl+Shift+I',
nonNativeMacOSRole: true,
windowMethod: 'toggleDevTools'
},
togglefullscreen: {
label: 'Toggle Full Screen',
accelerator: process.platform === 'darwin' ? 'Control+Command+F' : 'F11',
accelerator: isMac ? 'Control+Command+F' : 'F11',
windowMethod: (window) => {
window.setFullScreen(!window.isFullScreen())
}
@ -152,9 +156,8 @@ const roles = {
accelerator: 'CommandOrControl+Plus',
nonNativeMacOSRole: true,
webContentsMethod: (webContents) => {
webContents.getZoomLevel((zoomLevel) => {
webContents.setZoomLevel(zoomLevel + 0.5)
})
const zoomLevel = webContents.getZoomLevel()
webContents.setZoomLevel(zoomLevel + 0.5)
}
},
zoomout: {
@ -162,78 +165,97 @@ const roles = {
accelerator: 'CommandOrControl+-',
nonNativeMacOSRole: true,
webContentsMethod: (webContents) => {
webContents.getZoomLevel((zoomLevel) => {
webContents.setZoomLevel(zoomLevel - 0.5)
})
const zoomLevel = webContents.getZoomLevel()
webContents.setZoomLevel(zoomLevel - 0.5)
}
},
// Edit submenu (should fit both Mac & Windows)
// App submenu should be used for Mac only
appmenu: {
get label () {
return app.getName()
},
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideothers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
},
// File submenu
filemenu: {
label: 'File',
submenu: [
isMac ? { role: 'close' } : { role: 'quit' }
]
},
// Edit submenu
editmenu: {
label: 'Edit',
submenu: [
{
role: 'undo'
},
{
role: 'redo'
},
{
type: 'separator'
},
{
role: 'cut'
},
{
role: 'copy'
},
{
role: 'paste'
},
process.platform === 'darwin' ? {
role: 'pasteAndMatchStyle'
} : null,
{
role: 'delete'
},
process.platform === 'win32' ? {
type: 'separator'
} : null,
{
role: 'selectAll'
}
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
...(isMac ? [
{ role: 'pasteAndMatchStyle' },
{ role: 'delete' },
{ role: 'selectAll' },
{ type: 'separator' },
{
label: 'Speech',
submenu: [
{ role: 'startspeaking' },
{ role: 'stopspeaking' }
]
}
] : [
{ role: 'delete' },
{ type: 'separator' },
{ role: 'selectAll' }
])
]
},
// Window submenu should be used for Mac only
// View submenu
viewmenu: {
label: 'View',
submenu: [
{ role: 'reload' },
{ role: 'forcereload' },
{ role: 'toggledevtools' },
{ type: 'separator' },
{ role: 'resetzoom' },
{ role: 'zoomin' },
{ role: 'zoomout' },
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
},
// Window submenu
windowmenu: {
label: 'Window',
submenu: [
{
role: 'minimize'
},
{
role: 'close'
},
process.platform === 'darwin' ? {
type: 'separator'
} : null,
process.platform === 'darwin' ? {
role: 'front'
} : null
{ role: 'minimize' },
{ role: 'zoom' },
...(isMac ? [
{ type: 'separator' },
{ role: 'front' }
] : [
{ role: 'close' }
])
]
}
}
const canExecuteRole = (role) => {
if (!roles.hasOwnProperty(role)) return false
if (process.platform !== 'darwin') return true
if (!isMac) return true
// macOS handles all roles natively except for a few
return roles[role].nonNativeMacOSRole

View file

@ -109,6 +109,12 @@ Menu.prototype.insert = function (pos, item) {
throw new TypeError('Invalid item')
}
if (pos < 0) {
throw new RangeError(`Position ${pos} cannot be less than 0`)
} else if (pos > this.getItemCount()) {
throw new RangeError(`Position ${pos} cannot be greater than the total MenuItem count`)
}
// insert item depending on its type
insertItemByType.call(this, item, pos)
@ -145,6 +151,8 @@ Menu.setApplicationMenu = function (menu) {
}
applicationMenu = menu
v8Util.setHiddenValue(global, 'applicationMenuSet', true)
if (process.platform === 'darwin') {
if (!menu) return
menu._callMenuWillShow()
@ -159,14 +167,20 @@ Menu.buildFromTemplate = function (template) {
if (!Array.isArray(template)) {
throw new TypeError('Invalid template for Menu: Menu template must be an array')
}
const menu = new Menu()
if (!areValidTemplateItems(template)) {
throw new TypeError('Invalid template for MenuItem: must have at least one of label, role or type')
}
const filtered = removeExtraSeparators(template)
const sorted = sortTemplate(filtered)
sorted.forEach((item) => menu.append(new MenuItem(item)))
const menu = new Menu()
sorted.forEach(item => {
if (item instanceof MenuItem) {
menu.append(item)
} else {
menu.append(new MenuItem(item))
}
})
return menu
}
@ -176,7 +190,11 @@ Menu.buildFromTemplate = function (template) {
// validate the template against having the wrong attribute
function areValidTemplateItems (template) {
return template.every(item =>
item != null && typeof item === 'object' && (item.hasOwnProperty('label') || item.hasOwnProperty('role') || item.type === 'separator'))
item != null &&
typeof item === 'object' &&
(item.hasOwnProperty('label') ||
item.hasOwnProperty('role') ||
item.type === 'separator'))
}
function sortTemplate (template) {

View file

@ -30,9 +30,7 @@ module.exports = [
{ name: 'Tray', file: 'tray' },
{ name: 'View', file: 'view' },
{ name: 'webContents', file: 'web-contents' },
{ name: 'WebContentsView', file: 'web-contents-view' },
// The internal modules, invisible unless you know their names.
{ name: 'NavigationController', file: 'navigation-controller', private: true }
{ name: 'WebContentsView', file: 'web-contents-view' }
]
if (features.isViewApiEnabled()) {
@ -41,6 +39,8 @@ if (features.isViewApiEnabled()) {
{ name: 'Button', file: 'views/button' },
{ name: 'LabelButton', file: 'views/label-button' },
{ name: 'LayoutManager', file: 'views/layout-manager' },
{ name: 'MdTextButton', file: 'views/md-text-button' },
{ name: 'ResizeArea', file: 'views/resize-area' },
{ name: 'TextField', file: 'views/text-field' }
)
}

View file

@ -1,7 +1,7 @@
'use strict'
// TODO(deepak1556): Deprecate and remove standalone netLog module,
// it is now a property of sessio module.
// it is now a property of session module.
const { app, session } = require('electron')
// Fallback to default session.
@ -10,8 +10,12 @@ Object.setPrototypeOf(module.exports, new Proxy({}, {
if (!app.isReady()) return
const netLog = session.defaultSession.netLog
if (!Object.getPrototypeOf(netLog).hasOwnProperty(property)) return
// check for properties on the prototype chain that aren't functions
if (typeof netLog[property] !== 'function') return netLog[property]
// Returning a native function directly would throw error.
return (...args) => netLog[property](...args)
},

View file

@ -22,4 +22,30 @@ if (process.platform === 'linux') {
})
}
// TODO(deepak1556): Deprecate async api in favor of sync version in 5.0
powerMonitor.querySystemIdleState = function (threshold, callback) {
if (typeof threshold !== 'number') {
throw new Error('Must pass threshold as a number')
}
if (typeof callback !== 'function') {
throw new Error('Must pass callback as a function argument')
}
const idleState = this._querySystemIdleState(threshold)
process.nextTick(() => callback(idleState))
}
// TODO(deepak1556): Deprecate async api in favor of sync version in 5.0
powerMonitor.querySystemIdleTime = function (callback) {
if (typeof callback !== 'function') {
throw new Error('Must pass function as an argument')
}
const idleTime = this._querySystemIdleTime()
process.nextTick(() => callback(idleTime))
}
module.exports = powerMonitor

View file

@ -1,8 +1,8 @@
'use strict'
const { EventEmitter } = require('events')
const { app } = require('electron')
const { fromPartition, Session, Cookies } = process.atomBinding('session')
const { app, deprecate } = require('electron')
const { fromPartition, Session, Cookies, Protocol } = process.atomBinding('session')
// Public API.
Object.defineProperties(exports, {
@ -22,3 +22,10 @@ Object.setPrototypeOf(Cookies.prototype, EventEmitter.prototype)
Session.prototype._init = function () {
app.emit('session-created', this)
}
Cookies.prototype.flushStore = deprecate.promisify(Cookies.prototype.flushStore)
Cookies.prototype.get = deprecate.promisify(Cookies.prototype.get)
Cookies.prototype.remove = deprecate.promisify(Cookies.prototype.remove)
Cookies.prototype.set = deprecate.promisify(Cookies.prototype.set)
Protocol.prototype.isProtocolHandled = deprecate.promisify(Protocol.prototype.isProtocolHandled)

View file

@ -31,12 +31,6 @@ class TouchBar extends EventEmitter {
let { items, escapeItem } = options
// FIXME Support array as first argument, remove in 2.0
if (Array.isArray(options)) {
items = options
escapeItem = null
}
if (!Array.isArray(items)) {
items = []
}

View file

@ -5,9 +5,9 @@ const { EventEmitter } = require('events')
const electron = require('electron')
const path = require('path')
const url = require('url')
const v8Util = process.atomBinding('v8_util')
const { app, ipcMain, session, NavigationController, deprecate } = electron
const { app, ipcMain, session, deprecate } = electron
const NavigationController = require('@electron/internal/browser/navigation-controller')
const ipcMainInternal = require('@electron/internal/browser/ipc-main-internal')
const errorUtils = require('@electron/internal/common/error-utils')
@ -78,6 +78,7 @@ const defaultPrintingSetting = {
printWithCloudPrint: false,
printWithPrivet: false,
printWithExtension: false,
pagesPerSheet: 1,
deviceName: 'Save as PDF',
generateDraftData: true,
fitToPageEnabled: false,
@ -111,6 +112,7 @@ WebContents.prototype.send = function (channel, ...args) {
return this._send(internal, sendToAll, channel, args)
}
WebContents.prototype.sendToAll = function (channel, ...args) {
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument')
@ -142,6 +144,30 @@ WebContents.prototype._sendInternalToAll = function (channel, ...args) {
return this._send(internal, sendToAll, channel, args)
}
WebContents.prototype.sendToFrame = function (frameId, channel, ...args) {
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument')
} else if (typeof frameId !== 'number') {
throw new Error('Missing required frameId argument')
}
const internal = false
const sendToAll = false
return this._sendToFrame(internal, sendToAll, frameId, channel, args)
}
WebContents.prototype._sendToFrameInternal = function (frameId, channel, ...args) {
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument')
} else if (typeof frameId !== 'number') {
throw new Error('Missing required frameId argument')
}
const internal = true
const sendToAll = false
return this._sendToFrame(internal, sendToAll, frameId, channel, args)
}
// Following methods are mapped to webFrame.
const webFrameMethods = [
@ -197,6 +223,30 @@ WebContents.prototype.executeJavaScript = function (code, hasUserGesture, callba
}
}
// TODO(codebytere): remove when promisifications is complete
const nativeZoomLevel = WebContents.prototype.getZoomLevel
WebContents.prototype.getZoomLevel = function (callback) {
if (callback == null) {
return nativeZoomLevel.call(this)
} else {
process.nextTick(() => {
callback(nativeZoomLevel.call(this))
})
}
}
// TODO(codebytere): remove when promisifications is complete
const nativeZoomFactor = WebContents.prototype.getZoomFactor
WebContents.prototype.getZoomFactor = function (callback) {
if (callback == null) {
return nativeZoomFactor.call(this)
} else {
process.nextTick(() => {
callback(nativeZoomFactor.call(this))
})
}
}
WebContents.prototype.takeHeapSnapshot = function (filePath) {
return new Promise((resolve, reject) => {
const channel = `ELECTRON_TAKE_HEAP_SNAPSHOT_RESULT_${getNextId()}`
@ -277,16 +327,6 @@ WebContents.prototype.getPrinters = function () {
}
}
WebContents.prototype.getZoomLevel = function (callback) {
if (typeof callback !== 'function') {
throw new Error('Must pass function as an argument')
}
process.nextTick(() => {
const zoomLevel = this._getZoomLevel()
callback(zoomLevel)
})
}
WebContents.prototype.loadFile = function (filePath, options = {}) {
if (typeof filePath !== 'string') {
throw new Error('Must pass filePath as a string')
@ -303,22 +343,20 @@ WebContents.prototype.loadFile = function (filePath, options = {}) {
}))
}
WebContents.prototype.getZoomFactor = function (callback) {
if (typeof callback !== 'function') {
throw new Error('Must pass function as an argument')
const addReplyToEvent = (event) => {
event.reply = (...args) => {
event.sender.sendToFrame(event.frameId, ...args)
}
process.nextTick(() => {
const zoomFactor = this._getZoomFactor()
callback(zoomFactor)
})
}
WebContents.prototype.findInPage = function (text, options = {}) {
// TODO (nitsakh): Remove in 5.0
if (options.wordStart != null || options.medialCapitalAtWordStart != null) {
deprecate.log('wordStart and medialCapitalAtWordStart options are deprecated')
}
return this._findInPage(text, options)
const addReplyInternalToEvent = (event) => {
Object.defineProperty(event, '_replyInternal', {
configurable: false,
enumerable: false,
value: (...args) => {
event.sender._sendToFrameInternal(event.frameId, ...args)
}
})
}
const safeProtocols = new Set([
@ -337,27 +375,38 @@ WebContents.prototype._init = function () {
// The navigation controller.
NavigationController.call(this, this)
// Every remote callback from renderer process would add a listenter to the
// render-view-deleted event, so ignore the listenters warning.
// Every remote callback from renderer process would add a listener to the
// render-view-deleted event, so ignore the listeners warning.
this.setMaxListeners(0)
this.capturePage = deprecate.promisify(this.capturePage)
this.hasServiceWorker = deprecate.function(this.hasServiceWorker)
this.unregisterServiceWorker = deprecate.function(this.unregisterServiceWorker)
// Dispatch IPC messages to the ipc module.
this.on('ipc-message', function (event, [channel, ...args]) {
this.on('-ipc-message', function (event, [channel, ...args]) {
addReplyToEvent(event)
this.emit('ipc-message', event, channel, ...args)
ipcMain.emit(channel, event, ...args)
})
this.on('ipc-message-sync', function (event, [channel, ...args]) {
this.on('-ipc-message-sync', function (event, [channel, ...args]) {
Object.defineProperty(event, 'returnValue', {
set: function (value) {
return event.sendReply([value])
},
get: function () {}
})
addReplyToEvent(event)
this.emit('ipc-message-sync', event, channel, ...args)
ipcMain.emit(channel, event, ...args)
})
this.on('ipc-internal-message', function (event, [channel, ...args]) {
addReplyInternalToEvent(event)
ipcMainInternal.emit(channel, event, ...args)
})
this.on('ipc-internal-message-sync', function (event, [channel, ...args]) {
Object.defineProperty(event, 'returnValue', {
set: function (value) {
@ -365,6 +414,7 @@ WebContents.prototype._init = function () {
},
get: function () {}
})
addReplyInternalToEvent(event)
ipcMainInternal.emit(channel, event, ...args)
})
@ -381,6 +431,7 @@ WebContents.prototype._init = function () {
})
const forwardedEvents = [
'desktop-capturer-get-sources',
'remote-require',
'remote-get-global',
'remote-get-builtin',
@ -407,8 +458,8 @@ WebContents.prototype._init = function () {
// Handle window.open for BrowserWindow and BrowserView.
if (['browserView', 'window'].includes(this.getType())) {
// Make new windows requested by links behave like "window.open"
this.webContents.on('-new-window', (event, url, frameName, disposition,
// Make new windows requested by links behave like "window.open".
this.on('-new-window', (event, url, frameName, disposition,
additionalFeatures, postData,
referrer) => {
const options = {
@ -421,42 +472,16 @@ WebContents.prototype._init = function () {
options, additionalFeatures, postData)
})
this.webContents.on('-web-contents-created', (event, webContents, url,
frameName) => {
v8Util.setHiddenValue(webContents, 'url-framename', { url, frameName })
})
// Create a new browser window for the native implementation of
// "window.open", used in sandbox and nativeWindowOpen mode
this.webContents.on('-add-new-contents', (event, webContents, disposition,
userGesture, left, top, width,
height) => {
const urlFrameName = v8Util.getHiddenValue(webContents, 'url-framename')
// "window.open", used in sandbox and nativeWindowOpen mode.
this.on('-add-new-contents', (event, webContents, disposition,
userGesture, left, top, width, height, url, frameName) => {
if ((disposition !== 'foreground-tab' && disposition !== 'new-window' &&
disposition !== 'background-tab') || !urlFrameName) {
disposition !== 'background-tab')) {
event.preventDefault()
return
}
if (webContents.getLastWebPreferences().nodeIntegration === true) {
const message =
'Enabling Node.js integration in child windows opened with the ' +
'"nativeWindowOpen" option will cause memory leaks, please turn off ' +
'the "nodeIntegration" option.\\n' +
'From 5.x child windows opened with the "nativeWindowOpen" option ' +
'will always have Node.js integration disabled.\\n' +
'See https://github.com/electron/electron/pull/15076 for more.'
// console is only available after DOM is created.
const printWarning = () => this.webContents.executeJavaScript(`console.warn('${message}')`)
if (this.webContents.isDomReady()) {
printWarning()
} else {
this.webContents.once('dom-ready', printWarning)
}
}
const { url, frameName } = urlFrameName
v8Util.deleteHiddenValue(webContents, 'url-framename')
const options = {
show: true,
x: left,
@ -477,6 +502,8 @@ WebContents.prototype._init = function () {
// JavaScript wrapper of Debugger.
const { Debugger } = process.atomBinding('debugger')
Debugger.prototype.sendCommand = deprecate.promisify(Debugger.prototype.sendCommand)
Object.setPrototypeOf(Debugger.prototype, EventEmitter.prototype)
// Public APIs.

View file

@ -180,7 +180,7 @@ ipcMain.on('CHROME_RUNTIME_SENDMESSAGE', function (event, extensionId, message,
page.webContents._sendInternalToAll(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, event.sender.id, message, resultID)
ipcMain.once(`CHROME_RUNTIME_ONMESSAGE_RESULT_${resultID}`, (event, result) => {
event.sender._sendInternal(`CHROME_RUNTIME_SENDMESSAGE_RESULT_${originResultID}`, result)
event._replyInternal(`CHROME_RUNTIME_SENDMESSAGE_RESULT_${originResultID}`, result)
})
resultID++
})
@ -196,7 +196,7 @@ ipcMain.on('CHROME_TABS_SEND_MESSAGE', function (event, tabId, extensionId, isBa
contents._sendInternalToAll(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, senderTabId, message, resultID)
ipcMain.once(`CHROME_RUNTIME_ONMESSAGE_RESULT_${resultID}`, (event, result) => {
event.sender._sendInternal(`CHROME_TABS_SEND_MESSAGE_RESULT_${originResultID}`, result)
event._replyInternal(`CHROME_TABS_SEND_MESSAGE_RESULT_${originResultID}`, result)
})
resultID++
})
@ -298,7 +298,7 @@ const loadDevToolsExtensions = function (win, manifests) {
extensionInfoArray.forEach((extension) => {
win.devToolsWebContents._grantOriginAccess(extension.startPage)
})
win.devToolsWebContents.executeJavaScript(`DevToolsAPI.addExtensions(${JSON.stringify(extensionInfoArray)})`)
win.devToolsWebContents.executeJavaScript(`InspectorFrontendAPI.addExtensions(${JSON.stringify(extensionInfoArray)})`)
}
app.on('web-contents-created', function (event, webContents) {

View file

@ -0,0 +1,46 @@
'use strict'
const { app } = require('electron')
const cp = require('child_process')
const os = require('os')
const path = require('path')
const getTempDirectory = function () {
try {
return app.getPath('temp')
} catch (error) {
return os.tmpdir()
}
}
exports.crashReporterInit = function (options) {
const productName = options.productName || app.getName()
const crashesDirectory = path.join(getTempDirectory(), `${productName} Crashes`)
let crashServicePid
if (process.platform === 'win32') {
const env = {
ELECTRON_INTERNAL_CRASH_SERVICE: 1
}
const args = [
'--reporter-url=' + options.submitURL,
'--application-name=' + productName,
'--crashes-directory=' + crashesDirectory,
'--v=1'
]
const crashServiceProcess = cp.spawn(process.helperExecPath, args, {
env,
detached: true
})
crashServicePid = crashServiceProcess.pid
}
return {
productName,
crashesDirectory,
crashServicePid,
appVersion: app.getVersion()
}
}

View file

@ -0,0 +1,58 @@
'use strict'
const { shell, Menu } = require('electron')
const v8Util = process.atomBinding('v8_util')
const isMac = process.platform === 'darwin'
const setDefaultApplicationMenu = () => {
if (v8Util.getHiddenValue(global, 'applicationMenuSet')) return
const helpMenu = {
role: 'help',
submenu: [
{
label: 'Learn More',
click () {
shell.openExternalSync('https://electronjs.org')
}
},
{
label: 'Documentation',
click () {
shell.openExternalSync(
`https://github.com/electron/electron/tree/v${process.versions.electron}/docs#readme`
)
}
},
{
label: 'Community Discussions',
click () {
shell.openExternalSync('https://discuss.atom.io/c/electron')
}
},
{
label: 'Search Issues',
click () {
shell.openExternalSync('https://github.com/electron/electron/issues')
}
}
]
}
const template = [
...(isMac ? [{ role: 'appMenu' }] : []),
{ role: 'fileMenu' },
{ role: 'editMenu' },
{ role: 'viewMenu' },
{ role: 'windowMenu' },
helpMenu
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
}
module.exports = {
setDefaultApplicationMenu
}

View file

@ -1,7 +1,9 @@
'use strict'
const ipcMain = require('@electron/internal/browser/ipc-main-internal')
const { desktopCapturer } = process.atomBinding('desktop_capturer')
const eventBinding = process.atomBinding('event')
const deepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b)
@ -11,32 +13,40 @@ let requestsQueue = []
const electronSources = 'ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES'
const capturerResult = (id) => `ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_${id}`
ipcMain.on(electronSources, (event, captureWindow, captureScreen, thumbnailSize, id) => {
ipcMain.on(electronSources, (event, captureWindow, captureScreen, thumbnailSize, fetchWindowIcons, id) => {
const customEvent = eventBinding.createWithSender(event.sender)
event.sender.emit('desktop-capturer-get-sources', customEvent)
if (customEvent.defaultPrevented) {
event._replyInternal(capturerResult(id), [])
return
}
const request = {
id,
options: {
captureWindow,
captureScreen,
thumbnailSize
thumbnailSize,
fetchWindowIcons
},
webContents: event.sender
event
}
requestsQueue.push(request)
if (requestsQueue.length === 1) {
desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize)
desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize, fetchWindowIcons)
}
// If the WebContents is destroyed before receiving result, just remove the
// reference from requestsQueue to make the module not send the result to it.
event.sender.once('destroyed', () => {
request.webContents = null
request.event = null
})
})
desktopCapturer.emit = (event, name, sources) => {
desktopCapturer.emit = (event, name, sources, fetchWindowIcons) => {
// Receiving sources result from main process, now send them back to renderer.
const handledRequest = requestsQueue.shift()
const handledWebContents = handledRequest.webContents
const unhandledRequestsQueue = []
const result = sources.map(source => {
@ -44,20 +54,21 @@ desktopCapturer.emit = (event, name, sources) => {
id: source.id,
name: source.name,
thumbnail: source.thumbnail.toDataURL(),
display_id: source.display_id
display_id: source.display_id,
appIcon: (fetchWindowIcons && source.appIcon) ? source.appIcon.toDataURL() : null
}
})
if (handledWebContents) {
handledWebContents._sendInternal(capturerResult(handledRequest.id), result)
if (handledRequest.event) {
handledRequest.event._replyInternal(capturerResult(handledRequest.id), result)
}
// Check the queue to see whether there is another identical request & handle
requestsQueue.forEach(request => {
const webContents = request.webContents
const event = request.event
if (deepEqual(handledRequest.options, request.options)) {
if (webContents) {
webContents._sendInternal(capturerResult(request.id), result)
if (event) {
event._replyInternal(capturerResult(request.id), result)
}
} else {
unhandledRequestsQueue.push(request)
@ -67,7 +78,7 @@ desktopCapturer.emit = (event, name, sources) => {
// If the requestsQueue is not empty, start a new request handling.
if (requestsQueue.length > 0) {
const { captureWindow, captureScreen, thumbnailSize } = requestsQueue[0].options
return desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize)
const { captureWindow, captureScreen, thumbnailSize, fetchWindowIcons } = requestsQueue[0].options
return desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize, fetchWindowIcons)
}
}

View file

@ -163,14 +163,6 @@ const createGuest = function (embedder, params) {
}
}
})
guest.on('-web-contents-created', (...args) => {
if (guest.getLastWebPreferences().nativeWindowOpen === true) {
const embedder = getEmbedder(guestInstanceId)
if (embedder != null) {
embedder.emit('-web-contents-created', ...args)
}
}
})
return guestInstanceId
}
@ -189,7 +181,7 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn
const oldGuestInstance = guestInstances[oldGuestInstanceId]
if (oldGuestInstance) {
oldGuestInstance.guest.destroy()
oldGuestInstance.guest.detachFromOuterFrame()
}
}
@ -218,9 +210,10 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn
const webPreferences = {
guestInstanceId: guestInstanceId,
nodeIntegration: params.nodeintegration != null ? params.nodeintegration : false,
nodeIntegrationInSubFrames: params.nodeintegrationinsubframes != null ? params.nodeintegrationinsubframes : false,
enableRemoteModule: params.enableremotemodule,
plugins: params.plugins,
zoomFactor: embedder._getZoomFactor(),
zoomFactor: embedder.getZoomFactor(),
webSecurity: !params.disablewebsecurity,
enableBlinkFeatures: params.blinkfeatures,
disableBlinkFeatures: params.disableblinkfeatures
@ -254,7 +247,8 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn
['nativeWindowOpen', true],
['nodeIntegration', false],
['enableRemoteModule', false],
['sandbox', true]
['sandbox', true],
['nodeIntegrationInSubFrames', false]
])
// Inherit certain option values from embedder
@ -340,8 +334,8 @@ const isWebViewTagEnabledCache = new WeakMap()
const isWebViewTagEnabled = function (contents) {
if (!isWebViewTagEnabledCache.has(contents)) {
const value = contents.getLastWebPreferences().webviewTag
isWebViewTagEnabledCache.set(contents, value)
const webPreferences = contents.getLastWebPreferences() || {}
isWebViewTagEnabledCache.set(contents, !!webPreferences.webviewTag)
}
return isWebViewTagEnabledCache.get(contents)
@ -358,7 +352,7 @@ const handleMessage = function (channel, handler) {
}
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, params, requestId) {
event.sender._sendInternal(`ELECTRON_RESPONSE_${requestId}`, createGuest(event.sender, params))
event._replyInternal(`ELECTRON_RESPONSE_${requestId}`, createGuest(event.sender, params))
})
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST_SYNC', function (event, params) {
@ -368,7 +362,7 @@ handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST_SYNC', function (event,
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', function (event, guestInstanceId) {
try {
const guest = getGuestForWebContents(guestInstanceId, event.sender)
guest.destroy()
guest.detachFromOuterFrame()
} catch (error) {
console.error(`Guest destroy failed: ${error}`)
}
@ -408,7 +402,7 @@ handleMessage('ELECTRON_GUEST_VIEW_MANAGER_ASYNC_CALL', function (event, request
}, error => {
return [errorUtils.serialize(error)]
}).then(responseArgs => {
event.sender._sendInternal(`ELECTRON_GUEST_VIEW_MANAGER_ASYNC_CALL_RESPONSE_${requestId}`, ...responseArgs)
event._replyInternal(`ELECTRON_GUEST_VIEW_MANAGER_ASYNC_CALL_RESPONSE_${requestId}`, ...responseArgs)
})
})
@ -449,3 +443,4 @@ const getEmbedder = function (guestInstanceId) {
}
exports.getGuestForWebContents = getGuestForWebContents
exports.isWebViewTagEnabled = isWebViewTagEnabled

View file

@ -16,7 +16,8 @@ const inheritedWebPreferences = new Map([
['nodeIntegration', false],
['enableRemoteModule', false],
['sandbox', true],
['webviewTag', false]
['webviewTag', false],
['nodeIntegrationInSubFrames', false]
])
// Copy attribute of |parent| to |child| if it is not defined in |child|.
@ -90,9 +91,9 @@ const setupGuest = function (embedder, frameName, guest, options) {
}
const closedByUser = function () {
embedder._sendInternal('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId)
embedder.removeListener('render-view-deleted', closedByEmbedder)
embedder.removeListener('current-render-view-deleted', closedByEmbedder)
}
embedder.once('render-view-deleted', closedByEmbedder)
embedder.once('current-render-view-deleted', closedByEmbedder)
guest.once('closed', closedByUser)
if (frameName) {
frameToGuest.set(frameName, guest)
@ -118,28 +119,12 @@ const createGuest = function (embedder, url, referrer, frameName, options, postD
}
guest = new BrowserWindow(options)
if (!options.webContents || url !== 'about:blank') {
if (!options.webContents) {
// We should not call `loadURL` if the window was constructed from an
// existing webContents(window.open in a sandboxed renderer) and if the url
// is not 'about:blank'.
// existing webContents (window.open in a sandboxed renderer).
//
// Navigating to the url when creating the window from an existing
// webContents would not be necessary(it will navigate there anyway), but
// apparently there's a bug that allows the child window to be scripted by
// the opener, even when the child window is from another origin.
//
// That's why the second condition(url !== "about:blank") is required: to
// force `OverrideSiteInstanceForNavigation` to be called and consequently
// spawn a new renderer if the new window is targeting a different origin.
//
// If the URL is "about:blank", then it is very likely that the opener just
// wants to synchronously script the popup, for example:
//
// let popup = window.open()
// popup.document.body.write('<h1>hello</h1>')
//
// The above code would not work if a navigation to "about:blank" is done
// here, since the window would be cleared of all changes in the next tick.
// webContents is not necessary (it will navigate there anyway).
const loadOptions = {
httpReferrer: referrer
}

View file

@ -184,5 +184,17 @@ if (currentPlatformSupportsAppIndicator()) {
process.env.XDG_CURRENT_DESKTOP = 'Unity'
}
// Quit when all windows are closed and no other one is listening to this.
app.on('window-all-closed', () => {
if (app.listenerCount('window-all-closed') === 1) {
app.quit()
}
})
const { setDefaultApplicationMenu } = require('@electron/internal/browser/default-menu')
// Create default menu.
app.once('ready', setDefaultApplicationMenu)
// Finally load app's main.js and transfer control to C++.
Module._load(path.join(packagePath, mainStartupScript), Module, true)

View file

@ -63,9 +63,66 @@ const NavigationController = (function () {
if (options == null) {
options = {}
}
const p = new Promise((resolve, reject) => {
const resolveAndCleanup = () => {
removeListeners()
resolve()
}
const rejectAndCleanup = (errorCode, errorDescription, url) => {
const err = new Error(`${errorDescription} (${errorCode}) loading '${typeof url === 'string' ? url.substr(0, 2048) : url}'`)
Object.assign(err, { errno: errorCode, code: errorDescription, url })
removeListeners()
reject(err)
}
const finishListener = () => {
resolveAndCleanup()
}
const failListener = (event, errorCode, errorDescription, validatedURL, isMainFrame, frameProcessId, frameRoutingId) => {
if (isMainFrame) {
rejectAndCleanup(errorCode, errorDescription, validatedURL)
}
}
let navigationStarted = false
const navigationListener = (event, url, isSameDocument, isMainFrame, frameProcessId, frameRoutingId, navigationId) => {
if (isMainFrame) {
if (navigationStarted) {
// the webcontents has started another unrelated navigation in the
// main frame (probably from the app calling `loadURL` again); reject
// the promise
return rejectAndCleanup(-3, 'ERR_ABORTED', url)
}
navigationStarted = true
}
}
const stopLoadingListener = () => {
// By the time we get here, either 'finish' or 'fail' should have fired
// if the navigation occurred. However, in some situations (e.g. when
// attempting to load a page with a bad scheme), loading will stop
// without emitting finish or fail. In this case, we reject the promise
// with a generic failure.
// TODO(jeremy): enumerate all the cases in which this can happen. If
// the only one is with a bad scheme, perhaps ERR_INVALID_ARGUMENT
// would be more appropriate.
rejectAndCleanup(-2, 'ERR_FAILED', url)
}
const removeListeners = () => {
this.webContents.removeListener('did-finish-load', finishListener)
this.webContents.removeListener('did-fail-load', failListener)
this.webContents.removeListener('did-start-navigation', navigationListener)
this.webContents.removeListener('did-stop-loading', stopLoadingListener)
}
this.webContents.on('did-finish-load', finishListener)
this.webContents.on('did-fail-load', failListener)
this.webContents.on('did-start-navigation', navigationListener)
this.webContents.on('did-stop-loading', stopLoadingListener)
})
// Add a no-op rejection handler to silence the unhandled rejection error.
p.catch(() => {})
this.pendingIndex = -1
this.webContents._loadURL(url, options)
return this.webContents.emit('load-url', url, options)
this.webContents.emit('load-url', url, options)
return p
}
NavigationController.prototype.getURL = function () {

View file

@ -1,16 +1,14 @@
'use strict'
const { spawn } = require('child_process')
const electron = require('electron')
const { EventEmitter } = require('events')
const fs = require('fs')
const os = require('os')
const path = require('path')
const v8Util = process.atomBinding('v8_util')
const eventBinding = process.atomBinding('event')
const { isPromise } = electron
const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init')
const ipcMain = require('@electron/internal/browser/ipc-main-internal')
const objectsRegistry = require('@electron/internal/browser/objects-registry')
const guestViewManager = require('@electron/internal/browser/guest-view-manager')
@ -139,7 +137,7 @@ const plainObjectToMeta = function (obj) {
}
// Convert Error into meta data.
const exceptionToMeta = function (sender, contextId, error) {
const exceptionToMeta = function (error) {
return {
type: 'exception',
value: errorUtils.serialize(error)
@ -175,7 +173,7 @@ const removeRemoteListenersAndLogWarning = (sender, callIntoRenderer) => {
}
// Convert array of meta data from renderer into array of real values.
const unwrapArgs = function (sender, contextId, args) {
const unwrapArgs = function (sender, frameId, contextId, args) {
const metaToValue = function (meta) {
switch (meta.type) {
case 'value':
@ -183,7 +181,7 @@ const unwrapArgs = function (sender, contextId, args) {
case 'remote-object':
return objectsRegistry.get(meta.id)
case 'array':
return unwrapArgs(sender, contextId, meta.value)
return unwrapArgs(sender, frameId, contextId, meta.value)
case 'buffer':
return bufferUtils.metaToBuffer(meta.value)
case 'date':
@ -217,9 +215,11 @@ const unwrapArgs = function (sender, contextId, args) {
}
const callIntoRenderer = function (...args) {
let succeed = false
if (!sender.isDestroyed()) {
sender._sendInternal('ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args))
} else {
succeed = sender._sendToFrameInternal(frameId, 'ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args))
}
if (!succeed) {
removeRemoteListenersAndLogWarning(this, callIntoRenderer)
}
}
@ -274,7 +274,7 @@ const handleRemoteCommand = function (channel, handler) {
try {
returnValue = handler(event, contextId, ...args)
} catch (error) {
returnValue = exceptionToMeta(event.sender, contextId, error)
returnValue = exceptionToMeta(error)
}
if (returnValue !== undefined) {
@ -368,7 +368,7 @@ handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, co
})
handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) {
args = unwrapArgs(event.sender, contextId, args)
args = unwrapArgs(event.sender, event.frameId, contextId, args)
const constructor = objectsRegistry.get(id)
if (constructor == null) {
@ -379,7 +379,7 @@ handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId,
})
handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId, id, args) {
args = unwrapArgs(event.sender, contextId, args)
args = unwrapArgs(event.sender, event.frameId, contextId, args)
const func = objectsRegistry.get(id)
if (func == null) {
@ -390,7 +390,7 @@ handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId
})
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) {
args = unwrapArgs(event.sender, contextId, args)
args = unwrapArgs(event.sender, event.frameId, contextId, args)
const object = objectsRegistry.get(id)
if (object == null) {
@ -401,7 +401,7 @@ handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, cont
})
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, id, method, args) {
args = unwrapArgs(event.sender, contextId, args)
args = unwrapArgs(event.sender, event.frameId, contextId, args)
const obj = objectsRegistry.get(id)
if (obj == null) {
@ -412,7 +412,7 @@ handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId,
})
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) {
args = unwrapArgs(event.sender, contextId, args)
args = unwrapArgs(event.sender, event.frameId, contextId, args)
const obj = objectsRegistry.get(id)
if (obj == null) {
@ -468,46 +468,6 @@ ipcMain.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) {
event.returnValue = null
})
const getTempDirectory = function () {
try {
return electron.app.getPath('temp')
} catch (error) {
return os.tmpdir()
}
}
const crashReporterInit = function (options) {
const productName = options.productName || electron.app.getName()
const crashesDirectory = path.join(getTempDirectory(), `${productName} Crashes`)
let crashServicePid
if (process.platform === 'win32') {
const env = {
ELECTRON_INTERNAL_CRASH_SERVICE: 1
}
const args = [
'--reporter-url=' + options.submitURL,
'--application-name=' + productName,
'--crashes-directory=' + crashesDirectory,
'--v=1'
]
const crashServiceProcess = spawn(process.helperExecPath, args, {
env,
detached: true
})
crashServicePid = crashServiceProcess.pid
}
return {
productName,
crashesDirectory,
crashServicePid,
appVersion: electron.app.getVersion()
}
}
const setReturnValue = function (event, getValue) {
try {
event.returnValue = [null, getValue()]
@ -532,27 +492,40 @@ ipcMain.on('ELECTRON_BROWSER_CLIPBOARD_WRITE_FIND_TEXT', function (event, text)
setReturnValue(event, () => electron.clipboard.writeFindText(text))
})
ipcMain.on('ELECTRON_BROWSER_SANDBOX_LOAD', function (event) {
const preloadPath = event.sender._getPreloadPath()
const getPreloadScript = function (preloadPath) {
let preloadSrc = null
let preloadError = null
if (preloadPath) {
try {
preloadSrc = fs.readFileSync(preloadPath).toString()
} catch (err) {
preloadError = { stack: err ? err.stack : (new Error(`Failed to load "${preloadPath}"`)).stack }
preloadError = errorUtils.serialize(err)
}
}
return { preloadPath, preloadSrc, preloadError }
}
ipcMain.on('ELECTRON_BROWSER_SANDBOX_LOAD', function (event) {
const preloadPaths = [
...(event.sender.session ? event.sender.session.getPreloads() : []),
event.sender._getPreloadPath()
]
event.returnValue = {
preloadSrc,
preloadError,
preloadScripts: preloadPaths.map(path => getPreloadScript(path)),
isRemoteModuleEnabled: event.sender._isRemoteModuleEnabled(),
isWebViewTagEnabled: guestViewManager.isWebViewTagEnabled(event.sender),
process: {
arch: process.arch,
platform: process.platform,
env: process.env,
version: process.version,
versions: process.versions
versions: process.versions,
execPath: process.helperExecPath
}
}
})
ipcMain.on('ELECTRON_BROWSER_PRELOAD_ERROR', function (event, preloadPath, error) {
event.sender.emit('preload-error', event, preloadPath, errorUtils.deserialize(error))
})

View file

@ -2,8 +2,8 @@
if (process.platform === 'linux' && process.type === 'renderer') {
// On Linux we could not access clipboard in renderer process.
const { getRemoteForUsage } = require('@electron/internal/renderer/remote')
module.exports = getRemoteForUsage('clipboard').clipboard
const { getRemote } = require('@electron/internal/renderer/remote')
module.exports = getRemote('clipboard')
} else {
const clipboard = process.atomBinding('clipboard')

View file

@ -19,7 +19,9 @@ const deprecate = {
setHandler: (handler) => { deprecationHandler = handler },
getHandler: () => deprecationHandler,
warn: (oldName, newName) => {
return deprecate.log(`'${oldName}' is deprecated. Use '${newName}' instead.`)
if (!process.noDeprecation) {
deprecate.log(`'${oldName}' is deprecated. Use '${newName}' instead.`)
}
},
log: (message) => {
if (typeof deprecationHandler === 'function') {
@ -33,6 +35,14 @@ const deprecate = {
}
},
function: (fn, newName) => {
const warn = warnOnce(fn.name, newName)
return function () {
warn()
fn.apply(this, arguments)
}
},
event: (emitter, oldName, newName) => {
const warn = newName.startsWith('-') /* internal event */
? warnOnce(`${oldName} event`)
@ -67,6 +77,43 @@ const deprecate = {
})
},
function: (fn, newName) => {
// if newName is left blank, a removal warning will be displayed
const warn = warnOnce(fn.name, newName)
return function () {
if (!process.noDeprecation) warn()
return fn.apply(this, arguments)
}
},
promisify: (fn) => {
const fnName = fn.name || 'function'
const oldName = `${fnName} with callbacks`
const newName = `${fnName} with Promises`
const warn = warnOnce(oldName, newName)
return function (...params) {
let cb
if (params.length > 0 && typeof params[params.length - 1] === 'function') {
cb = params.pop()
}
const promise = fn.apply(this, params)
if (!cb) return promise
if (process.enablePromiseAPIs) warn()
return promise
.then(res => {
process.nextTick(() => {
cb.length === 2 ? cb(null, res) : cb(res)
})
}, err => {
process.nextTick(() => {
cb.length === 2 ? cb(err) : cb()
})
})
}
},
renameProperty: (o, oldName, newName) => {
const warn = warnOnce(oldName, newName)

View file

@ -2,28 +2,16 @@
const binding = process.atomBinding('crash_reporter')
const errorUtils = require('@electron/internal/common/error-utils')
class CrashReporter {
contructor () {
this.productName = null
this.crashesDirectory = null
}
sendSync (channel, ...args) {
init (options) {
throw new Error('Not implemented')
}
invoke (command, ...args) {
const [ error, result ] = this.sendSync(command, ...args)
if (error) {
throw errorUtils.deserialize(error)
}
return result
}
start (options) {
if (options == null) options = {}
@ -51,7 +39,7 @@ class CrashReporter {
throw new Error('submitURL is a required option to crashReporter.start')
}
const ret = this.invoke('ELECTRON_CRASH_REPORTER_INIT', {
const ret = this.init({
submitURL,
productName
})

View file

@ -4,12 +4,12 @@
// - `features` input string
// - `emit` function(key, value) - called for each parsed KV
module.exports = function parseFeaturesString (features, emit) {
features = `${features}`
features = `${features}`.trim()
// split the string by ','
features.split(/,\s*/).forEach((feature) => {
features.split(/\s*,\s*/).forEach((feature) => {
// expected form is either a key by itself or a key/value pair in the form of
// 'key=value'
let [key, value] = feature.split(/\s*=/)
let [key, value] = feature.split(/\s*=\s*/)
if (!key) return
// interpret the value as a boolean, if possible

View file

@ -46,6 +46,8 @@ exports.syncMethods = new Set([
'downloadURL',
'inspectServiceWorker',
'showDefinitionForSelection',
'getZoomFactor',
'getZoomLevel',
'setZoomFactor',
'setZoomLevel',
'sendImeEvent'
@ -58,8 +60,6 @@ exports.asyncCallbackMethods = new Set([
'sendInputEvent',
'setLayoutZoomLevelLimits',
'setVisualZoomLevelLimits',
'getZoomFactor',
'getZoomLevel',
'print',
'printToPDF'
])

View file

@ -2,10 +2,21 @@
const CrashReporter = require('@electron/internal/common/crash-reporter')
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
const errorUtils = require('@electron/internal/common/error-utils')
const invoke = function (command, ...args) {
const [ error, result ] = ipcRenderer.sendSync(command, ...args)
if (error) {
throw errorUtils.deserialize(error)
}
return result
}
class CrashReporterRenderer extends CrashReporter {
sendSync (channel, ...args) {
return ipcRenderer.sendSync(channel, ...args)
init (options) {
return invoke('ELECTRON_CRASH_REPORTER_INIT', options)
}
}

View file

@ -1,6 +1,6 @@
'use strict'
const { nativeImage } = require('electron')
const { nativeImage, deprecate } = require('electron')
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
const includes = [].includes
@ -17,32 +17,43 @@ function isValid (options) {
return Array.isArray(types)
}
exports.getSources = function (options, callback) {
if (!isValid(options)) return callback(new Error('Invalid options'))
const captureWindow = includes.call(options.types, 'window')
const captureScreen = includes.call(options.types, 'screen')
function mapSources (sources) {
return sources.map(source => ({
id: source.id,
name: source.name,
thumbnail: nativeImage.createFromDataURL(source.thumbnail),
display_id: source.display_id,
appIcon: source.appIcon ? nativeImage.createFromDataURL(source.appIcon) : null
}))
}
if (options.thumbnailSize == null) {
options.thumbnailSize = {
width: 150,
height: 150
const getSources = (options) => {
return new Promise((resolve, reject) => {
if (!isValid(options)) throw new Error('Invalid options')
const captureWindow = includes.call(options.types, 'window')
const captureScreen = includes.call(options.types, 'screen')
if (options.thumbnailSize == null) {
options.thumbnailSize = {
width: 150,
height: 150
}
}
if (options.fetchWindowIcons == null) {
options.fetchWindowIcons = false
}
}
const id = incrementId()
ipcRenderer.send('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, options.thumbnailSize, id)
return ipcRenderer.once(`ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_${id}`, (event, sources) => {
callback(null, (() => {
const results = []
sources.forEach(source => {
results.push({
id: source.id,
name: source.name,
thumbnail: nativeImage.createFromDataURL(source.thumbnail),
display_id: source.display_id
})
})
return results
})())
const id = incrementId()
ipcRenderer.send('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, options.thumbnailSize, options.fetchWindowIcons, id)
return ipcRenderer.once(`ELECTRON_RENDERER_DESKTOP_CAPTURER_RESULT_${id}`, (event, sources) => {
try {
resolve(mapSources(sources))
} catch (error) {
reject(error)
}
})
})
}
exports.getSources = deprecate.promisify(getSources)

View file

@ -8,11 +8,11 @@ const ipcRenderer = v8Util.getHiddenValue(global, 'ipc')
const internal = false
ipcRenderer.send = function (...args) {
return binding.send('ipc-message', args)
return binding.send('-ipc-message', args)
}
ipcRenderer.sendSync = function (...args) {
return binding.sendSync('ipc-message-sync', args)[0]
return binding.sendSync('-ipc-message-sync', args)[0]
}
ipcRenderer.sendToHost = function (...args) {

View file

@ -311,7 +311,9 @@ exports.getCurrentWindow = () => {
// Get current WebContents object.
exports.getCurrentWebContents = () => {
return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', contextId))
const command = 'ELECTRON_BROWSER_CURRENT_WEB_CONTENTS'
const meta = ipcRenderer.sendSync(command, contextId)
return metaToValue(meta)
}
// Get a global object in browser.

View file

@ -1,4 +1,8 @@
'use strict'
const { getRemoteForUsage } = require('@electron/internal/renderer/remote')
module.exports = getRemoteForUsage('screen').screen
const { deprecate } = require('electron')
deprecate.warn(`electron.screen`, `electron.remote.screen`)
const { getRemote } = require('@electron/internal/renderer/remote')
module.exports = getRemote('screen')

View file

@ -2,6 +2,7 @@
const { EventEmitter } = require('events')
const binding = process.atomBinding('web_frame')
const { deprecate } = require('electron')
class WebFrame extends EventEmitter {
constructor (context) {
@ -47,6 +48,26 @@ class WebFrame extends EventEmitter {
get routingId () {
return binding._getRoutingId(this.context)
}
// Deprecations
// TODO(nitsakh): Remove in 6.0
setIsolatedWorldSecurityOrigin (worldId, securityOrigin) {
deprecate.warn('webFrame.setIsolatedWorldSecurityOrigin', 'webFrame.setIsolatedWorldInfo')
binding.setIsolatedWorldInfo(this.context, worldId, { securityOrigin })
}
setIsolatedWorldContentSecurityPolicy (worldId, csp) {
deprecate.warn('webFrame.setIsolatedWorldContentSecurityPolicy', 'webFrame.setIsolatedWorldInfo')
binding.setIsolatedWorldInfo(this.context, worldId, {
securityOrigin: window.location.origin,
csp
})
}
setIsolatedWorldHumanReadableName (worldId, name) {
deprecate.warn('webFrame.setIsolatedWorldHumanReadableName', 'webFrame.setIsolatedWorldInfo')
binding.setIsolatedWorldInfo(this.context, worldId, { name })
}
}
// Populate the methods.
@ -59,7 +80,7 @@ for (const name in binding) {
}
// Helper to return WebFrame or null depending on context.
// TODO(zcbenz): Consider returning same WebFrame for the same context.
// TODO(zcbenz): Consider returning same WebFrame for the same frame.
function getWebFrame (context) {
return context ? new WebFrame(context) : null
}

View file

@ -3,11 +3,15 @@
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
const { runInThisContext } = require('vm')
const escapePattern = function (pattern) {
return pattern.replace(/[\\^$+?.()|[\]{}]/g, '\\$&')
}
// Check whether pattern matches.
// https://developer.chrome.com/extensions/match_patterns
const matchesPattern = function (pattern) {
if (pattern === '<all_urls>') return true
const regexp = new RegExp(`^${pattern.replace(/\*/g, '.*')}$`)
const regexp = new RegExp(`^${pattern.split('*').map(escapePattern).join('.*')}$`)
const url = `${location.protocol}//${location.host}${location.pathname}`
return url.match(regexp)
}

View file

@ -6,9 +6,10 @@
// Does not implement predefined messages:
// https://developer.chrome.com/extensions/i18n#overview-predefined
const { potentiallyRemoteRequire } = require('@electron/internal/renderer/remote')
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
const fs = require('fs')
const path = require('path')
const fs = potentiallyRemoteRequire('fs')
const path = potentiallyRemoteRequire('path')
let metadata

View file

@ -1,9 +1,9 @@
'use strict'
const fs = require('fs')
const path = require('path')
const { remote } = require('electron')
const { app } = remote
const { getRemote, potentiallyRemoteRequire } = require('@electron/internal/renderer/remote')
const fs = potentiallyRemoteRequire('fs')
const path = potentiallyRemoteRequire('path')
const app = getRemote('app')
const getChromeStoragePath = (storageType, extensionId) => {
return path.join(

View file

@ -31,68 +31,67 @@ const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
require('@electron/internal/renderer/web-frame-init')()
// Process command line arguments.
let nodeIntegration = false
let webviewTag = false
let preloadScript = null
let preloadScripts = []
let isBackgroundPage = false
let appPath = null
for (const arg of process.argv) {
if (arg.indexOf('--guest-instance-id=') === 0) {
// This is a guest web view.
process.guestInstanceId = parseInt(arg.substr(arg.indexOf('=') + 1))
} else if (arg.indexOf('--opener-id=') === 0) {
// This is a guest BrowserWindow.
process.openerId = parseInt(arg.substr(arg.indexOf('=') + 1))
} else if (arg.indexOf('--node-integration=') === 0) {
nodeIntegration = arg.substr(arg.indexOf('=') + 1) === 'true'
} else if (arg.indexOf('--preload=') === 0) {
preloadScript = arg.substr(arg.indexOf('=') + 1)
} else if (arg === '--background-page') {
isBackgroundPage = true
} else if (arg.indexOf('--app-path=') === 0) {
appPath = arg.substr(arg.indexOf('=') + 1)
} else if (arg.indexOf('--webview-tag=') === 0) {
webviewTag = arg.substr(arg.indexOf('=') + 1) === 'true'
} else if (arg.indexOf('--preload-scripts') === 0) {
preloadScripts = arg.substr(arg.indexOf('=') + 1).split(path.delimiter)
}
const { hasSwitch, getSwitchValue } = process.atomBinding('command_line')
const parseOption = function (name, defaultValue, converter = value => value) {
return hasSwitch(name) ? converter(getSwitchValue(name)) : defaultValue
}
const contextIsolation = hasSwitch('context-isolation')
const nodeIntegration = hasSwitch('node-integration')
const webviewTag = hasSwitch('webview-tag')
const isHiddenPage = hasSwitch('hidden-page')
const isBackgroundPage = hasSwitch('background-page')
const usesNativeWindowOpen = hasSwitch('native-window-open')
const preloadScript = parseOption('preload', null)
const preloadScripts = parseOption('preload-scripts', [], value => value.split(path.delimiter))
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 = { ipcRenderer, guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen }
// The webContents preload script is loaded after the session preload scripts.
if (preloadScript) {
preloadScripts.push(preloadScript)
}
if (window.location.protocol === 'chrome-devtools:') {
// Override some inspector APIs.
require('@electron/internal/renderer/inspector')
nodeIntegration = false
} else if (window.location.protocol === 'chrome-extension:') {
// Add implementations of chrome API.
require('@electron/internal/renderer/chrome-api').injectTo(window.location.hostname, isBackgroundPage, window)
nodeIntegration = false
} else if (window.location.protocol === 'chrome:') {
// Disable node integration for chrome UI scheme.
nodeIntegration = false
} else {
// Override default web functions.
require('@electron/internal/renderer/override')
switch (window.location.protocol) {
case 'chrome-devtools:': {
// Override some inspector APIs.
require('@electron/internal/renderer/inspector')
break
}
case 'chrome-extension:': {
// Inject the chrome.* APIs that chrome extensions require
require('@electron/internal/renderer/chrome-api').injectTo(window.location.hostname, isBackgroundPage, window)
break
}
case 'chrome:':
break
default: {
// Override default web functions.
require('@electron/internal/renderer/window-setup')(ipcRenderer, guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen)
// Inject content scripts.
require('@electron/internal/renderer/content-scripts-injector')
// Load webview tag implementation.
if (webviewTag && process.guestInstanceId == null) {
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view')
if (process.argv.includes('--context-isolation')) {
v8Util.setHiddenValue(window, 'setup-webview', setupWebView)
} else {
setupWebView(window)
// Inject content scripts.
if (process.isMainFrame) {
require('@electron/internal/renderer/content-scripts-injector')
}
}
}
// Load webview tag implementation.
if (process.isMainFrame) {
require('@electron/internal/renderer/web-view/web-view-init')(contextIsolation, webviewTag, guestInstanceId)
}
// Pass the arguments to isolatedWorld.
if (contextIsolation) {
v8Util.setHiddenValue(global, 'isolated-world-args', isolatedWorldArgs)
}
if (nodeIntegration) {
// Export node bindings to global.
global.require = require
@ -150,27 +149,21 @@ if (nodeIntegration) {
})
}
const errorUtils = require('@electron/internal/common/error-utils')
// Load the preload scripts.
for (const preloadScript of preloadScripts) {
try {
require(preloadScript)
} catch (error) {
console.error('Unable to load preload script: ' + preloadScript)
console.error(error.stack || error.message)
console.error(`Unable to load preload script: ${preloadScript}`)
console.error(`${error}`)
ipcRenderer.send('ELECTRON_BROWSER_PRELOAD_ERROR', preloadScript, errorUtils.serialize(error))
}
}
// Warn about security issues
require('@electron/internal/renderer/security-warnings')(nodeIntegration)
// Report focus/blur events of webview to browser.
// Note that while Chromium content APIs have observer for focus/blur, they
// unfortunately do not work for webview.
if (process.guestInstanceId) {
window.addEventListener('focus', () => {
ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', true, process.guestInstanceId)
})
window.addEventListener('blur', () => {
ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', false, process.guestInstanceId)
})
if (process.isMainFrame) {
require('@electron/internal/renderer/security-warnings')(nodeIntegration)
}

View file

@ -1,5 +1,7 @@
'use strict'
const { getRemote, potentiallyRemoteRequire } = require('@electron/internal/renderer/remote')
window.onload = function () {
// Use menu API to show context menu.
window.InspectorFrontendHost.showContextMenuAtPoint = createMenu
@ -18,7 +20,7 @@ function completeURL (project, path) {
}
window.confirm = function (message, title) {
const { dialog } = require('electron').remote
const dialog = getRemote('dialog')
if (title == null) {
title = ''
}
@ -62,8 +64,7 @@ const convertToMenuTemplate = function (items) {
}
const createMenu = function (x, y, items) {
const { remote } = require('electron')
const { Menu } = remote
const Menu = getRemote('Menu')
let template = convertToMenuTemplate(items)
if (useEditMenuItems(x, y, template)) {
@ -73,7 +74,8 @@ const createMenu = function (x, y, items) {
// The menu is expected to show asynchronously.
setTimeout(function () {
menu.popup({ window: remote.getCurrentWindow() })
const getCurrentWindow = getRemote('getCurrentWindow')
menu.popup({ window: getCurrentWindow() })
})
}
@ -116,7 +118,7 @@ const getEditMenuItems = function () {
}
const showFileChooserDialog = function (callback) {
const { dialog } = require('electron').remote
const dialog = getRemote('dialog')
const files = dialog.showOpenDialog({})
if (files != null) {
callback(pathToHtml5FileObject(files[0]))
@ -124,7 +126,7 @@ const showFileChooserDialog = function (callback) {
}
const pathToHtml5FileObject = function (path) {
const fs = require('fs')
const fs = potentiallyRemoteRequire('fs')
const blob = new Blob([fs.readFileSync(path)])
blob.name = path
return blob

View file

@ -1,18 +0,0 @@
'use strict'
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
const v8Util = process.atomBinding('v8_util')
const { guestInstanceId, openerId } = process
const hiddenPage = process.argv.includes('--hidden-page')
const usesNativeWindowOpen = process.argv.includes('--native-window-open')
const contextIsolation = process.argv.includes('--context-isolation')
// Pass the arguments to isolatedWorld.
if (contextIsolation) {
const isolatedWorldArgs = { ipcRenderer, guestInstanceId, hiddenPage, openerId, usesNativeWindowOpen }
v8Util.setHiddenValue(global, 'isolated-world-args', isolatedWorldArgs)
}
require('@electron/internal/renderer/window-setup')(ipcRenderer, guestInstanceId, openerId, hiddenPage, usesNativeWindowOpen)

View file

@ -2,9 +2,24 @@
const { remote } = require('electron')
exports.getRemoteForUsage = function (usage) {
exports.getRemote = function (name) {
if (!remote) {
throw new Error(`${usage} requires remote, which is not enabled`)
throw new Error(`${name} requires remote, which is not enabled`)
}
return remote[name]
}
exports.remoteRequire = function (name) {
if (!remote) {
throw new Error(`${name} requires remote, which is not enabled`)
}
return remote.require(name)
}
exports.potentiallyRemoteRequire = function (name) {
if (process.sandboxed) {
return exports.remoteRequire(name)
} else {
return require(name)
}
return remote
}

View file

@ -249,54 +249,6 @@ const warnAboutAllowedPopups = function () {
}
}
const warnAboutNodeIntegrationDefault = function (webPreferences) {
if (webPreferences && webPreferences.nodeIntegration && !webPreferences.nodeIntegrationWasExplicitlyEnabled) {
const warning = `This window has node integration enabled by default. In ` +
`Electron 5.0.0, node integration will be disabled by default. To prepare ` +
`for this change, set {nodeIntegration: true} in the webPreferences for ` +
`this window, or ensure that this window does not rely on node integration ` +
`and set {nodeIntegration: false}.`
console.warn('%cElectron Deprecation Warning (nodeIntegration default change)', 'font-weight: bold;', warning)
}
}
const warnAboutContextIsolationDefault = function (webPreferences) {
if (webPreferences && webPreferences.preload && !webPreferences.contextIsolation && !webPreferences.contextIsolationWasExplicitlyDisabled) {
const url = 'https://electronjs.org/docs/tutorial/security#3-enable-context-isolation-for-remote-content'
const warning = `This window has context isolation disabled by default. In ` +
`Electron 5.0.0, context isolation will be enabled by default. To prepare ` +
`for this change, set {contextIsolation: false} in the webPreferences for ` +
`this window, or ensure that this window does not rely on context ` +
`isolation being disabled, and set {contextIsolation: true}.\n\n` +
`For more information, see ${url}`
console.warn('%cElectron Deprecation Warning (contextIsolation default change)', 'font-weight: bold;', warning)
}
}
const warnAboutDeprecatedWebviewTagDefault = function (webPreferences) {
if (!webPreferences) {
return
}
if (webPreferences.webviewTagWasExplicitlyEnabled) {
return
}
if (!document || !document.getElementsByTagName) {
return
}
const webviews = document.getElementsByTagName('webview')
if (webviews && webviews.length > 0) {
const url = 'https://github.com/electron/electron/blob/master/docs/api/breaking-changes.md#new-browserwindow-webpreferences-'
const warning = `This window has the <webview> tag enabled by default. In ` +
`Electron 5.0.0, <webview> tags will be disabled by default. To prepare ` +
`for this change, set {webviewTag: true} in the webPreferences for ` +
`this window.\n\n` +
`For more information, see ${url}`
console.warn('%cElectron Deprecation Warning (webviewTag default change)',
'font-weight: bold;', warning)
}
}
// Currently missing since we can't easily programmatically check for it:
// #12WebViews: Verify the options and params of all `<webview>` tags
@ -309,9 +261,6 @@ const logSecurityWarnings = function (webPreferences, nodeIntegration) {
warnAboutEnableBlinkFeatures(webPreferences)
warnAboutInsecureCSP()
warnAboutAllowedPopups()
warnAboutNodeIntegrationDefault(webPreferences)
warnAboutContextIsolationDefault(webPreferences)
warnAboutDeprecatedWebviewTagDefault(webPreferences)
}
const getWebPreferences = function () {

View file

@ -1,7 +1,7 @@
'use strict'
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
const { WebViewImpl } = require('@electron/internal/renderer/web-view/web-view')
const { WebViewImpl } = require('@electron/internal/renderer/web-view/web-view-impl')
const webViewConstants = require('@electron/internal/renderer/web-view/web-view-constants')
const errorUtils = require('@electron/internal/common/error-utils')
@ -270,6 +270,7 @@ WebViewImpl.prototype.setupWebViewAttributes = function () {
this.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER] = new HttpReferrerAttribute(this)
this.attributes[webViewConstants.ATTRIBUTE_USERAGENT] = new UserAgentAttribute(this)
this.attributes[webViewConstants.ATTRIBUTE_NODEINTEGRATION] = new BooleanAttribute(webViewConstants.ATTRIBUTE_NODEINTEGRATION, this)
this.attributes[webViewConstants.ATTRIBUTE_NODEINTEGRATIONINSUBFRAMES] = new BooleanAttribute(webViewConstants.ATTRIBUTE_NODEINTEGRATIONINSUBFRAMES, this)
this.attributes[webViewConstants.ATTRIBUTE_PLUGINS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_PLUGINS, this)
this.attributes[webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY, this)
this.attributes[webViewConstants.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_ALLOWPOPUPS, this)

View file

@ -7,6 +7,7 @@ module.exports = {
ATTRIBUTE_SRC: 'src',
ATTRIBUTE_HTTPREFERRER: 'httpreferrer',
ATTRIBUTE_NODEINTEGRATION: 'nodeintegration',
ATTRIBUTE_NODEINTEGRATIONINSUBFRAMES: 'nodeintegrationinsubframes',
ATTRIBUTE_ENABLEREMOTEMODULE: 'enableremotemodule',
ATTRIBUTE_PLUGINS: 'plugins',
ATTRIBUTE_DISABLEWEBSECURITY: 'disablewebsecurity',

View file

@ -0,0 +1,111 @@
'use strict'
// When using context isolation, the WebViewElement and the custom element
// methods have to be defined in the main world to be able to be registered.
//
// Note: The hidden values can only be read/set inside the same context, all
// methods that access the "internal" hidden value must be put in this file.
//
// Note: This file could be loaded in the main world of contextIsolation page,
// which runs in browserify environment instead of Node environment, all native
// modules must be passed from outside, all included files must be plain JS.
const webViewConstants = require('@electron/internal/renderer/web-view/web-view-constants')
// Return a WebViewElement class that is defined in this context.
const defineWebViewElement = (v8Util, webViewImpl) => {
const { guestViewInternal, WebViewImpl } = webViewImpl
return class WebViewElement extends HTMLElement {
static get observedAttributes () {
return [
webViewConstants.ATTRIBUTE_PARTITION,
webViewConstants.ATTRIBUTE_SRC,
webViewConstants.ATTRIBUTE_HTTPREFERRER,
webViewConstants.ATTRIBUTE_USERAGENT,
webViewConstants.ATTRIBUTE_NODEINTEGRATION,
webViewConstants.ATTRIBUTE_NODEINTEGRATIONINSUBFRAMES,
webViewConstants.ATTRIBUTE_PLUGINS,
webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY,
webViewConstants.ATTRIBUTE_ALLOWPOPUPS,
webViewConstants.ATTRIBUTE_ENABLEREMOTEMODULE,
webViewConstants.ATTRIBUTE_PRELOAD,
webViewConstants.ATTRIBUTE_BLINKFEATURES,
webViewConstants.ATTRIBUTE_DISABLEBLINKFEATURES,
webViewConstants.ATTRIBUTE_WEBPREFERENCES
]
}
constructor () {
super()
v8Util.setHiddenValue(this, 'internal', new WebViewImpl(this))
}
connectedCallback () {
const internal = v8Util.getHiddenValue(this, 'internal')
if (!internal) {
return
}
if (!internal.elementAttached) {
guestViewInternal.registerEvents(internal, internal.viewInstanceId)
internal.elementAttached = true
internal.attributes[webViewConstants.ATTRIBUTE_SRC].parse()
}
}
attributeChangedCallback (name, oldValue, newValue) {
const internal = v8Util.getHiddenValue(this, 'internal')
if (internal) {
internal.handleWebviewAttributeMutation(name, oldValue, newValue)
}
}
disconnectedCallback () {
const internal = v8Util.getHiddenValue(this, 'internal')
if (!internal) {
return
}
guestViewInternal.deregisterEvents(internal.viewInstanceId)
internal.elementAttached = false
this.internalInstanceId = 0
internal.reset()
}
}
}
// Register <webview> custom element.
const registerWebViewElement = (v8Util, webViewImpl) => {
const WebViewElement = defineWebViewElement(v8Util, webViewImpl)
webViewImpl.setupMethods(WebViewElement)
// The customElements.define has to be called in a special scope.
webViewImpl.webFrame.allowGuestViewElementDefinition(window, () => {
window.customElements.define('webview', WebViewElement)
window.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
// Now that |observedAttributes| has been retrieved, we can hide it from
// user code as well.
delete WebViewElement.observedAttributes
})
}
// Prepare to register the <webview> element.
const setupWebView = (v8Util, webViewImpl) => {
const useCapture = true
window.addEventListener('readystatechange', function listener (event) {
if (document.readyState === 'loading') {
return
}
webViewImpl.setupAttributes()
registerWebViewElement(v8Util, webViewImpl)
window.removeEventListener(event.type, listener, useCapture)
}, useCapture)
}
module.exports = { setupWebView }

View file

@ -24,7 +24,6 @@ const getNextId = function () {
class WebViewImpl {
constructor (webviewNode) {
this.webviewNode = webviewNode
v8Util.setHiddenValue(this.webviewNode, 'internal', this)
this.elementAttached = false
this.beforeFirstNavigation = true
this.hasFocus = false
@ -201,38 +200,25 @@ class WebViewImpl {
}
}
// Registers <webview> custom element.
const registerWebViewElement = (window) => {
const proto = Object.create(window.HTMLObjectElement.prototype)
proto.createdCallback = function () {
return new WebViewImpl(this)
}
proto.attributeChangedCallback = function (name, oldValue, newValue) {
const setupAttributes = () => {
require('@electron/internal/renderer/web-view/web-view-attributes')
}
const setupMethods = (WebViewElement) => {
// WebContents associated with this webview.
WebViewElement.prototype.getWebContents = function () {
const { getRemote } = require('@electron/internal/renderer/remote')
const getGuestWebContents = getRemote('getGuestWebContents')
const internal = v8Util.getHiddenValue(this, 'internal')
if (internal) {
internal.handleWebviewAttributeMutation(name, oldValue, newValue)
if (!internal.guestInstanceId) {
internal.createGuestSync()
}
return getGuestWebContents(internal.guestInstanceId)
}
proto.detachedCallback = function () {
const internal = v8Util.getHiddenValue(this, 'internal')
if (!internal) {
return
}
guestViewInternal.deregisterEvents(internal.viewInstanceId)
internal.elementAttached = false
this.internalInstanceId = 0
internal.reset()
}
proto.attachedCallback = function () {
const internal = v8Util.getHiddenValue(this, 'internal')
if (!internal) {
return
}
if (!internal.elementAttached) {
guestViewInternal.registerEvents(internal, internal.viewInstanceId)
internal.elementAttached = true
internal.attributes[webViewConstants.ATTRIBUTE_SRC].parse()
}
// Focusing the webview should move page focus to the underlying iframe.
WebViewElement.prototype.focus = function () {
this.contentWindow.focus()
}
const getGuestInstanceId = function (self) {
@ -255,7 +241,7 @@ const registerWebViewElement = (window) => {
}
}
for (const method of syncMethods) {
proto[method] = createBlockHandler(method)
WebViewElement.prototype[method] = createBlockHandler(method)
}
const createNonBlockHandler = function (method) {
@ -274,7 +260,7 @@ const registerWebViewElement = (window) => {
}
for (const method of asyncCallbackMethods) {
proto[method] = createNonBlockHandler(method)
WebViewElement.prototype[method] = createNonBlockHandler(method)
}
const createPromiseHandler = function (method) {
@ -299,48 +285,8 @@ const registerWebViewElement = (window) => {
}
for (const method of asyncPromiseMethods) {
proto[method] = createPromiseHandler(method)
WebViewElement.prototype[method] = createPromiseHandler(method)
}
// WebContents associated with this webview.
proto.getWebContents = function () {
const { getRemoteForUsage } = require('@electron/internal/renderer/remote')
const remote = getRemoteForUsage('getWebContents()')
const internal = v8Util.getHiddenValue(this, 'internal')
if (!internal.guestInstanceId) {
internal.createGuestSync()
}
return remote.getGuestWebContents(internal.guestInstanceId)
}
// Focusing the webview should move page focus to the underlying iframe.
proto.focus = function () {
this.contentWindow.focus()
}
window.WebView = webFrame.registerEmbedderCustomElement(window, 'webview', {
prototype: proto
})
// Delete the callbacks so developers cannot call them and produce unexpected
// behavior.
delete proto.createdCallback
delete proto.attachedCallback
delete proto.detachedCallback
delete proto.attributeChangedCallback
}
const setupWebView = (window) => {
require('@electron/internal/renderer/web-view/web-view-attributes')
const useCapture = true
window.addEventListener('readystatechange', function listener (event) {
if (document.readyState === 'loading') {
return
}
registerWebViewElement(window)
window.removeEventListener(event.type, listener, useCapture)
}, useCapture)
}
module.exports = { setupWebView, WebViewImpl }
module.exports = { setupAttributes, setupMethods, guestViewInternal, webFrame, WebViewImpl }

View file

@ -0,0 +1,35 @@
'use strict'
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')
const v8Util = process.atomBinding('v8_util')
function handleFocusBlur (guestInstanceId) {
// Note that while Chromium content APIs have observer for focus/blur, they
// unfortunately do not work for webview.
window.addEventListener('focus', () => {
ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', true, guestInstanceId)
})
window.addEventListener('blur', () => {
ipcRenderer.send('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', false, guestInstanceId)
})
}
module.exports = function (contextIsolation, webviewTag, guestInstanceId) {
// Don't allow recursive `<webview>`.
if (webviewTag && guestInstanceId == null) {
const webViewImpl = require('@electron/internal/renderer/web-view/web-view-impl')
if (contextIsolation) {
v8Util.setHiddenValue(window, 'web-view-impl', webViewImpl)
} else {
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element')
setupWebView(v8Util, webViewImpl)
}
}
if (guestInstanceId) {
// Report focus/blur events of webview to browser.
handleFocusBlur(guestInstanceId)
}
}

View file

@ -23,10 +23,10 @@
// - document.hidden
// - document.visibilityState
const { defineProperty } = Object
const { defineProperty, defineProperties } = Object
// Helper function to resolve relative url.
const a = window.top.document.createElement('a')
const a = window.document.createElement('a')
const resolveURL = function (url) {
a.href = url
return a.href
@ -54,12 +54,61 @@ const removeProxy = (guestId) => {
delete windowProxies[guestId]
}
function LocationProxy (ipcRenderer, guestId) {
const getGuestURL = function () {
const urlString = ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', guestId, 'getURL')
try {
return new URL(urlString)
} catch (e) {
console.error('LocationProxy: failed to parse string', urlString, e)
}
return null
}
const propertyProxyFor = function (property) {
return {
get: function () {
const guestURL = getGuestURL()
const value = guestURL ? guestURL[property] : ''
return value === undefined ? '' : value
},
set: function (newVal) {
const guestURL = getGuestURL()
if (guestURL) {
guestURL[property] = newVal
return ipcRenderer.sendSync(
'ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC',
guestId, 'loadURL', guestURL.toString())
}
}
}
}
defineProperties(this, {
hash: propertyProxyFor('hash'),
href: propertyProxyFor('href'),
host: propertyProxyFor('host'),
hostname: propertyProxyFor('hostname'),
origin: propertyProxyFor('origin'),
pathname: propertyProxyFor('pathname'),
port: propertyProxyFor('port'),
protocol: propertyProxyFor('protocol'),
search: propertyProxyFor('search')
})
this.toString = function () {
return this.href
}
}
function BrowserWindowProxy (ipcRenderer, guestId) {
this.closed = false
const location = new LocationProxy(ipcRenderer, guestId)
defineProperty(this, 'location', {
get: function () {
return ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', guestId, 'getURL')
return location
},
set: function (url) {
url = resolveURL(url)