refactor: implement <webview> methods via dedicated IPCs without the remote module (#14377)

This commit is contained in:
Milan Burda 2018-10-01 03:07:50 +02:00 committed by Cheng Zhao
parent ce38be74df
commit d48f9bcf7f
4 changed files with 73 additions and 48 deletions

View file

@ -373,19 +373,37 @@ handleRemoteCommand('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, cont
return valueToMeta(event.sender, contextId, guestViewManager.getGuest(guestInstanceId)) return valueToMeta(event.sender, contextId, guestViewManager.getGuest(guestInstanceId))
}) })
ipcMain.on('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function (event, contextId, requestId, guestInstanceId, method, ...args) { ipcMain.on('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function (event, requestId, guestInstanceId, method, args, hasCallback) {
new Promise(resolve => {
let guestViewManager = require('./guest-view-manager')
let guest = guestViewManager.getGuest(guestInstanceId)
if (guest.hostWebContents !== event.sender) {
throw new Error('Access denied')
}
if (hasCallback) {
guest[method](...args, resolve)
} else {
resolve(guest[method](...args))
}
}).then(result => {
return [null, result]
}, error => {
return [errorUtils.serialize(error)]
}).then(responseArgs => {
event.sender.send(`ELECTRON_RENDERER_ASYNC_CALL_TO_GUEST_VIEW_RESPONSE_${requestId}`, ...responseArgs)
})
})
ipcMain.on('ELECTRON_BROWSER_SYNC_CALL_TO_GUEST_VIEW', function (event, guestInstanceId, method, args) {
try { try {
let guestViewManager = require('@electron/internal/browser/guest-view-manager') let guestViewManager = require('@electron/internal/browser/guest-view-manager')
let guest = guestViewManager.getGuest(guestInstanceId) let guest = guestViewManager.getGuest(guestInstanceId)
if (requestId) { if (guest.hostWebContents !== event.sender) {
const responseCallback = function (result) { throw new Error('Access denied')
event.sender.send(`ELECTRON_RENDERER_ASYNC_CALL_TO_GUEST_VIEW_RESPONSE_${requestId}`, result)
} }
args.push(responseCallback) event.returnValue = [null, guest[method].apply(guest, args)]
}
guest[method].apply(guest, args)
} catch (error) { } catch (error) {
event.returnValue = exceptionToMeta(event.sender, contextId, error) event.returnValue = [errorUtils.serialize(error)]
} }
}) })

View file

@ -1,8 +1,9 @@
'use strict' 'use strict'
const { ipcRenderer } = require('electron')
const WebViewImpl = require('@electron/internal/renderer/web-view/web-view') const WebViewImpl = require('@electron/internal/renderer/web-view/web-view')
const webViewConstants = require('@electron/internal/renderer/web-view/web-view-constants') const webViewConstants = require('@electron/internal/renderer/web-view/web-view-constants')
const { remote } = require('electron') const errorUtils = require('@electron/internal/common/error-utils')
// Helper function to resolve url set in attribute. // Helper function to resolve url set in attribute.
const a = document.createElement('a') const a = document.createElement('a')
@ -180,8 +181,15 @@ class SrcAttribute extends WebViewAttribute {
if (useragent) { if (useragent) {
opts.userAgent = useragent opts.userAgent = useragent
} }
const guestContents = remote.getGuestWebContents(this.webViewImpl.guestInstanceId)
guestContents.loadURL(this.getValue(), opts) const guestInstanceId = this.webViewImpl.guestInstanceId
const method = 'loadURL'
const args = [this.getValue(), opts]
const [error] = ipcRenderer.sendSync('ELECTRON_BROWSER_SYNC_CALL_TO_GUEST_VIEW', guestInstanceId, method, args)
if (error) {
throw errorUtils.deserialize(error)
}
} }
} }

View file

