refactor: Port renderer/web-view to TypeScript (#17250)

This commit is contained in:
Felix Rieseberg 2019-03-07 15:26:23 -08:00 committed by Samuel Attard
parent 8c6bf9c848
commit f3fc4023cf
13 changed files with 441 additions and 301 deletions

View file

@ -73,12 +73,12 @@ filenames = {
"lib/renderer/security-warnings.ts", "lib/renderer/security-warnings.ts",
"lib/renderer/window-setup.ts", "lib/renderer/window-setup.ts",
"lib/renderer/web-frame-init.ts", "lib/renderer/web-frame-init.ts",
"lib/renderer/web-view/guest-view-internal.js", "lib/renderer/web-view/guest-view-internal.ts",
"lib/renderer/web-view/web-view-attributes.js", "lib/renderer/web-view/web-view-attributes.ts",
"lib/renderer/web-view/web-view-constants.js", "lib/renderer/web-view/web-view-constants.ts",
"lib/renderer/web-view/web-view-element.js", "lib/renderer/web-view/web-view-element.ts",
"lib/renderer/web-view/web-view-impl.js", "lib/renderer/web-view/web-view-impl.ts",
"lib/renderer/web-view/web-view-init.js", "lib/renderer/web-view/web-view-init.ts",
"lib/renderer/api/exports/electron.js", "lib/renderer/api/exports/electron.js",
"lib/renderer/api/crash-reporter.js", "lib/renderer/api/crash-reporter.js",
"lib/renderer/api/ipc-renderer.js", "lib/renderer/api/ipc-renderer.js",

View file

@ -93,7 +93,8 @@ switch (window.location.protocol) {
// Load webview tag implementation. // Load webview tag implementation.
if (process.isMainFrame) { if (process.isMainFrame) {
require('@electron/internal/renderer/web-view/web-view-init')(contextIsolation, webviewTag, guestInstanceId) const { webViewInit } = require('@electron/internal/renderer/web-view/web-view-init')
webViewInit(contextIsolation, webviewTag, guestInstanceId)
} }
// Pass the arguments to isolatedWorld. // Pass the arguments to isolatedWorld.

View file

@ -1,105 +0,0 @@
'use strict'
const { webFrame } = require('electron')
const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal')
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils')
const WEB_VIEW_EVENTS = {
'load-commit': ['url', 'isMainFrame'],
'did-attach': [],
'did-finish-load': [],
'did-fail-load': ['errorCode', 'errorDescription', 'validatedURL', 'isMainFrame', 'frameProcessId', 'frameRoutingId'],
'did-frame-finish-load': ['isMainFrame', 'frameProcessId', 'frameRoutingId'],
'did-start-loading': [],
'did-stop-loading': [],
'dom-ready': [],
'console-message': ['level', 'message', 'line', 'sourceId'],
'context-menu': ['params'],
'devtools-opened': [],
'devtools-closed': [],
'devtools-focused': [],
'new-window': ['url', 'frameName', 'disposition', 'options'],
'will-navigate': ['url'],
'did-start-navigation': ['url', 'isInPlace', 'isMainFrame', 'frameProcessId', 'frameRoutingId'],
'did-navigate': ['url', 'httpResponseCode', 'httpStatusText'],
'did-frame-navigate': ['url', 'httpResponseCode', 'httpStatusText', 'isMainFrame', 'frameProcessId', 'frameRoutingId'],
'did-navigate-in-page': ['url', 'isMainFrame', 'frameProcessId', 'frameRoutingId'],
'focus-change': ['focus', 'guestInstanceId'],
'close': [],
'crashed': [],
'gpu-crashed': [],
'plugin-crashed': ['name', 'version'],
'destroyed': [],
'page-title-updated': ['title', 'explicitSet'],
'page-favicon-updated': ['favicons'],
'enter-html-full-screen': [],
'leave-html-full-screen': [],
'media-started-playing': [],
'media-paused': [],
'found-in-page': ['result'],
'did-change-theme-color': ['themeColor'],
'update-target-url': ['url']
}
const DEPRECATED_EVENTS = {
'page-title-updated': 'page-title-set'
}
const dispatchEvent = function (webView, eventName, eventKey, ...args) {
if (DEPRECATED_EVENTS[eventName] != null) {
dispatchEvent(webView, DEPRECATED_EVENTS[eventName], eventKey, ...args)
}
const domEvent = new Event(eventName)
WEB_VIEW_EVENTS[eventKey].forEach((prop, index) => {
domEvent[prop] = args[index]
})
webView.dispatchEvent(domEvent)
if (eventName === 'load-commit') {
webView.onLoadCommit(domEvent)
} else if (eventName === 'focus-change') {
webView.onFocusChange(domEvent)
}
}
module.exports = {
registerEvents: function (webView, viewInstanceId) {
ipcRendererInternal.on(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${viewInstanceId}`, function () {
webView.guestInstanceId = undefined
webView.reset()
const domEvent = new Event('destroyed')
webView.dispatchEvent(domEvent)
})
ipcRendererInternal.on(`ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-${viewInstanceId}`, function (event, eventName, ...args) {
dispatchEvent(webView, eventName, eventName, ...args)
})
ipcRendererInternal.on(`ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-${viewInstanceId}`, function (event, channel, ...args) {
const domEvent = new Event('ipc-message')
domEvent.channel = channel
domEvent.args = args
webView.dispatchEvent(domEvent)
})
},
deregisterEvents: function (viewInstanceId) {
ipcRendererInternal.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${viewInstanceId}`)
ipcRendererInternal.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-${viewInstanceId}`)
ipcRendererInternal.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-${viewInstanceId}`)
},
createGuest: function (params) {
return ipcRendererUtils.invoke('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', params)
},
createGuestSync: function (params) {
return ipcRendererUtils.invokeSync('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', params)
},
destroyGuest: function (guestInstanceId) {
ipcRendererUtils.invoke('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', guestInstanceId)
},
attachGuest: function (elementInstanceId, guestInstanceId, params, contentWindow) {
const embedderFrameId = webFrame.getWebFrameId(contentWindow)
if (embedderFrameId < 0) { // this error should not happen.
throw new Error('Invalid embedder frame')
}
ipcRendererUtils.invoke('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', embedderFrameId, elementInstanceId, guestInstanceId, params)
}
}

View file

@ -0,0 +1,124 @@
import { webFrame, IpcMessageEvent } from 'electron'
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'
import { invoke, invokeSync } from '@electron/internal/renderer/ipc-renderer-internal-utils'
import { WebViewImpl } from '@electron/internal/renderer/web-view/web-view-impl'
const WEB_VIEW_EVENTS: Record<string, Array<string>> = {
'load-commit': ['url', 'isMainFrame'],
'did-attach': [],
'did-finish-load': [],
'did-fail-load': ['errorCode', 'errorDescription', 'validatedURL', 'isMainFrame', 'frameProcessId', 'frameRoutingId'],
'did-frame-finish-load': ['isMainFrame', 'frameProcessId', 'frameRoutingId'],
'did-start-loading': [],
'did-stop-loading': [],
'dom-ready': [],
'console-message': ['level', 'message', 'line', 'sourceId'],
'context-menu': ['params'],
'devtools-opened': [],
'devtools-closed': [],
'devtools-focused': [],
'new-window': ['url', 'frameName', 'disposition', 'options'],
'will-navigate': ['url'],
'did-start-navigation': ['url', 'isInPlace', 'isMainFrame', 'frameProcessId', 'frameRoutingId'],
'did-navigate': ['url', 'httpResponseCode', 'httpStatusText'],
'did-frame-navigate': ['url', 'httpResponseCode', 'httpStatusText', 'isMainFrame', 'frameProcessId', 'frameRoutingId'],
'did-navigate-in-page': ['url', 'isMainFrame', 'frameProcessId', 'frameRoutingId'],
'focus-change': ['focus', 'guestInstanceId'],
'close': [],
'crashed': [],
'gpu-crashed': [],
'plugin-crashed': ['name', 'version'],
'destroyed': [],
'page-title-updated': ['title', 'explicitSet'],
'page-favicon-updated': ['favicons'],
'enter-html-full-screen': [],
'leave-html-full-screen': [],
'media-started-playing': [],
'media-paused': [],
'found-in-page': ['result'],
'did-change-theme-color': ['themeColor'],
'update-target-url': ['url']
}
const DEPRECATED_EVENTS: Record<string, string> = {
'page-title-updated': 'page-title-set'
}
const dispatchEvent = function (
webView: WebViewImpl, eventName: string, eventKey: string, ...args: Array<any>
) {
if (DEPRECATED_EVENTS[eventName] != null) {
dispatchEvent(webView, DEPRECATED_EVENTS[eventName], eventKey, ...args)
}
const domEvent = new Event(eventName) as ElectronInternal.WebViewEvent
WEB_VIEW_EVENTS[eventKey].forEach((prop, index) => {
(domEvent as any)[prop] = args[index]
})
webView.dispatchEvent(domEvent)
if (eventName === 'load-commit') {
webView.onLoadCommit(domEvent)
} else if (eventName === 'focus-change') {
webView.onFocusChange()
}
}
export function registerEvents (webView: WebViewImpl, viewInstanceId: number) {
ipcRendererInternal.on(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${viewInstanceId}`, function () {
webView.guestInstanceId = undefined
webView.reset()
const domEvent = new Event('destroyed')
webView.dispatchEvent(domEvent)
})
ipcRendererInternal.on(`ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-${viewInstanceId}`, function (event, eventName, ...args) {
dispatchEvent(webView, eventName, eventName, ...args)
})
ipcRendererInternal.on(`ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-${viewInstanceId}`, function (event, channel, ...args) {
const domEvent = new Event('ipc-message') as IpcMessageEvent
domEvent.channel = channel
domEvent.args = args
webView.dispatchEvent(domEvent)
})
}
export function deregisterEvents (viewInstanceId: number) {
ipcRendererInternal.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${viewInstanceId}`)
ipcRendererInternal.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-${viewInstanceId}`)
ipcRendererInternal.removeAllListeners(`ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-${viewInstanceId}`)
}
export function createGuest (params: Record<string, any>): Promise<number> {
return invoke('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', params)
}
export function createGuestSync (params: Record<string, any>): number {
return invokeSync('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', params)
}
export function destroyGuest (guestInstanceId: number): void {
invoke('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', guestInstanceId)
}
export function attachGuest (
elementInstanceId: number, guestInstanceId: number, params: Record<string, any>, contentWindow: Window
) {
const embedderFrameId = (webFrame as ElectronInternal.WebFrameInternal).getWebFrameId(contentWindow)
if (embedderFrameId < 0) { // this error should not happen.
throw new Error('Invalid embedder frame')
}
invoke('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', embedderFrameId, elementInstanceId, guestInstanceId, params)
}
export const guestViewInternalModule = {
deregisterEvents,
createGuest,
createGuestSync,
destroyGuest,
attachGuest
}

View file

@ -1,14 +1,12 @@
'use strict' import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils'
import { WebViewImpl } from '@electron/internal/renderer/web-view/web-view-impl'
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils') import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants'
const { WebViewImpl } = require('@electron/internal/renderer/web-view/web-view-impl')
const webViewConstants = require('@electron/internal/renderer/web-view/web-view-constants')
// 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')
const resolveURL = function (url) { const resolveURL = function (url?: string | null) {
if (url === '') return '' if (!url) return ''
a.href = url a.href = url
return a.href return a.href
} }
@ -16,33 +14,35 @@ const resolveURL = function (url) {
// Attribute objects. // Attribute objects.
// Default implementation of a WebView attribute. // Default implementation of a WebView attribute.
class WebViewAttribute { class WebViewAttribute {
constructor (name, webViewImpl) { public value: any;
public ignoreMutation = false;
constructor (public name: string, public webViewImpl: WebViewImpl) {
this.name = name this.name = name
this.value = webViewImpl.webviewNode[name] || '' this.value = (webViewImpl.webviewNode as Record<string, any>)[name] || ''
this.webViewImpl = webViewImpl this.webViewImpl = webViewImpl
this.ignoreMutation = false
this.defineProperty() this.defineProperty()
} }
// Retrieves and returns the attribute's value. // Retrieves and returns the attribute's value.
getValue () { public getValue () {
return this.webViewImpl.webviewNode.getAttribute(this.name) || this.value return this.webViewImpl.webviewNode.getAttribute(this.name) || this.value
} }
// Sets the attribute's value. // Sets the attribute's value.
setValue (value) { public setValue (value: any) {
this.webViewImpl.webviewNode.setAttribute(this.name, value || '') this.webViewImpl.webviewNode.setAttribute(this.name, value || '')
} }
// Changes the attribute's value without triggering its mutation handler. // Changes the attribute's value without triggering its mutation handler.
setValueIgnoreMutation (value) { public setValueIgnoreMutation (value: any) {
this.ignoreMutation = true this.ignoreMutation = true
this.setValue(value) this.setValue(value)
this.ignoreMutation = false this.ignoreMutation = false
} }
// Defines this attribute as a property on the webview node. // Defines this attribute as a property on the webview node.
defineProperty () { public defineProperty () {
return Object.defineProperty(this.webViewImpl.webviewNode, this.name, { return Object.defineProperty(this.webViewImpl.webviewNode, this.name, {
get: () => { get: () => {
return this.getValue() return this.getValue()
@ -55,7 +55,7 @@ class WebViewAttribute {
} }
// Called when the attribute's value changes. // Called when the attribute's value changes.
handleMutation () {} public handleMutation (..._args: Array<any>): any {}
} }
// An attribute that is treated as a Boolean. // An attribute that is treated as a Boolean.
@ -64,7 +64,7 @@ class BooleanAttribute extends WebViewAttribute {
return this.webViewImpl.webviewNode.hasAttribute(this.name) return this.webViewImpl.webviewNode.hasAttribute(this.name)
} }
setValue (value) { setValue (value: boolean) {
if (value) { if (value) {
this.webViewImpl.webviewNode.setAttribute(this.name, '') this.webViewImpl.webviewNode.setAttribute(this.name, '')
} else { } else {
@ -75,35 +75,38 @@ class BooleanAttribute extends WebViewAttribute {
// Attribute representing the state of the storage partition. // Attribute representing the state of the storage partition.
class PartitionAttribute extends WebViewAttribute { class PartitionAttribute extends WebViewAttribute {
constructor (webViewImpl) { public validPartitionId = true
super(webViewConstants.ATTRIBUTE_PARTITION, webViewImpl)
this.validPartitionId = true constructor (public webViewImpl: WebViewImpl) {
super(WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION, webViewImpl)
} }
handleMutation (oldValue, newValue) { public handleMutation (oldValue: any, newValue: any) {
newValue = newValue || '' newValue = newValue || ''
// The partition cannot change if the webview has already navigated. // The partition cannot change if the webview has already navigated.
if (!this.webViewImpl.beforeFirstNavigation) { if (!this.webViewImpl.beforeFirstNavigation) {
console.error(webViewConstants.ERROR_MSG_ALREADY_NAVIGATED) console.error(WEB_VIEW_CONSTANTS.ERROR_MSG_ALREADY_NAVIGATED)
this.setValueIgnoreMutation(oldValue) this.setValueIgnoreMutation(oldValue)
return return
} }
if (newValue === 'persist:') { if (newValue === 'persist:') {
this.validPartitionId = false this.validPartitionId = false
console.error(webViewConstants.ERROR_MSG_INVALID_PARTITION_ATTRIBUTE) console.error(WEB_VIEW_CONSTANTS.ERROR_MSG_INVALID_PARTITION_ATTRIBUTE)
} }
} }
} }
// Attribute that handles the location and navigation of the webview. // Attribute that handles the location and navigation of the webview.
class SrcAttribute extends WebViewAttribute { class SrcAttribute extends WebViewAttribute {
constructor (webViewImpl) { public observer!: MutationObserver;
super(webViewConstants.ATTRIBUTE_SRC, webViewImpl)
constructor (public webViewImpl: WebViewImpl) {
super(WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC, webViewImpl)
this.setupMutationObserver() this.setupMutationObserver()
} }
getValue () { public getValue () {
if (this.webViewImpl.webviewNode.hasAttribute(this.name)) { if (this.webViewImpl.webviewNode.hasAttribute(this.name)) {
return resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name)) return resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name))
} else { } else {
@ -111,7 +114,7 @@ class SrcAttribute extends WebViewAttribute {
} }
} }
setValueIgnoreMutation (value) { public setValueIgnoreMutation (value: any) {
super.setValueIgnoreMutation(value) super.setValueIgnoreMutation(value)
// takeRecords() is needed to clear queued up src mutations. Without it, it // takeRecords() is needed to clear queued up src mutations. Without it, it
@ -121,7 +124,7 @@ class SrcAttribute extends WebViewAttribute {
this.observer.takeRecords() this.observer.takeRecords()
} }
handleMutation (oldValue, newValue) { public handleMutation (oldValue: any, newValue: any) {
// Once we have navigated, we don't allow clearing the src attribute. // Once we have navigated, we don't allow clearing the src attribute.
// Once <webview> enters a navigated state, it cannot return to a // Once <webview> enters a navigated state, it cannot return to a
// placeholder state. // placeholder state.
@ -139,7 +142,7 @@ class SrcAttribute extends WebViewAttribute {
// attribute without any changes to its value. This is useful in the case // attribute without any changes to its value. This is useful in the case
// where the webview guest has crashed and navigating to the same address // where the webview guest has crashed and navigating to the same address
// spawns off a new process. // spawns off a new process.
setupMutationObserver () { public setupMutationObserver () {
this.observer = new MutationObserver((mutations) => { this.observer = new MutationObserver((mutations) => {
for (const mutation of mutations) { for (const mutation of mutations) {
const { oldValue } = mutation const { oldValue } = mutation
@ -150,16 +153,18 @@ class SrcAttribute extends WebViewAttribute {
this.handleMutation(oldValue, newValue) this.handleMutation(oldValue, newValue)
} }
}) })
const params = { const params = {
attributes: true, attributes: true,
attributeOldValue: true, attributeOldValue: true,
attributeFilter: [this.name] attributeFilter: [this.name]
} }
this.observer.observe(this.webViewImpl.webviewNode, params) this.observer.observe(this.webViewImpl.webviewNode, params)
} }
parse () { public parse () {
if (!this.webViewImpl.elementAttached || !this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId || !this.getValue()) { if (!this.webViewImpl.elementAttached || !this.webViewImpl.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION].validPartitionId || !this.getValue()) {
return return
} }
if (this.webViewImpl.guestInstanceId == null) { if (this.webViewImpl.guestInstanceId == null) {
@ -171,12 +176,14 @@ class SrcAttribute extends WebViewAttribute {
} }
// Navigate to |this.src|. // Navigate to |this.src|.
const opts = {} const opts: Record<string, string> = {}
const httpreferrer = this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER].getValue()
const httpreferrer = this.webViewImpl.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_HTTPREFERRER].getValue()
if (httpreferrer) { if (httpreferrer) {
opts.httpReferrer = httpreferrer opts.httpReferrer = httpreferrer
} }
const useragent = this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_USERAGENT].getValue()
const useragent = this.webViewImpl.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_USERAGENT].getValue()
if (useragent) { if (useragent) {
opts.userAgent = useragent opts.userAgent = useragent
} }
@ -191,69 +198,72 @@ class SrcAttribute extends WebViewAttribute {
// Attribute specifies HTTP referrer. // Attribute specifies HTTP referrer.
class HttpReferrerAttribute extends WebViewAttribute { class HttpReferrerAttribute extends WebViewAttribute {
constructor (webViewImpl) { constructor (webViewImpl: WebViewImpl) {
super(webViewConstants.ATTRIBUTE_HTTPREFERRER, webViewImpl) super(WEB_VIEW_CONSTANTS.ATTRIBUTE_HTTPREFERRER, webViewImpl)
} }
} }
// Attribute specifies user agent // Attribute specifies user agent
class UserAgentAttribute extends WebViewAttribute { class UserAgentAttribute extends WebViewAttribute {
constructor (webViewImpl) { constructor (webViewImpl: WebViewImpl) {
super(webViewConstants.ATTRIBUTE_USERAGENT, webViewImpl) super(WEB_VIEW_CONSTANTS.ATTRIBUTE_USERAGENT, webViewImpl)
} }
} }
// Attribute that set preload script. // Attribute that set preload script.
class PreloadAttribute extends WebViewAttribute { class PreloadAttribute extends WebViewAttribute {
constructor (webViewImpl) { constructor (webViewImpl: WebViewImpl) {
super(webViewConstants.ATTRIBUTE_PRELOAD, webViewImpl) super(WEB_VIEW_CONSTANTS.ATTRIBUTE_PRELOAD, webViewImpl)
} }
getValue () { public getValue () {
if (!this.webViewImpl.webviewNode.hasAttribute(this.name)) { if (!this.webViewImpl.webviewNode.hasAttribute(this.name)) {
return this.value return this.value
} }
let preload = resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name)) let preload = resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name))
const protocol = preload.substr(0, 5) const protocol = preload.substr(0, 5)
if (protocol !== 'file:') { if (protocol !== 'file:') {
console.error(webViewConstants.ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE) console.error(WEB_VIEW_CONSTANTS.ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE)
preload = '' preload = ''
} }
return preload return preload
} }
} }
// Attribute that specifies the blink features to be enabled. // Attribute that specifies the blink features to be enabled.
class BlinkFeaturesAttribute extends WebViewAttribute { class BlinkFeaturesAttribute extends WebViewAttribute {
constructor (webViewImpl) { constructor (webViewImpl: WebViewImpl) {
super(webViewConstants.ATTRIBUTE_BLINKFEATURES, webViewImpl) super(WEB_VIEW_CONSTANTS.ATTRIBUTE_BLINKFEATURES, webViewImpl)
} }
} }
// Attribute that specifies the blink features to be disabled. // Attribute that specifies the blink features to be disabled.
class DisableBlinkFeaturesAttribute extends WebViewAttribute { class DisableBlinkFeaturesAttribute extends WebViewAttribute {
constructor (webViewImpl) { constructor (webViewImpl: WebViewImpl) {
super(webViewConstants.ATTRIBUTE_DISABLEBLINKFEATURES, webViewImpl) super(WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEBLINKFEATURES, webViewImpl)
} }
} }
// Attribute that specifies the web preferences to be enabled. // Attribute that specifies the web preferences to be enabled.
class WebPreferencesAttribute extends WebViewAttribute { class WebPreferencesAttribute extends WebViewAttribute {
constructor (webViewImpl) { constructor (webViewImpl: WebViewImpl) {
super(webViewConstants.ATTRIBUTE_WEBPREFERENCES, webViewImpl) super(WEB_VIEW_CONSTANTS.ATTRIBUTE_WEBPREFERENCES, webViewImpl)
} }
} }
class EnableRemoteModuleAttribute extends WebViewAttribute { class EnableRemoteModuleAttribute extends WebViewAttribute {
constructor (webViewImpl) { constructor (webViewImpl: WebViewImpl) {
super(webViewConstants.ATTRIBUTE_ENABLEREMOTEMODULE, webViewImpl) super(WEB_VIEW_CONSTANTS.ATTRIBUTE_ENABLEREMOTEMODULE, webViewImpl)
} }
getValue () { public getValue () {
return this.webViewImpl.webviewNode.getAttribute(this.name) !== 'false' return this.webViewImpl.webviewNode.getAttribute(this.name) !== 'false'
} }
setValue (value) { public setValue (value: any) {
this.webViewImpl.webviewNode.setAttribute(this.name, value ? 'true' : 'false') this.webViewImpl.webviewNode.setAttribute(this.name, value ? 'true' : 'false')
} }
} }
@ -261,17 +271,17 @@ class EnableRemoteModuleAttribute extends WebViewAttribute {
// Sets up all of the webview attributes. // Sets up all of the webview attributes.
WebViewImpl.prototype.setupWebViewAttributes = function () { WebViewImpl.prototype.setupWebViewAttributes = function () {
this.attributes = {} this.attributes = {}
this.attributes[webViewConstants.ATTRIBUTE_PARTITION] = new PartitionAttribute(this) this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION] = new PartitionAttribute(this)
this.attributes[webViewConstants.ATTRIBUTE_SRC] = new SrcAttribute(this) this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC] = new SrcAttribute(this)
this.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER] = new HttpReferrerAttribute(this) this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_HTTPREFERRER] = new HttpReferrerAttribute(this)
this.attributes[webViewConstants.ATTRIBUTE_USERAGENT] = new UserAgentAttribute(this) this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_USERAGENT] = new UserAgentAttribute(this)
this.attributes[webViewConstants.ATTRIBUTE_NODEINTEGRATION] = new BooleanAttribute(webViewConstants.ATTRIBUTE_NODEINTEGRATION, this) this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATION] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATION, this)
this.attributes[webViewConstants.ATTRIBUTE_PLUGINS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_PLUGINS, this) this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PLUGINS] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_PLUGINS, this)
this.attributes[webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY, this) this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEWEBSECURITY, this)
this.attributes[webViewConstants.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_ALLOWPOPUPS, this) this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_ALLOWPOPUPS, this)
this.attributes[webViewConstants.ATTRIBUTE_ENABLEREMOTEMODULE] = new EnableRemoteModuleAttribute(this) this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_ENABLEREMOTEMODULE] = new EnableRemoteModuleAttribute(this)
this.attributes[webViewConstants.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this) this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this)
this.attributes[webViewConstants.ATTRIBUTE_BLINKFEATURES] = new BlinkFeaturesAttribute(this) this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_BLINKFEATURES] = new BlinkFeaturesAttribute(this)
this.attributes[webViewConstants.ATTRIBUTE_DISABLEBLINKFEATURES] = new DisableBlinkFeaturesAttribute(this) this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEBLINKFEATURES] = new DisableBlinkFeaturesAttribute(this)
this.attributes[webViewConstants.ATTRIBUTE_WEBPREFERENCES] = new WebPreferencesAttribute(this) this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_WEBPREFERENCES] = new WebPreferencesAttribute(this)
} }

View file

@ -1,28 +0,0 @@
'use strict'
module.exports = {
// Attributes.
ATTRIBUTE_NAME: 'name',
ATTRIBUTE_PARTITION: 'partition',
ATTRIBUTE_SRC: 'src',
ATTRIBUTE_HTTPREFERRER: 'httpreferrer',
ATTRIBUTE_NODEINTEGRATION: 'nodeintegration',
ATTRIBUTE_ENABLEREMOTEMODULE: 'enableremotemodule',
ATTRIBUTE_PLUGINS: 'plugins',
ATTRIBUTE_DISABLEWEBSECURITY: 'disablewebsecurity',
ATTRIBUTE_ALLOWPOPUPS: 'allowpopups',
ATTRIBUTE_PRELOAD: 'preload',
ATTRIBUTE_USERAGENT: 'useragent',
ATTRIBUTE_BLINKFEATURES: 'blinkfeatures',
ATTRIBUTE_DISABLEBLINKFEATURES: 'disableblinkfeatures',
ATTRIBUTE_WEBPREFERENCES: 'webpreferences',
// Internal attribute.
ATTRIBUTE_INTERNALINSTANCEID: 'internalinstanceid',
// Error messages.
ERROR_MSG_ALREADY_NAVIGATED: 'The object has already navigated, so its partition cannot be changed.',
ERROR_MSG_CANNOT_INJECT_SCRIPT: '<webview>: ' + 'Script cannot be injected into content until the page has loaded.',
ERROR_MSG_INVALID_PARTITION_ATTRIBUTE: 'Invalid partition attribute.',
ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE: 'Only "file:" protocol is supported in "preload" attribute.'
}

View file

@ -0,0 +1,26 @@
export const enum WEB_VIEW_CONSTANTS {
// Attributes.
ATTRIBUTE_NAME = 'name',
ATTRIBUTE_PARTITION = 'partition',
ATTRIBUTE_SRC = 'src',
ATTRIBUTE_HTTPREFERRER = 'httpreferrer',
ATTRIBUTE_NODEINTEGRATION = 'nodeintegration',
ATTRIBUTE_ENABLEREMOTEMODULE = 'enableremotemodule',
ATTRIBUTE_PLUGINS = 'plugins',
ATTRIBUTE_DISABLEWEBSECURITY = 'disablewebsecurity',
ATTRIBUTE_ALLOWPOPUPS = 'allowpopups',
ATTRIBUTE_PRELOAD = 'preload',
ATTRIBUTE_USERAGENT = 'useragent',
ATTRIBUTE_BLINKFEATURES = 'blinkfeatures',
ATTRIBUTE_DISABLEBLINKFEATURES = 'disableblinkfeatures',
ATTRIBUTE_WEBPREFERENCES = 'webpreferences',
// Internal attribute.
ATTRIBUTE_INTERNALINSTANCEID = 'internalinstanceid',
// Error messages.
ERROR_MSG_ALREADY_NAVIGATED = 'The object has already navigated, so its partition cannot be changed.',
ERROR_MSG_CANNOT_INJECT_SCRIPT = '<webview> = ' + 'Script cannot be injected into content until the page has loaded.',
ERROR_MSG_INVALID_PARTITION_ATTRIBUTE = 'Invalid partition attribute.',
ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE = 'Only "file:" protocol is supported in "preload" attribute.'
}

View file

@ -1,5 +1,3 @@
'use strict'
// When using context isolation, the WebViewElement and the custom element // 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. // methods have to be defined in the main world to be able to be registered.
// //
@ -10,27 +8,30 @@
// which runs in browserify environment instead of Node environment, all native // which runs in browserify environment instead of Node environment, all native
// modules must be passed from outside, all included files must be plain JS. // modules must be passed from outside, all included files must be plain JS.
const webViewConstants = require('@electron/internal/renderer/web-view/web-view-constants') import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants'
import { WebViewImpl, webViewImplModule } from '@electron/internal/renderer/web-view/web-view-impl'
// Return a WebViewElement class that is defined in this context. // Return a WebViewElement class that is defined in this context.
const defineWebViewElement = (v8Util, webViewImpl) => { const defineWebViewElement = (v8Util: NodeJS.V8UtilBinding, webViewImpl: typeof webViewImplModule) => {
const { guestViewInternal, WebViewImpl } = webViewImpl const { guestViewInternal, WebViewImpl } = webViewImpl
return class WebViewElement extends HTMLElement { return class WebViewElement extends HTMLElement {
public internalInstanceId?: number;
static get observedAttributes () { static get observedAttributes () {
return [ return [
webViewConstants.ATTRIBUTE_PARTITION, WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION,
webViewConstants.ATTRIBUTE_SRC, WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC,
webViewConstants.ATTRIBUTE_HTTPREFERRER, WEB_VIEW_CONSTANTS.ATTRIBUTE_HTTPREFERRER,
webViewConstants.ATTRIBUTE_USERAGENT, WEB_VIEW_CONSTANTS.ATTRIBUTE_USERAGENT,
webViewConstants.ATTRIBUTE_NODEINTEGRATION, WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATION,
webViewConstants.ATTRIBUTE_PLUGINS, WEB_VIEW_CONSTANTS.ATTRIBUTE_PLUGINS,
webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY, WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEWEBSECURITY,
webViewConstants.ATTRIBUTE_ALLOWPOPUPS, WEB_VIEW_CONSTANTS.ATTRIBUTE_ALLOWPOPUPS,
webViewConstants.ATTRIBUTE_ENABLEREMOTEMODULE, WEB_VIEW_CONSTANTS.ATTRIBUTE_ENABLEREMOTEMODULE,
webViewConstants.ATTRIBUTE_PRELOAD, WEB_VIEW_CONSTANTS.ATTRIBUTE_PRELOAD,
webViewConstants.ATTRIBUTE_BLINKFEATURES, WEB_VIEW_CONSTANTS.ATTRIBUTE_BLINKFEATURES,
webViewConstants.ATTRIBUTE_DISABLEBLINKFEATURES, WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEBLINKFEATURES,
webViewConstants.ATTRIBUTE_WEBPREFERENCES WEB_VIEW_CONSTANTS.ATTRIBUTE_WEBPREFERENCES
] ]
} }
@ -40,26 +41,26 @@ const defineWebViewElement = (v8Util, webViewImpl) => {
} }
connectedCallback () { connectedCallback () {
const internal = v8Util.getHiddenValue(this, 'internal') const internal = v8Util.getHiddenValue<WebViewImpl>(this, 'internal')
if (!internal) { if (!internal) {
return return
} }
if (!internal.elementAttached) { if (!internal.elementAttached) {
guestViewInternal.registerEvents(internal, internal.viewInstanceId) guestViewInternal.registerEvents(internal, internal.viewInstanceId)
internal.elementAttached = true internal.elementAttached = true
internal.attributes[webViewConstants.ATTRIBUTE_SRC].parse() internal.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC].parse()
} }
} }
attributeChangedCallback (name, oldValue, newValue) { attributeChangedCallback (name: string, oldValue: any, newValue: any) {
const internal = v8Util.getHiddenValue(this, 'internal') const internal = v8Util.getHiddenValue<WebViewImpl>(this, 'internal')
if (internal) { if (internal) {
internal.handleWebviewAttributeMutation(name, oldValue, newValue) internal.handleWebviewAttributeMutation(name, oldValue, newValue)
} }
} }
disconnectedCallback () { disconnectedCallback () {
const internal = v8Util.getHiddenValue(this, 'internal') const internal = v8Util.getHiddenValue<WebViewImpl>(this, 'internal')
if (!internal) { if (!internal) {
return return
} }
@ -72,15 +73,18 @@ const defineWebViewElement = (v8Util, webViewImpl) => {
} }
// Register <webview> custom element. // Register <webview> custom element.
const registerWebViewElement = (v8Util, webViewImpl) => { const registerWebViewElement = (v8Util: NodeJS.V8UtilBinding, webViewImpl: typeof webViewImplModule) => {
const WebViewElement = defineWebViewElement(v8Util, webViewImpl) // I wish eslint wasn't so stupid, but it is
// eslint-disable-next-line
const WebViewElement = defineWebViewElement(v8Util, webViewImpl) as unknown as typeof ElectronInternal.WebViewElement
webViewImpl.setupMethods(WebViewElement) webViewImpl.setupMethods(WebViewElement)
// The customElements.define has to be called in a special scope. // The customElements.define has to be called in a special scope.
webViewImpl.webFrame.allowGuestViewElementDefinition(window, () => { const webFrame = webViewImpl.webFrame as ElectronInternal.WebFrameInternal
window.customElements.define('webview', WebViewElement) webFrame.allowGuestViewElementDefinition(window, () => {
window.WebView = WebViewElement window.customElements.define('webview', WebViewElement);
(window as any).WebView = WebViewElement
// Delete the callbacks so developers cannot call them and produce unexpected // Delete the callbacks so developers cannot call them and produce unexpected
// behavior. // behavior.
@ -90,21 +94,24 @@ const registerWebViewElement = (v8Util, webViewImpl) => {
// Now that |observedAttributes| has been retrieved, we can hide it from // Now that |observedAttributes| has been retrieved, we can hide it from
// user code as well. // user code as well.
delete WebViewElement.observedAttributes // TypeScript is concerned that we're deleting a read-only attribute
delete (WebViewElement as any).observedAttributes
}) })
} }
// Prepare to register the <webview> element. // Prepare to register the <webview> element.
const setupWebView = (v8Util, webViewImpl) => { export const setupWebView = (v8Util: NodeJS.V8UtilBinding, webViewImpl: typeof webViewImplModule) => {
const useCapture = true const useCapture = true
window.addEventListener('readystatechange', function listener (event) { const listener = (event: Event) => {
if (document.readyState === 'loading') { if (document.readyState === 'loading') {
return return
} }
webViewImpl.setupAttributes() webViewImpl.setupAttributes()
registerWebViewElement(v8Util, webViewImpl) registerWebViewElement(v8Util, webViewImpl)
window.removeEventListener(event.type, listener, useCapture)
}, useCapture)
}
module.exports = { setupWebView } window.removeEventListener(event.type, listener, useCapture)
}
window.addEventListener('readystatechange', listener, useCapture)
}

View file

@ -1,16 +1,15 @@
'use strict' import { deprecate, webFrame } from 'electron'
const { webFrame, deprecate } = require('electron') import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils'
import * as guestViewInternal from '@electron/internal/renderer/web-view/guest-view-internal'
const v8Util = process.atomBinding('v8_util') import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants'
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils') import {
const guestViewInternal = require('@electron/internal/renderer/web-view/guest-view-internal')
const webViewConstants = require('@electron/internal/renderer/web-view/web-view-constants')
const {
syncMethods, syncMethods,
asyncCallbackMethods, asyncCallbackMethods,
asyncPromiseMethods asyncPromiseMethods
} = require('@electron/internal/common/web-view-methods') } from '@electron/internal/common/web-view-methods'
const v8Util = process.atomBinding('v8_util')
// ID generator. // ID generator.
let nextId = 0 let nextId = 0
@ -20,16 +19,25 @@ const getNextId = function () {
} }
// Represents the internal state of the WebView node. // Represents the internal state of the WebView node.
class WebViewImpl { export class WebViewImpl {
constructor (webviewNode) { public beforeFirstNavigation = true
this.webviewNode = webviewNode public elementAttached = false
this.elementAttached = false public guestInstanceId?: number
this.beforeFirstNavigation = true public hasFocus = false
this.hasFocus = false public internalInstanceId?: number;
public resizeObserver?: ResizeObserver;
public userAgentOverride?: string;
public viewInstanceId: number
// on* Event handlers. // on* Event handlers.
this.on = {} public on: Record<string, any> = {}
public internalElement: HTMLIFrameElement
// Replaced in web-view-attributes
public attributes: Record<string, any> = {}
public setupWebViewAttributes (): void {}
constructor (public webviewNode: HTMLElement) {
// Create internal iframe element. // Create internal iframe element.
this.internalElement = this.createInternalElement() this.internalElement = this.createInternalElement()
const shadowRoot = this.webviewNode.attachShadow({ mode: 'open' }) const shadowRoot = this.webviewNode.attachShadow({ mode: 'open' })
@ -70,22 +78,17 @@ class WebViewImpl {
} }
this.beforeFirstNavigation = true this.beforeFirstNavigation = true
this.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId = true this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION].validPartitionId = true
// Since attachment swaps a local frame for a remote frame, we need our // Since attachment swaps a local frame for a remote frame, we need our
// internal iframe element to be local again before we can reattach. // internal iframe element to be local again before we can reattach.
const newFrame = this.createInternalElement() const newFrame = this.createInternalElement()
const oldFrame = this.internalElement const oldFrame = this.internalElement
this.internalElement = newFrame this.internalElement = newFrame
oldFrame.parentNode.replaceChild(newFrame, oldFrame)
}
// Sets the <webview>.request property. if (oldFrame && oldFrame.parentNode) {
setRequestPropertyOnWebViewNode (request) { oldFrame.parentNode.replaceChild(newFrame, oldFrame)
Object.defineProperty(this.webviewNode, 'request', { }
value: request,
enumerable: true
})
} }
// This observer monitors mutations to attributes of the <webview> and // This observer monitors mutations to attributes of the <webview> and
@ -93,7 +96,7 @@ class WebViewImpl {
// a BrowserPlugin property will update the corresponding BrowserPlugin // a BrowserPlugin property will update the corresponding BrowserPlugin
// attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more // attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more
// details. // details.
handleWebviewAttributeMutation (attributeName, oldValue, newValue) { handleWebviewAttributeMutation (attributeName: string, oldValue: any, newValue: any) {
if (!this.attributes[attributeName] || this.attributes[attributeName].ignoreMutation) { if (!this.attributes[attributeName] || this.attributes[attributeName].ignoreMutation) {
return return
} }
@ -103,7 +106,7 @@ class WebViewImpl {
} }
onElementResize () { onElementResize () {
const resizeEvent = new Event('resize') const resizeEvent = new Event('resize') as ElectronInternal.WebFrameResizeEvent
resizeEvent.newWidth = this.webviewNode.clientWidth resizeEvent.newWidth = this.webviewNode.clientWidth
resizeEvent.newHeight = this.webviewNode.clientHeight resizeEvent.newHeight = this.webviewNode.clientHeight
this.dispatchEvent(resizeEvent) this.dispatchEvent(resizeEvent)
@ -120,13 +123,13 @@ class WebViewImpl {
this.attachGuestInstance(guestViewInternal.createGuestSync(this.buildParams())) this.attachGuestInstance(guestViewInternal.createGuestSync(this.buildParams()))
} }
dispatchEvent (webViewEvent) { dispatchEvent (webViewEvent: Electron.Event) {
this.webviewNode.dispatchEvent(webViewEvent) this.webviewNode.dispatchEvent(webViewEvent)
} }
// Adds an 'on<event>' property on the webview, which can be used to set/unset // Adds an 'on<event>' property on the webview, which can be used to set/unset
// an event handler. // an event handler.
setupEventProperty (eventName) { setupEventProperty (eventName: string) {
const propertyName = `on${eventName.toLowerCase()}` const propertyName = `on${eventName.toLowerCase()}`
return Object.defineProperty(this.webviewNode, propertyName, { return Object.defineProperty(this.webviewNode, propertyName, {
get: () => { get: () => {
@ -146,14 +149,14 @@ class WebViewImpl {
} }
// Updates state upon loadcommit. // Updates state upon loadcommit.
onLoadCommit (webViewEvent) { onLoadCommit (webViewEvent: ElectronInternal.WebViewEvent) {
const oldValue = this.webviewNode.getAttribute(webViewConstants.ATTRIBUTE_SRC) const oldValue = this.webviewNode.getAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC)
const newValue = webViewEvent.url const newValue = webViewEvent.url
if (webViewEvent.isMainFrame && (oldValue !== newValue)) { if (webViewEvent.isMainFrame && (oldValue !== newValue)) {
// Touching the src attribute triggers a navigation. To avoid // Touching the src attribute triggers a navigation. To avoid
// triggering a page reload on every guest-initiated navigation, // triggering a page reload on every guest-initiated navigation,
// we do not handle this mutation. // we do not handle this mutation.
this.attributes[webViewConstants.ATTRIBUTE_SRC].setValueIgnoreMutation(newValue) this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC].setValueIgnoreMutation(newValue)
} }
} }
@ -166,52 +169,66 @@ class WebViewImpl {
} }
} }
onAttach (storagePartitionId) { onAttach (storagePartitionId: number) {
return this.attributes[webViewConstants.ATTRIBUTE_PARTITION].setValue(storagePartitionId) return this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION].setValue(storagePartitionId)
} }
buildParams () { buildParams () {
const params = { const params: Record<string, any> = {
instanceId: this.viewInstanceId, instanceId: this.viewInstanceId,
userAgentOverride: this.userAgentOverride userAgentOverride: this.userAgentOverride
} }
for (const attributeName in this.attributes) { for (const attributeName in this.attributes) {
if (this.attributes.hasOwnProperty(attributeName)) { if (this.attributes.hasOwnProperty(attributeName)) {
params[attributeName] = this.attributes[attributeName].getValue() params[attributeName] = this.attributes[attributeName].getValue()
} }
} }
return params return params
} }
attachGuestInstance (guestInstanceId) { attachGuestInstance (guestInstanceId: number) {
if (!this.elementAttached) { if (!this.elementAttached) {
// The element could be detached before we got response from browser. // The element could be detached before we got response from browser.
return return
} }
this.internalInstanceId = getNextId() this.internalInstanceId = getNextId()
this.guestInstanceId = guestInstanceId this.guestInstanceId = 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 */
// TODO(zcbenz): Should we deprecate the "resize" event? Wait, it is not // TODO(zcbenz): Should we deprecate the "resize" event? Wait, it is not
// even documented. // even documented.
this.resizeObserver = new ResizeObserver(this.onElementResize.bind(this)).observe(this.internalElement) this.resizeObserver = new ResizeObserver(this.onElementResize.bind(this))
this.resizeObserver.observe(this.internalElement)
} }
} }
const setupAttributes = () => { export const setupAttributes = () => {
require('@electron/internal/renderer/web-view/web-view-attributes') require('@electron/internal/renderer/web-view/web-view-attributes')
} }
const setupMethods = (WebViewElement) => { // I wish eslint wasn't so stupid, but it is
// eslint-disable-next-line
export const setupMethods = (WebViewElement: typeof ElectronInternal.WebViewElement) => {
// WebContents associated with this webview. // WebContents associated with this webview.
WebViewElement.prototype.getWebContents = function () { WebViewElement.prototype.getWebContents = function () {
const { getRemote } = require('@electron/internal/renderer/remote') const { getRemote } = require('@electron/internal/renderer/remote')
const getGuestWebContents = getRemote('getGuestWebContents') const getGuestWebContents = getRemote('getGuestWebContents')
const internal = v8Util.getHiddenValue(this, 'internal') const internal = v8Util.getHiddenValue<WebViewImpl>(this, 'internal')
if (!internal.guestInstanceId) { if (!internal.guestInstanceId) {
internal.createGuestSync() internal.createGuestSync()
} }
return getGuestWebContents(internal.guestInstanceId) return getGuestWebContents(internal.guestInstanceId)
} }
@ -220,8 +237,8 @@ const setupMethods = (WebViewElement) => {
this.contentWindow.focus() this.contentWindow.focus()
} }
const getGuestInstanceId = function (self) { const getGuestInstanceId = function (self: any) {
const internal = v8Util.getHiddenValue(self, 'internal') const internal = v8Util.getHiddenValue<WebViewImpl>(self, 'internal')
if (!internal.guestInstanceId) { if (!internal.guestInstanceId) {
throw new Error('The WebView must be attached to the DOM and the dom-ready event emitted before this method can be called.') throw new Error('The WebView must be attached to the DOM and the dom-ready event emitted before this method can be called.')
} }
@ -229,34 +246,41 @@ const setupMethods = (WebViewElement) => {
} }
// Forward proto.foo* method calls to WebViewImpl.foo*. // Forward proto.foo* method calls to WebViewImpl.foo*.
const createBlockHandler = function (method) { const createBlockHandler = function (method: string) {
return function (...args) { return function (this: any, ...args: Array<any>) {
return ipcRendererUtils.invokeSync('ELECTRON_GUEST_VIEW_MANAGER_CALL', getGuestInstanceId(this), method, args) return ipcRendererUtils.invokeSync('ELECTRON_GUEST_VIEW_MANAGER_CALL', getGuestInstanceId(this), method, args)
} }
} }
for (const method of syncMethods) { for (const method of syncMethods) {
WebViewElement.prototype[method] = createBlockHandler(method) (WebViewElement.prototype as Record<string, any>)[method] = createBlockHandler(method)
} }
const createNonBlockHandler = function (method) { const createNonBlockHandler = function (method: string) {
return function (...args) { return function (this: any, ...args: Array<any>) {
ipcRendererUtils.invoke('ELECTRON_GUEST_VIEW_MANAGER_CALL', getGuestInstanceId(this), method, args) ipcRendererUtils.invoke('ELECTRON_GUEST_VIEW_MANAGER_CALL', getGuestInstanceId(this), method, args)
} }
} }
for (const method of asyncCallbackMethods) { for (const method of asyncCallbackMethods) {
WebViewElement.prototype[method] = createNonBlockHandler(method) (WebViewElement.prototype as Record<string, any>)[method] = createNonBlockHandler(method)
} }
const createPromiseHandler = function (method) { const createPromiseHandler = function (method: string) {
return function (...args) { return function (this: any, ...args: Array<any>) {
return ipcRendererUtils.invoke('ELECTRON_GUEST_VIEW_MANAGER_CALL', getGuestInstanceId(this), method, args) return ipcRendererUtils.invoke('ELECTRON_GUEST_VIEW_MANAGER_CALL', getGuestInstanceId(this), method, args)
} }
} }
for (const method of asyncPromiseMethods) { for (const method of asyncPromiseMethods) {
WebViewElement.prototype[method] = deprecate.promisify(createPromiseHandler(method)) (WebViewElement.prototype as Record<string, any>)[method] = deprecate.promisify(createPromiseHandler(method))
} }
} }
module.exports = { setupAttributes, setupMethods, guestViewInternal, webFrame, WebViewImpl } export const webViewImplModule = {
setupAttributes,
setupMethods,
guestViewInternal,
webFrame,
WebViewImpl
}

View file

@ -1,9 +1,8 @@
'use strict' import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'
const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal')
const v8Util = process.atomBinding('v8_util') const v8Util = process.atomBinding('v8_util')
function handleFocusBlur (guestInstanceId) { function handleFocusBlur (guestInstanceId: number) {
// Note that while Chromium content APIs have observer for focus/blur, they // Note that while Chromium content APIs have observer for focus/blur, they
// unfortunately do not work for webview. // unfortunately do not work for webview.
@ -16,15 +15,17 @@ function handleFocusBlur (guestInstanceId) {
}) })
} }
module.exports = function (contextIsolation, webviewTag, guestInstanceId) { export function webViewInit (
contextIsolation: boolean, webviewTag: ElectronInternal.WebViewElement, guestInstanceId: number
) {
// Don't allow recursive `<webview>`. // Don't allow recursive `<webview>`.
if (webviewTag && guestInstanceId == null) { if (webviewTag && guestInstanceId == null) {
const webViewImpl = require('@electron/internal/renderer/web-view/web-view-impl') const { webViewImplModule } = require('@electron/internal/renderer/web-view/web-view-impl')
if (contextIsolation) { if (contextIsolation) {
v8Util.setHiddenValue(window, 'web-view-impl', webViewImpl) v8Util.setHiddenValue(window, 'web-view-impl', webViewImplModule)
} else { } else {
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element') const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element')
setupWebView(v8Util, webViewImpl) setupWebView(v8Util, webViewImplModule)
} }
} }

View file

@ -116,7 +116,8 @@ const guestInstanceId = binding.guestInstanceId && parseInt(binding.guestInstanc
// Load webview tag implementation. // Load webview tag implementation.
if (process.isMainFrame) { if (process.isMainFrame) {
require('@electron/internal/renderer/web-view/web-view-init')(contextIsolation, isWebViewTagEnabled, guestInstanceId) const { webViewInit } = require('@electron/internal/renderer/web-view/web-view-init')
webViewInit(contextIsolation, isWebViewTagEnabled, guestInstanceId)
} }
const errorUtils = require('@electron/internal/common/error-utils') const errorUtils = require('@electron/internal/common/error-utils')

View file

@ -62,5 +62,56 @@ declare interface Window {
FileSystemWorkspaceBinding: { FileSystemWorkspaceBinding: {
completeURL: (project: string, path: string) => string; completeURL: (project: string, path: string) => string;
} }
} };
ResizeObserver: ResizeObserver;
}
/**
* The ResizeObserver interface is used to observe changes to Element's content
* rect.
*
* It is modeled after MutationObserver and IntersectionObserver.
*/
declare class ResizeObserver {
constructor (callback: ResizeObserverCallback);
/**
* Adds target to the list of observed elements.
*/
observe: (target: Element) => void;
/**
* Removes target from the list of observed elements.
*/
unobserve: (target: Element) => void;
/**
* Clears both the observationTargets and activeTargets lists.
*/
disconnect: () => void;
}
/**
* This callback delivers ResizeObserver's notifications. It is invoked by a
* broadcast active observations algorithm.
*/
interface ResizeObserverCallback {
(entries: ResizeObserverEntry[], observer: ResizeObserver): void;
}
interface ResizeObserverEntry {
/**
* @param target The Element whose size has changed.
*/
new (target: Element): ResizeObserverEntry;
/**
* The Element whose size has changed.
*/
readonly target: Element;
/**
* Element's content rect when ResizeObserverCallback is invoked.
*/
readonly contentRect: DOMRectReadOnly;
} }

View file

@ -80,6 +80,34 @@ declare namespace ElectronInternal {
on(channel: string, listener: (event: IpcMainInternalEvent, ...args: any[]) => void): this; on(channel: string, listener: (event: IpcMainInternalEvent, ...args: any[]) => void): this;
once(channel: string, listener: (event: IpcMainInternalEvent, ...args: any[]) => void): this; once(channel: string, listener: (event: IpcMainInternalEvent, ...args: any[]) => void): this;
} }
interface WebFrameInternal extends Electron.WebFrame {
getWebFrameId(window: Window): number;
allowGuestViewElementDefinition(window: Window, context: any): void;
}
interface WebFrameResizeEvent extends Electron.Event {
newWidth: number;
newHeight: number;
}
interface WebViewEvent extends Event {
url: string;
isMainFrame: boolean;
}
abstract class WebViewElement extends HTMLElement {
static observedAttributes: Array<string>;
public contentWindow: Window;
public connectedCallback(): void;
public attributeChangedCallback(): void;
public disconnectedCallback(): void;
// Created in web-view-impl
public getWebContents(): Electron.WebContents;
}
} }
declare namespace Chrome { declare namespace Chrome {