From 3a5ae28c9532004f55ffd2198731e530f94a59bf Mon Sep 17 00:00:00 2001 From: Jeremy Rose Date: Tue, 28 Feb 2023 11:08:22 -0800 Subject: [PATCH] feat: expose initiator in navigation events (#37085) --- docs/api/web-contents.md | 91 ++++++++++++++----- .../browser/api/electron_api_web_contents.cc | 32 ++++++- spec/api-browser-window-spec.ts | 39 +++----- 3 files changed, 107 insertions(+), 55 deletions(-) diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index ee47301e120b..6fba7b988e53 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -207,8 +207,23 @@ See [`window.open()`](window-open.md) for more details and how to use this in co Returns: -* `event` Event -* `url` string +* `details` Event<> + * `url` string - The URL the frame is navigating to. + * `isSameDocument` boolean - Whether the navigation happened without changing + document. Examples of same document navigations are reference fragment + navigations, pushState/replaceState, and same page history navigation. + * `isMainFrame` boolean - True if the navigation is taking place in a main frame. + * `frame` WebFrameMain - The frame to be navigated. + * `initiator` WebFrameMain (optional) - The frame which initiated the + navigation, which can be a parent frame (e.g. via `window.open` with a + frame's name), or null if the navigation was not initiated by a frame. This + can also be null if the initiating frame was deleted before the event was + emitted. +* `url` string _Deprecated_ +* `isInPlace` boolean _Deprecated_ +* `isMainFrame` boolean _Deprecated_ +* `frameProcessId` Integer _Deprecated_ +* `frameRoutingId` Integer _Deprecated_ Emitted when a user or the page wants to start navigation. It can happen when the `window.location` object is changed or a user clicks a link in the page. @@ -226,26 +241,47 @@ Calling `event.preventDefault()` will prevent the navigation. Returns: -* `event` Event -* `url` string -* `isInPlace` boolean -* `isMainFrame` boolean -* `frameProcessId` Integer -* `frameRoutingId` Integer +* `details` Event<> + * `url` string - The URL the frame is navigating to. + * `isSameDocument` boolean - Whether the navigation happened without changing + document. Examples of same document navigations are reference fragment + navigations, pushState/replaceState, and same page history navigation. + * `isMainFrame` boolean - True if the navigation is taking place in a main frame. + * `frame` WebFrameMain - The frame to be navigated. + * `initiator` WebFrameMain (optional) - The frame which initiated the + navigation, which can be a parent frame (e.g. via `window.open` with a + frame's name), or null if the navigation was not initiated by a frame. This + can also be null if the initiating frame was deleted before the event was + emitted. +* `url` string _Deprecated_ +* `isInPlace` boolean _Deprecated_ +* `isMainFrame` boolean _Deprecated_ +* `frameProcessId` Integer _Deprecated_ +* `frameRoutingId` Integer _Deprecated_ -Emitted when any frame (including main) starts navigating. `isInPlace` will be -`true` for in-page navigations. +Emitted when any frame (including main) starts navigating. #### Event: 'will-redirect' Returns: -* `event` Event -* `url` string -* `isInPlace` boolean -* `isMainFrame` boolean -* `frameProcessId` Integer -* `frameRoutingId` Integer +* `details` Event<> + * `url` string - The URL the frame is navigating to. + * `isSameDocument` boolean - Whether the navigation happened without changing + document. Examples of same document navigations are reference fragment + navigations, pushState/replaceState, and same page history navigation. + * `isMainFrame` boolean - True if the navigation is taking place in a main frame. + * `frame` WebFrameMain - The frame to be navigated. + * `initiator` WebFrameMain (optional) - The frame which initiated the + navigation, which can be a parent frame (e.g. via `window.open` with a + frame's name), or null if the navigation was not initiated by a frame. This + can also be null if the initiating frame was deleted before the event was + emitted. +* `url` string _Deprecated_ +* `isInPlace` boolean _Deprecated_ +* `isMainFrame` boolean _Deprecated_ +* `frameProcessId` Integer _Deprecated_ +* `frameRoutingId` Integer _Deprecated_ Emitted when a server side redirect occurs during navigation. For example a 302 redirect. @@ -260,12 +296,23 @@ redirect). Returns: -* `event` Event -* `url` string -* `isInPlace` boolean -* `isMainFrame` boolean -* `frameProcessId` Integer -* `frameRoutingId` Integer +* `details` Event<> + * `url` string - The URL the frame is navigating to. + * `isSameDocument` boolean - Whether the navigation happened without changing + document. Examples of same document navigations are reference fragment + navigations, pushState/replaceState, and same page history navigation. + * `isMainFrame` boolean - True if the navigation is taking place in a main frame. + * `frame` WebFrameMain - The frame to be navigated. + * `initiator` WebFrameMain (optional) - The frame which initiated the + navigation, which can be a parent frame (e.g. via `window.open` with a + frame's name), or null if the navigation was not initiated by a frame. This + can also be null if the initiating frame was deleted before the event was + emitted. +* `url` string _Deprecated_ +* `isInPlace` boolean _Deprecated_ +* `isMainFrame` boolean _Deprecated_ +* `frameProcessId` Integer _Deprecated_ +* `frameRoutingId` Integer _Deprecated_ Emitted after a server side redirect occurs during navigation. For example a 302 redirect. diff --git a/shell/browser/api/electron_api_web_contents.cc b/shell/browser/api/electron_api_web_contents.cc index 1f9c48a6424f..4df972cd3f82 100644 --- a/shell/browser/api/electron_api_web_contents.cc +++ b/shell/browser/api/electron_api_web_contents.cc @@ -112,6 +112,7 @@ #include "shell/common/gin_converters/gurl_converter.h" #include "shell/common/gin_converters/image_converter.h" #include "shell/common/gin_converters/net_converter.h" +#include "shell/common/gin_converters/optional_converter.h" #include "shell/common/gin_converters/value_converter.h" #include "shell/common/gin_helper/dictionary.h" #include "shell/common/gin_helper/object_template_builder.h" @@ -1621,8 +1622,7 @@ void WebContents::RenderFrameHostChanged(content::RenderFrameHost* old_host, // // |old_host| can be a nullptr so we use |new_host| for looking up the // WebFrameMain instance. - auto* web_frame = - WebFrameMain::FromFrameTreeNodeId(new_host->GetFrameTreeNodeId()); + auto* web_frame = WebFrameMain::FromRenderFrameHost(new_host); if (web_frame) { web_frame->UpdateRenderFrameHost(new_host); } @@ -1761,7 +1761,7 @@ void WebContents::DidStopLoading() { } bool WebContents::EmitNavigationEvent( - const std::string& event, + const std::string& event_name, content::NavigationHandle* navigation_handle) { bool is_main_frame = navigation_handle->IsInMainFrame(); int frame_tree_node_id = navigation_handle->GetFrameTreeNodeId(); @@ -1782,8 +1782,30 @@ bool WebContents::EmitNavigationEvent( } bool is_same_document = navigation_handle->IsSameDocument(); auto url = navigation_handle->GetURL(); - return Emit(event, url, is_same_document, is_main_frame, frame_process_id, - frame_routing_id); + content::RenderFrameHost* initiator_frame_host = + navigation_handle->GetInitiatorFrameToken().has_value() + ? content::RenderFrameHost::FromFrameToken( + navigation_handle->GetInitiatorProcessID(), + navigation_handle->GetInitiatorFrameToken().value()) + : nullptr; + + v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + + gin::Handle event = + gin_helper::internal::Event::New(isolate); + v8::Local event_object = event.ToV8().As(); + + gin_helper::Dictionary dict(isolate, event_object); + dict.Set("url", url); + dict.Set("isSameDocument", is_same_document); + dict.Set("isMainFrame", is_main_frame); + dict.Set("frame", frame_host); + dict.SetGetter("initiator", initiator_frame_host); + + EmitWithoutEvent(event_name, event, url, is_same_document, is_main_frame, + frame_process_id, frame_routing_id); + return event->GetDefaultPrevented(); } void WebContents::Message(bool internal, diff --git a/spec/api-browser-window-spec.ts b/spec/api-browser-window-spec.ts index 4eb4e908c497..298379a9fd64 100644 --- a/spec/api-browser-window-spec.ts +++ b/spec/api-browser-window-spec.ts @@ -5,7 +5,8 @@ import * as fs from 'fs'; import * as qs from 'querystring'; import * as http from 'http'; import * as os from 'os'; -import { app, BrowserWindow, BrowserView, dialog, ipcMain, OnBeforeSendHeadersListenerDetails, protocol, screen, webContents, session, WebContents } from 'electron/main'; +import { AddressInfo } from 'net'; +import { app, BrowserWindow, BrowserView, dialog, ipcMain, OnBeforeSendHeadersListenerDetails, protocol, screen, webContents, session, WebContents, WebFrameMain } from 'electron/main'; import { emittedUntil, emittedNTimes } from './lib/events-helpers'; import { ifit, ifdescribe, defer, listen } from './lib/spec-helpers'; @@ -553,35 +554,17 @@ describe('BrowserWindow module', () => { }); it('is triggered when a cross-origin iframe navigates _top', async () => { - await w.loadURL(`data:text/html,`); - await setTimeout(1000); - w.webContents.debugger.attach('1.1'); - const targets = await w.webContents.debugger.sendCommand('Target.getTargets'); - const iframeTarget = targets.targetInfos.find((t: any) => t.type === 'iframe'); - const { sessionId } = await w.webContents.debugger.sendCommand('Target.attachToTarget', { - targetId: iframeTarget.targetId, - flatten: true + w.loadURL(`data:text/html,`); + await emittedUntil(w.webContents, 'did-frame-finish-load', (e: any, isMainFrame: boolean) => !isMainFrame); + let initiator: WebFrameMain | undefined; + w.webContents.on('will-navigate', (e) => { + initiator = e.initiator; }); - let willNavigateEmitted = false; - w.webContents.on('will-navigate', () => { - willNavigateEmitted = true; - }); - await w.webContents.debugger.sendCommand('Input.dispatchMouseEvent', { - type: 'mousePressed', - x: 10, - y: 10, - clickCount: 1, - button: 'left' - }, sessionId); - await w.webContents.debugger.sendCommand('Input.dispatchMouseEvent', { - type: 'mouseReleased', - x: 10, - y: 10, - clickCount: 1, - button: 'left' - }, sessionId); + const subframe = w.webContents.mainFrame.frames[0]; + subframe.executeJavaScript('document.getElementsByTagName("a")[0].click()', true); await once(w.webContents, 'did-navigate'); - expect(willNavigateEmitted).to.be.true(); + expect(initiator).not.to.be.undefined(); + expect(initiator).to.equal(subframe); }); });