feat: webFrameMain.fromFrameToken (#47850)

* feat: webFrameMain.fromFrameToken

* refactor: return null instead of undefined

* docs: mention renderer webFrame property

* chore: undo null->undefined in wfm.fromId api
this will be updated in another pr
This commit is contained in:
Sam Maddock 2025-07-31 16:41:44 -04:00 committed by GitHub
commit 25e2459f31
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 55 additions and 3 deletions

View file

@ -66,6 +66,16 @@ These methods can be accessed from the `webFrameMain` module:
Returns `WebFrameMain | undefined` - A frame with the given process and routing IDs, Returns `WebFrameMain | undefined` - A frame with the given process and routing IDs,
or `undefined` if there is no WebFrameMain associated with the given IDs. or `undefined` if there is no WebFrameMain associated with the given IDs.
### `webFrameMain.fromFrameToken(processId, frameToken)`
* `processId` Integer - An `Integer` representing the internal ID of the process which owns the frame.
* `frameToken` string - A `string` token identifying the unique frame. Can also
be retrieved in the renderer process via
[`webFrame.frameToken`](web-frame.md#webframeframetoken-readonly).
Returns `WebFrameMain | null` - A frame with the given process and frame token,
or `null` if there is no WebFrameMain associated with the given IDs.
## Class: WebFrameMain ## Class: WebFrameMain
Process: [Main](../glossary.md#main-process)<br /> Process: [Main](../glossary.md#main-process)<br />

View file

@ -1,7 +1,7 @@
import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl'; import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
import { MessagePortMain } from '@electron/internal/browser/message-port-main'; import { MessagePortMain } from '@electron/internal/browser/message-port-main';
const { WebFrameMain, fromId } = process._linkedBinding('electron_browser_web_frame_main'); const { WebFrameMain, fromId, fromFrameToken } = process._linkedBinding('electron_browser_web_frame_main');
Object.defineProperty(WebFrameMain.prototype, 'ipc', { Object.defineProperty(WebFrameMain.prototype, 'ipc', {
get () { get () {
@ -43,5 +43,6 @@ WebFrameMain.prototype.postMessage = function (...args) {
}; };
export default { export default {
fromId fromId,
fromFrameToken
}; };

View file

@ -641,6 +641,31 @@ v8::Local<v8::Value> FromID(gin_helper::ErrorThrower thrower,
return WebFrameMain::From(thrower.isolate(), rfh).ToV8(); return WebFrameMain::From(thrower.isolate(), rfh).ToV8();
} }
v8::Local<v8::Value> FromFrameToken(gin_helper::ErrorThrower thrower,
int render_process_id,
std::string render_frame_token) {
if (!electron::Browser::Get()->is_ready()) {
thrower.ThrowError("WebFrameMain is available only after app ready");
return v8::Null(thrower.isolate());
}
auto token = base::Token::FromString(render_frame_token);
if (!token)
return v8::Null(thrower.isolate());
auto unguessable_token =
base::UnguessableToken::Deserialize(token->high(), token->low());
if (!unguessable_token)
return v8::Null(thrower.isolate());
auto frame_token = blink::LocalFrameToken(unguessable_token.value());
auto* rfh = content::RenderFrameHost::FromFrameToken(
content::GlobalRenderFrameHostToken(render_process_id, frame_token));
if (!rfh)
return v8::Null(thrower.isolate());
return WebFrameMain::From(thrower.isolate(), rfh).ToV8();
}
v8::Local<v8::Value> FromIdIfExists(gin_helper::ErrorThrower thrower, v8::Local<v8::Value> FromIdIfExists(gin_helper::ErrorThrower thrower,
int render_process_id, int render_process_id,
int render_frame_id) { int render_frame_id) {
@ -677,6 +702,7 @@ void Initialize(v8::Local<v8::Object> exports,
gin_helper::Dictionary dict{isolate, exports}; gin_helper::Dictionary dict{isolate, exports};
dict.Set("WebFrameMain", WebFrameMain::GetConstructor(isolate, context)); dict.Set("WebFrameMain", WebFrameMain::GetConstructor(isolate, context));
dict.SetMethod("fromId", &FromID); dict.SetMethod("fromId", &FromID);
dict.SetMethod("fromFrameToken", &FromFrameToken);
dict.SetMethod("_fromIdIfExists", &FromIdIfExists); dict.SetMethod("_fromIdIfExists", &FromIdIfExists);
dict.SetMethod("_fromFtnIdIfExists", &FromFtnIdIfExists); dict.SetMethod("_fromFtnIdIfExists", &FromFtnIdIfExists);
} }

View file

@ -496,6 +496,19 @@ describe('webFrameMain module', () => {
}); });
}); });
describe('webFrameMain.fromFrameToken', () => {
it('returns null for unknown IDs', () => {
expect(webFrameMain.fromFrameToken(0, '')).to.be.null();
});
it('can find existing frame', async () => {
const w = new BrowserWindow({ show: false });
const { mainFrame } = w.webContents;
const frame = webFrameMain.fromFrameToken(mainFrame.processId, mainFrame.frameToken);
expect(frame).to.equal(mainFrame);
});
});
describe('webFrameMain.collectJavaScriptCallStack', () => { describe('webFrameMain.collectJavaScriptCallStack', () => {
let server: Server; let server: Server;
before(async () => { before(async () => {

View file

@ -133,7 +133,8 @@ declare namespace NodeJS {
interface WebFrameMainBinding { interface WebFrameMainBinding {
WebFrameMain: typeof Electron.WebFrameMain; WebFrameMain: typeof Electron.WebFrameMain;
fromId(processId: number, routingId: number): Electron.WebFrameMain; fromId(processId: number, routingId: number): Electron.WebFrameMain | undefined;
fromFrameToken(processId: number, frameToken: string): Electron.WebFrameMain | null;
_fromIdIfExists(processId: number, routingId: number): Electron.WebFrameMain | null; _fromIdIfExists(processId: number, routingId: number): Electron.WebFrameMain | null;
_fromFtnIdIfExists(frameTreeNodeId: number): Electron.WebFrameMain | null; _fromFtnIdIfExists(frameTreeNodeId: number): Electron.WebFrameMain | null;
} }
@ -153,6 +154,7 @@ declare namespace NodeJS {
interface WebFrameBinding { interface WebFrameBinding {
mainFrame: InternalWebFrame; mainFrame: InternalWebFrame;
WebFrame: Electron.WebFrame;
} }
type DataPipe = { type DataPipe = {