electron/lib/renderer/web-view/guest-view-internal.ts
Robo c438b93f18
fix: ensure guest-embedder map is updated when webview is removed (#23342)
There are use cases of webview where the container holding the webview is not
actually destroyed first, instead just webview gets removed from DOM, in such
situations the browser process map is not updated accordingly and holds reference
to stale guest contents, and any window operations like scroll, resize or keyboard
events that has to chain through browser embedder will lead to UAF crash.

Ref: https://github.com/microsoft/vscode/issues/92420
2020-04-30 21:33:14 -07:00

118 lines
4.6 KiB
TypeScript

import { webFrame, IpcMessageEvent } from 'electron';
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
import * as ipcRendererUtils 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: [],
'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 ipcRendererInternal.invoke('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', params);
}
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');
}
ipcRendererInternal.invoke('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', embedderFrameId, elementInstanceId, guestInstanceId, params);
}
export function detachGuest (guestInstanceId: number) {
return ipcRendererUtils.invokeSync('ELECTRON_GUEST_VIEW_MANAGER_DETACH_GUEST', guestInstanceId);
}
export const guestViewInternalModule = {
deregisterEvents,
createGuest,
attachGuest,
detachGuest
};