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)
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    guest[method].apply(guest, args)
 | 
					    event.returnValue = [null, 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