@ -5,9 +5,7 @@ const { ipcRenderer, remote, webFrame } = require('electron')
const v8Util = process.atomBinding('v8_util') const v8Util = process.atomBinding('v8_util')
const guestViewInternal = require('@electron/internal/renderer/web-view/guest-view-internal') const guestViewInternal = require('@electron/internal/renderer/web-view/guest-view-internal')
const webViewConstants = require('@electron/internal/renderer/web-view/web-view-constants') const webViewConstants = require('@electron/internal/renderer/web-view/web-view-constants')
const errorUtils = require('@electron/internal/common/error-utils')
// An unique ID that can represent current context.
const contextId = v8Util.getHiddenValue(global, 'contextId')
// ID generator. // ID generator.
let nextId = 0 let nextId = 0
@ -66,7 +64,6 @@ class WebViewImpl {
this.guestInstanceId = void 0 this.guestInstanceId = void 0
} }
this.webContents = null
this.beforeFirstNavigation = true this.beforeFirstNavigation = true
this.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId = true this.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId = true
@ -188,7 +185,6 @@ class WebViewImpl {
} }
this.internalInstanceId = getNextId() this.internalInstanceId = getNextId()
this.guestInstanceId = guestInstanceId this.guestInstanceId = guestInstanceId
this.webContents = remote.getGuestWebContents(this.guestInstanceId)
guestViewInternal.attachGuest(this.internalInstanceId, this.guestInstanceId, this.buildParams(), this.internalElement.contentWindow) guestViewInternal.attachGuest(this.internalInstanceId, this.guestInstanceId, this.buildParams(), this.internalElement.contentWindow)
// ResizeObserver is a browser global not recognized by "standard". // ResizeObserver is a browser global not recognized by "standard".
/* globals ResizeObserver */ /* globals ResizeObserver */
@ -277,14 +273,9 @@ const registerWebViewElement = function () {
'stopFindInPage', 'stopFindInPage',
'downloadURL', 'downloadURL',
'inspectServiceWorker', 'inspectServiceWorker',
'print',
'printToPDF',
'showDefinitionForSelection', 'showDefinitionForSelection',
'capturePage',
'setZoomFactor', 'setZoomFactor',
'setZoomLevel', 'setZoomLevel'
'getZoomLevel',
'getZoomFactor'
] ]
const nonblockMethods = [ const nonblockMethods = [
'insertCSS', 'insertCSS',
@ -292,17 +283,32 @@ const registerWebViewElement = function () {
'send', 'send',
'sendInputEvent', 'sendInputEvent',
'setLayoutZoomLevelLimits', 'setLayoutZoomLevelLimits',
'setVisualZoomLevelLimits' 'setVisualZoomLevelLimits',
// with callback
'capturePage',
'executeJavaScript',
'getZoomFactor',
'getZoomLevel',
'print',
'printToPDF'
] ]
const getGuestInstanceId = function (self) {
const internal = v8Util.getHiddenValue(self, '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.')
}
return internal.guestInstanceId
}
// Forward proto.foo* method calls to WebViewImpl.foo*. // Forward proto.foo* method calls to WebViewImpl.foo*.
const createBlockHandler = function (m) { const createBlockHandler = function (method) {
return function (...args) { return function (...args) {
const internal = v8Util.getHiddenValue(this, 'internal') const [error, result] = ipcRenderer.sendSync('ELECTRON_BROWSER_SYNC_CALL_TO_GUEST_VIEW', getGuestInstanceId(this), method, args)
if (internal.webContents) { if (error) {
return internal.webContents[m](...args) throw errorUtils.deserialize(error)
} else { } else {
throw new Error(`Cannot call ${m} because the webContents is unavailable. The WebView must be attached to the DOM and the dom-ready event emitted before this method can be called.`) return result
} }
} }
} }
@ -310,36 +316,31 @@ const registerWebViewElement = function () {
proto[method] = createBlockHandler(method) proto[method] = createBlockHandler(method)
} }
const createNonBlockHandler = function (m) { const createNonBlockHandler = function (method) {
return function (...args) { return function (...args) {
const internal = v8Util.getHiddenValue(this, 'internal') const callback = (typeof args[args.length - 1] === 'function') ? args.pop() : null
ipcRenderer.send('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', contextId, null, internal.guestInstanceId, m, ...args) const requestId = getNextId()
ipcRenderer.once(`ELECTRON_RENDERER_ASYNC_CALL_TO_GUEST_VIEW_RESPONSE_${requestId}`, function (event, error, result) {
if (error == null) {
if (callback) callback(result)
} else {
throw errorUtils.deserialize(error)
}
})
ipcRenderer.send('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', requestId, getGuestInstanceId(this), method, args, callback != null)
} }
} }
for (const method of nonblockMethods) { for (const method of nonblockMethods) {
proto[method] = createNonBlockHandler(method) proto[method] = createNonBlockHandler(method)
} }
proto.executeJavaScript = function (code, hasUserGesture, callback) {
const internal = v8Util.getHiddenValue(this, 'internal')
if (typeof hasUserGesture === 'function') {
callback = hasUserGesture
hasUserGesture = false
}
const requestId = getNextId()
ipcRenderer.send('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', contextId, requestId, internal.guestInstanceId, 'executeJavaScript', code, hasUserGesture)
ipcRenderer.once(`ELECTRON_RENDERER_ASYNC_CALL_TO_GUEST_VIEW_RESPONSE_${requestId}`, function (event, result) {
if (callback) callback(result)
})
}
// WebContents associated with this webview. // WebContents associated with this webview.
proto.getWebContents = function () { proto.getWebContents = function () {
const internal = v8Util.getHiddenValue(this, 'internal') const internal = v8Util.getHiddenValue(this, 'internal')
if (!internal.webContents) { if (!internal.guestInstanceId) {
internal.createGuestSync() internal.createGuestSync()
} }
return internal.webContents return remote.getGuestWebContents(internal.guestInstanceId)
} }
// Focusing the webview should move page focus to the underlying iframe. // Focusing the webview should move page focus to the underlying iframe.

View file

@ -890,7 +890,6 @@ describe('<webview> tag', function () {
it('throws a custom error when an API method is called before the event is emitted', () => { it('throws a custom error when an API method is called before the event is emitted', () => {
const expectedErrorMessage = const expectedErrorMessage =
'Cannot call stop because the webContents is unavailable. ' +
'The WebView must be attached to the DOM ' + 'The WebView must be attached to the DOM ' +
'and the dom-ready event emitted before this method can be called.' 'and the dom-ready event emitted before this method can be called.'
expect(() => { webview.stop() }).to.throw(expectedErrorMessage) expect(() => { webview.stop() }).to.throw(expectedErrorMessage)
@ -1179,7 +1178,6 @@ describe('<webview> tag', function () {
loadWebView(webview) loadWebView(webview)
setTimeout(() => { setTimeout(() => {
const expectedErrorMessage = const expectedErrorMessage =
'Cannot call stop because the webContents is unavailable. ' +
'The WebView must be attached to the DOM ' + 'The WebView must be attached to the DOM ' +
'and the dom-ready event emitted before this method can be called.' 'and the dom-ready event emitted before this method can be called.'
expect(() => { webview.stop() }).to.throw(expectedErrorMessage) expect(() => { webview.stop() }).to.throw(expectedErrorMessage)