diff --git a/docs/api/web-frame-main.md b/docs/api/web-frame-main.md
index b1c9f7791443..f72a935016f1 100644
--- a/docs/api/web-frame-main.md
+++ b/docs/api/web-frame-main.md
@@ -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,
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
Process: [Main](../glossary.md#main-process)
diff --git a/lib/browser/api/web-frame-main.ts b/lib/browser/api/web-frame-main.ts
index ce6023f843fd..de4e74b0dd13 100644
--- a/lib/browser/api/web-frame-main.ts
+++ b/lib/browser/api/web-frame-main.ts
@@ -1,7 +1,7 @@
import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
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', {
get () {
@@ -43,5 +43,6 @@ WebFrameMain.prototype.postMessage = function (...args) {
};
export default {
- fromId
+ fromId,
+ fromFrameToken
};
diff --git a/shell/browser/api/electron_api_web_frame_main.cc b/shell/browser/api/electron_api_web_frame_main.cc
index 5422e3204d42..2888ebaa09da 100644
--- a/shell/browser/api/electron_api_web_frame_main.cc
+++ b/shell/browser/api/electron_api_web_frame_main.cc
@@ -641,6 +641,31 @@ v8::Local FromID(gin_helper::ErrorThrower thrower,
return WebFrameMain::From(thrower.isolate(), rfh).ToV8();
}
+v8::Local 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 FromIdIfExists(gin_helper::ErrorThrower thrower,
int render_process_id,
int render_frame_id) {
@@ -677,6 +702,7 @@ void Initialize(v8::Local exports,
gin_helper::Dictionary dict{isolate, exports};
dict.Set("WebFrameMain", WebFrameMain::GetConstructor(isolate, context));
dict.SetMethod("fromId", &FromID);
+ dict.SetMethod("fromFrameToken", &FromFrameToken);
dict.SetMethod("_fromIdIfExists", &FromIdIfExists);
dict.SetMethod("_fromFtnIdIfExists", &FromFtnIdIfExists);
}
diff --git a/spec/api-web-frame-main-spec.ts b/spec/api-web-frame-main-spec.ts
index df9c2fe87f96..ccb109beec28 100644
--- a/spec/api-web-frame-main-spec.ts
+++ b/spec/api-web-frame-main-spec.ts
@@ -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', () => {
let server: Server;
before(async () => {
diff --git a/typings/internal-ambient.d.ts b/typings/internal-ambient.d.ts
index 7d612cf3769d..40ba2511682d 100644
--- a/typings/internal-ambient.d.ts
+++ b/typings/internal-ambient.d.ts
@@ -133,7 +133,8 @@ declare namespace NodeJS {
interface WebFrameMainBinding {
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;
_fromFtnIdIfExists(frameTreeNodeId: number): Electron.WebFrameMain | null;
}
@@ -153,6 +154,7 @@ declare namespace NodeJS {
interface WebFrameBinding {
mainFrame: InternalWebFrame;
+ WebFrame: Electron.WebFrame;
}
type DataPipe = {