refactor: implement <webview> methods via dedicated IPCs without the remote module (#14377)
This commit is contained in:
parent
ce38be74df
commit
d48f9bcf7f
4 changed files with 73 additions and 48 deletions
|
@ -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)]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue