feat: expose initiator in navigation events (#37085)

This commit is contained in:
Jeremy Rose 2023-02-28 11:08:22 -08:00 committed by GitHub
parent ed7b5c44a2
commit 3a5ae28c95
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 107 additions and 55 deletions

View file

@ -207,8 +207,23 @@ See [`window.open()`](window-open.md) for more details and how to use this in co
Returns: Returns:
* `event` Event * `details` Event<>
* `url` string * `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 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. 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: Returns:
* `event` Event * `details` Event<>
* `url` string * `url` string - The URL the frame is navigating to.
* `isInPlace` boolean * `isSameDocument` boolean - Whether the navigation happened without changing
* `isMainFrame` boolean document. Examples of same document navigations are reference fragment
* `frameProcessId` Integer navigations, pushState/replaceState, and same page history navigation.
* `frameRoutingId` Integer * `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 Emitted when any frame (including main) starts navigating.
`true` for in-page navigations.
#### Event: 'will-redirect' #### Event: 'will-redirect'
Returns: Returns:
* `event` Event * `details` Event<>
* `url` string * `url` string - The URL the frame is navigating to.
* `isInPlace` boolean * `isSameDocument` boolean - Whether the navigation happened without changing
* `isMainFrame` boolean document. Examples of same document navigations are reference fragment
* `frameProcessId` Integer navigations, pushState/replaceState, and same page history navigation.
* `frameRoutingId` Integer * `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 Emitted when a server side redirect occurs during navigation. For example a 302
redirect. redirect.
@ -260,12 +296,23 @@ redirect).
Returns: Returns:
* `event` Event * `details` Event<>
* `url` string * `url` string - The URL the frame is navigating to.
* `isInPlace` boolean * `isSameDocument` boolean - Whether the navigation happened without changing
* `isMainFrame` boolean document. Examples of same document navigations are reference fragment
* `frameProcessId` Integer navigations, pushState/replaceState, and same page history navigation.
* `frameRoutingId` Integer * `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 Emitted after a server side redirect occurs during navigation. For example a 302
redirect. redirect.

View file

@ -112,6 +112,7 @@
#include "shell/common/gin_converters/gurl_converter.h" #include "shell/common/gin_converters/gurl_converter.h"
#include "shell/common/gin_converters/image_converter.h" #include "shell/common/gin_converters/image_converter.h"
#include "shell/common/gin_converters/net_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_converters/value_converter.h"
#include "shell/common/gin_helper/dictionary.h" #include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/object_template_builder.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 // |old_host| can be a nullptr so we use |new_host| for looking up the
// WebFrameMain instance. // WebFrameMain instance.
auto* web_frame = auto* web_frame = WebFrameMain::FromRenderFrameHost(new_host);
WebFrameMain::FromFrameTreeNodeId(new_host->GetFrameTreeNodeId());
if (web_frame) { if (web_frame) {
web_frame->UpdateRenderFrameHost(new_host); web_frame->UpdateRenderFrameHost(new_host);
} }
@ -1761,7 +1761,7 @@ void WebContents::DidStopLoading() {
} }
bool WebContents::EmitNavigationEvent( bool WebContents::EmitNavigationEvent(
const std::string& event, const std::string& event_name,
content::NavigationHandle* navigation_handle) { content::NavigationHandle* navigation_handle) {
bool is_main_frame = navigation_handle->IsInMainFrame(); bool is_main_frame = navigation_handle->IsInMainFrame();
int frame_tree_node_id = navigation_handle->GetFrameTreeNodeId(); int frame_tree_node_id = navigation_handle->GetFrameTreeNodeId();
@ -1782,8 +1782,30 @@ bool WebContents::EmitNavigationEvent(
} }
bool is_same_document = navigation_handle->IsSameDocument(); bool is_same_document = navigation_handle->IsSameDocument();
auto url = navigation_handle->GetURL(); auto url = navigation_handle->GetURL();
return Emit(event, url, is_same_document, is_main_frame, frame_process_id, content::RenderFrameHost* initiator_frame_host =
frame_routing_id); 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<gin_helper::internal::Event> event =
gin_helper::internal::Event::New(isolate);
v8::Local<v8::Object> event_object = event.ToV8().As<v8::Object>();
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, void WebContents::Message(bool internal,

View file

@ -5,7 +5,8 @@ import * as fs from 'fs';
import * as qs from 'querystring'; import * as qs from 'querystring';
import * as http from 'http'; import * as http from 'http';
import * as os from 'os'; 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 { emittedUntil, emittedNTimes } from './lib/events-helpers';
import { ifit, ifdescribe, defer, listen } from './lib/spec-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 () => { it('is triggered when a cross-origin iframe navigates _top', async () => {
await w.loadURL(`data:text/html,<iframe src="${url}/navigate-top"></iframe>`); w.loadURL(`data:text/html,<iframe src="http://127.0.0.1:${(server.address() as AddressInfo).port}/navigate-top"></iframe>`);
await setTimeout(1000); await emittedUntil(w.webContents, 'did-frame-finish-load', (e: any, isMainFrame: boolean) => !isMainFrame);
w.webContents.debugger.attach('1.1'); let initiator: WebFrameMain | undefined;
const targets = await w.webContents.debugger.sendCommand('Target.getTargets'); w.webContents.on('will-navigate', (e) => {
const iframeTarget = targets.targetInfos.find((t: any) => t.type === 'iframe'); initiator = e.initiator;
const { sessionId } = await w.webContents.debugger.sendCommand('Target.attachToTarget', {
targetId: iframeTarget.targetId,
flatten: true
}); });
let willNavigateEmitted = false; const subframe = w.webContents.mainFrame.frames[0];
w.webContents.on('will-navigate', () => { subframe.executeJavaScript('document.getElementsByTagName("a")[0].click()', true);
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);
await once(w.webContents, 'did-navigate'); await once(w.webContents, 'did-navigate');
expect(willNavigateEmitted).to.be.true(); expect(initiator).not.to.be.undefined();
expect(initiator).to.equal(subframe);
}); });
}); });