diff --git a/lib/browser/guest-view-manager.ts b/lib/browser/guest-view-manager.ts index f3aeeeeffd49..ca4f7aba0d5f 100644 --- a/lib/browser/guest-view-manager.ts +++ b/lib/browser/guest-view-manager.ts @@ -7,7 +7,7 @@ import { webViewEvents } from '@electron/internal/browser/web-view-events'; import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages'; interface GuestInstance { - elementInstanceId: number; + elementInstanceId?: number; visibilityState?: VisibilityState; embedder: Electron.WebContents; guest: Electron.WebContents; @@ -45,7 +45,6 @@ function makeWebPreferences (embedder: Electron.WebContents, params: Record) { - const webPreferences = makeWebPreferences(embedder, params); - const event = eventBinding.createWithSender(embedder); - - embedder.emit('will-attach-webview', event, webPreferences, params); - if (event.defaultPrevented) { - return -1; - } - // eslint-disable-next-line no-undef const guest = (webContents as typeof ElectronInternal.WebContents).create({ - ...webPreferences, type: 'webview', + partition: params.partition, embedder }); - const guestInstanceId = guest.id; guestInstances.set(guestInstanceId, { - elementInstanceId, guest, embedder }); @@ -108,6 +97,9 @@ const createGuest = function (embedder: Electron.WebContents, embedderFrameId: n // Init guest web view after attached. guest.once('did-attach' as any, function (this: Electron.WebContents, event: Electron.Event) { + params = this.attachParams!; + delete this.attachParams; + const previouslyAttached = this.viewInstanceId != null; this.viewInstanceId = params.instanceId; @@ -177,25 +169,76 @@ const createGuest = function (embedder: Electron.WebContents, embedderFrameId: n } }); + if (attachGuest(embedder, embedderFrameId, elementInstanceId, guestInstanceId, params)) { + return guestInstanceId; + } + + return -1; +}; + +// Attach the guest to an element of embedder. +const attachGuest = function (embedder: Electron.WebContents, embedderFrameId: number, elementInstanceId: number, guestInstanceId: number, params: Record) { // Destroy the old guest when attaching. const key = `${embedder.id}-${elementInstanceId}`; const oldGuestInstanceId = embedderElementsMap.get(key); if (oldGuestInstanceId != null) { + // Reattachment to the same guest is just a no-op. + if (oldGuestInstanceId === guestInstanceId) { + return false; + } + const oldGuestInstance = guestInstances.get(oldGuestInstanceId); if (oldGuestInstance) { oldGuestInstance.guest.detachFromOuterFrame(); } } + const guestInstance = guestInstances.get(guestInstanceId); + // If this isn't a valid guest instance then do nothing. + if (!guestInstance) { + console.error(new Error(`Guest attach failed: Invalid guestInstanceId ${guestInstanceId}`)); + return false; + } + const { guest } = guestInstance; + if (guest.hostWebContents !== embedder) { + console.error(new Error(`Guest attach failed: Access denied to guestInstanceId ${guestInstanceId}`)); + return false; + } + + // If this guest is already attached to an element then remove it + if (guestInstance.elementInstanceId) { + const oldKey = `${guestInstance.embedder.id}-${guestInstance.elementInstanceId}`; + embedderElementsMap.delete(oldKey); + + // Remove guest from embedder if moving across web views + if (guest.viewInstanceId !== params.instanceId) { + webViewManager.removeGuest(guestInstance.embedder, guestInstanceId); + guestInstance.embedder._sendInternal(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DESTROY_GUEST}-${guest.viewInstanceId}`); + } + } + + const webPreferences = makeWebPreferences(embedder, params); + + const event = eventBinding.createWithSender(embedder); + embedder.emit('will-attach-webview', event, webPreferences, params); + if (event.defaultPrevented) { + if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId; + guest.destroy(); + return false; + } + + guest.attachParams = params; embedderElementsMap.set(key, guestInstanceId); + guest.setEmbedder(embedder); + guestInstance.embedder = embedder; + guestInstance.elementInstanceId = elementInstanceId; watchEmbedder(embedder); webViewManager.addGuest(guestInstanceId, embedder, guest, webPreferences); guest.attachToIframe(embedder, embedderFrameId); - - return guestInstanceId; + return true; }; // Remove an guest-embedder relationship. diff --git a/lib/common/ipc-messages.ts b/lib/common/ipc-messages.ts index 659b758ca976..11bf7fdd832d 100644 --- a/lib/common/ipc-messages.ts +++ b/lib/common/ipc-messages.ts @@ -8,6 +8,7 @@ export const enum IPC_MESSAGES { GUEST_INSTANCE_VISIBILITY_CHANGE = 'GUEST_INSTANCE_VISIBILITY_CHANGE', + GUEST_VIEW_INTERNAL_DESTROY_GUEST = 'GUEST_VIEW_INTERNAL_DESTROY_GUEST', GUEST_VIEW_INTERNAL_DISPATCH_EVENT = 'GUEST_VIEW_INTERNAL_DISPATCH_EVENT', GUEST_VIEW_MANAGER_CREATE_AND_ATTACH_GUEST = 'GUEST_VIEW_MANAGER_CREATE_AND_ATTACH_GUEST', diff --git a/lib/renderer/web-view/guest-view-internal.ts b/lib/renderer/web-view/guest-view-internal.ts index bd8a5a774a4d..d8b9b61c135f 100644 --- a/lib/renderer/web-view/guest-view-internal.ts +++ b/lib/renderer/web-view/guest-view-internal.ts @@ -6,6 +6,7 @@ const { mainFrame: webFrame } = process._linkedBinding('electron_renderer_web_fr export interface GuestViewDelegate { dispatchEvent (eventName: string, props: Record): void; + reset(): void; } const DEPRECATED_EVENTS: Record = { @@ -13,6 +14,11 @@ const DEPRECATED_EVENTS: Record = { } as const; export function registerEvents (viewInstanceId: number, delegate: GuestViewDelegate) { + ipcRendererInternal.on(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DESTROY_GUEST}-${viewInstanceId}`, function () { + delegate.reset(); + delegate.dispatchEvent('destroyed', {}); + }); + ipcRendererInternal.on(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT}-${viewInstanceId}`, function (event, eventName, props) { if (DEPRECATED_EVENTS[eventName] != null) { delegate.dispatchEvent(DEPRECATED_EVENTS[eventName], props); @@ -23,6 +29,7 @@ export function registerEvents (viewInstanceId: number, delegate: GuestViewDeleg } export function deregisterEvents (viewInstanceId: number) { + ipcRendererInternal.removeAllListeners(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DESTROY_GUEST}-${viewInstanceId}`); ipcRendererInternal.removeAllListeners(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT}-${viewInstanceId}`); } diff --git a/lib/renderer/web-view/web-view-element.ts b/lib/renderer/web-view/web-view-element.ts index ee290ce09b0f..84ac5a9ba79c 100644 --- a/lib/renderer/web-view/web-view-element.ts +++ b/lib/renderer/web-view/web-view-element.ts @@ -55,7 +55,8 @@ const defineWebViewElement = (hooks: WebViewImplHooks) => { } if (!internal.elementAttached) { hooks.guestViewInternal.registerEvents(internal.viewInstanceId, { - dispatchEvent: internal.dispatchEvent.bind(internal) + dispatchEvent: internal.dispatchEvent.bind(internal), + reset: internal.reset.bind(internal) }); internal.elementAttached = true; (internal.attributes.get(WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC) as SrcAttribute).parse(); diff --git a/lib/renderer/web-view/web-view-impl.ts b/lib/renderer/web-view/web-view-impl.ts index 295879b3df8a..d12eff2f69b9 100644 --- a/lib/renderer/web-view/web-view-impl.ts +++ b/lib/renderer/web-view/web-view-impl.ts @@ -193,7 +193,7 @@ export class WebViewImpl { attachGuestInstance (guestInstanceId: number) { if (guestInstanceId === -1) { - this.dispatchEvent('destroyed'); + // Do nothing return; } diff --git a/shell/browser/api/electron_api_web_view_manager.cc b/shell/browser/api/electron_api_web_view_manager.cc index 2bf03616b711..7758c59cd5fd 100644 --- a/shell/browser/api/electron_api_web_view_manager.cc +++ b/shell/browser/api/electron_api_web_view_manager.cc @@ -28,6 +28,10 @@ void AddGuest(int guest_instance_id, electron::WebContentsZoomController::FromWebContents(guest_web_contents) ->SetDefaultZoomFactor(zoom_factor); } + + WebContentsPreferences::From(guest_web_contents)->Merge(options); + // Trigger re-calculation of webkit prefs. + guest_web_contents->NotifyPreferencesChanged(); } void RemoveGuest(content::WebContents* embedder, int guest_instance_id) { diff --git a/shell/browser/web_contents_preferences.cc b/shell/browser/web_contents_preferences.cc index efa0aa9fdae4..80e3844b5b41 100644 --- a/shell/browser/web_contents_preferences.cc +++ b/shell/browser/web_contents_preferences.cc @@ -176,7 +176,11 @@ void WebContentsPreferences::Clear() { void WebContentsPreferences::SetFromDictionary( const gin_helper::Dictionary& web_preferences) { Clear(); + Merge(web_preferences); +} +void WebContentsPreferences::Merge( + const gin_helper::Dictionary& web_preferences) { web_preferences.Get(options::kPlugins, &plugins_); web_preferences.Get(options::kExperimentalFeatures, &experimental_features_); web_preferences.Get(options::kNodeIntegration, &node_integration_); diff --git a/shell/browser/web_contents_preferences.h b/shell/browser/web_contents_preferences.h index 4973d161a8ea..bdf7ca4de3ae 100644 --- a/shell/browser/web_contents_preferences.h +++ b/shell/browser/web_contents_preferences.h @@ -40,6 +40,8 @@ class WebContentsPreferences WebContentsPreferences(const WebContentsPreferences&) = delete; WebContentsPreferences& operator=(const WebContentsPreferences&) = delete; + void Merge(const gin_helper::Dictionary& new_web_preferences); + void SetFromDictionary(const gin_helper::Dictionary& new_web_preferences); // Append command paramters according to preferences. diff --git a/typings/internal-electron.d.ts b/typings/internal-electron.d.ts index 55feb0a0d7b9..20135dd4ea11 100644 --- a/typings/internal-electron.d.ts +++ b/typings/internal-electron.d.ts @@ -81,6 +81,7 @@ declare namespace Electron { attachToIframe(embedderWebContents: Electron.WebContents, embedderFrameId: number): void; detachFromOuterFrame(): void; setEmbedder(embedder: Electron.WebContents): void; + attachParams?: Record; viewInstanceId: number; }