From 3d59aa56098ed4ba9e2d1d56694f5b3c8307b0c2 Mon Sep 17 00:00:00 2001 From: Milan Burda Date: Tue, 5 Jan 2021 09:18:38 +0100 Subject: [PATCH] feat: add webFrameMain.executeJavaScriptInIsolatedWorld() (#26913) --- docs/api/web-frame-main.md | 11 +++++ .../api/electron_api_web_frame_main.cc | 45 +++++++++++++++++++ .../browser/api/electron_api_web_frame_main.h | 4 ++ spec-main/api-web-frame-main-spec.ts | 13 ++++++ 4 files changed, 73 insertions(+) diff --git a/docs/api/web-frame-main.md b/docs/api/web-frame-main.md index f3f92c025971..703dd2e127c7 100644 --- a/docs/api/web-frame-main.md +++ b/docs/api/web-frame-main.md @@ -86,6 +86,17 @@ In the browser window some HTML APIs like `requestFullScreen` can only be invoked by a gesture from the user. Setting `userGesture` to `true` will remove this limitation. +#### `frame.executeJavaScriptInIsolatedWorld(worldId, code[, userGesture])` + +* `worldId` Integer - The ID of the world to run the javascript in, `0` is the default world, `999` is the world used by Electron's `contextIsolation` feature. You can provide any integer here. +* `code` String +* `userGesture` Boolean (optional) - Default is `false`. + +Returns `Promise` - A promise that resolves with the result of the executed +code or is rejected if execution throws or results in a rejected promise. + +Works like `executeJavaScript` but evaluates `scripts` in an isolated context. + #### `frame.reload()` Returns `boolean` - Whether the reload was initiated successfully. Only results in `false` when the frame has no history. diff --git a/shell/browser/api/electron_api_web_frame_main.cc b/shell/browser/api/electron_api_web_frame_main.cc index 6a25aab8c77f..514cc5a2eeaa 100644 --- a/shell/browser/api/electron_api_web_frame_main.cc +++ b/shell/browser/api/electron_api_web_frame_main.cc @@ -108,6 +108,49 @@ v8::Local WebFrameMain::ExecuteJavaScript( return handle; } +v8::Local WebFrameMain::ExecuteJavaScriptInIsolatedWorld( + gin::Arguments* args, + int world_id, + const base::string16& code) { + gin_helper::Promise promise(args->isolate()); + v8::Local handle = promise.GetHandle(); + + // Optional userGesture parameter + bool user_gesture; + if (!args->PeekNext().IsEmpty()) { + if (args->PeekNext()->IsBoolean()) { + args->GetNext(&user_gesture); + } else { + args->ThrowTypeError("userGesture must be a boolean"); + return handle; + } + } else { + user_gesture = false; + } + + if (render_frame_disposed_) { + promise.RejectWithErrorMessage( + "Render frame was disposed before WebFrameMain could be accessed"); + return handle; + } + + if (user_gesture) { + auto* ftn = content::FrameTreeNode::From(render_frame_); + ftn->UpdateUserActivationState( + blink::mojom::UserActivationUpdateType::kNotifyActivation, + blink::mojom::UserActivationNotificationType::kTest); + } + + render_frame_->ExecuteJavaScriptForTests( + code, + base::BindOnce([](gin_helper::Promise promise, + base::Value value) { promise.Resolve(value); }, + std::move(promise)), + world_id); + + return handle; +} + bool WebFrameMain::Reload(v8::Isolate* isolate) { if (!CheckRenderFrame()) return false; @@ -222,6 +265,8 @@ gin::ObjectTemplateBuilder WebFrameMain::GetObjectTemplateBuilder( v8::Isolate* isolate) { return gin::Wrappable::GetObjectTemplateBuilder(isolate) .SetMethod("executeJavaScript", &WebFrameMain::ExecuteJavaScript) + .SetMethod("executeJavaScriptInIsolatedWorld", + &WebFrameMain::ExecuteJavaScriptInIsolatedWorld) .SetMethod("reload", &WebFrameMain::Reload) .SetProperty("frameTreeNodeId", &WebFrameMain::FrameTreeNodeID) .SetProperty("name", &WebFrameMain::Name) diff --git a/shell/browser/api/electron_api_web_frame_main.h b/shell/browser/api/electron_api_web_frame_main.h index 5c5801277cca..9d381e23f459 100644 --- a/shell/browser/api/electron_api_web_frame_main.h +++ b/shell/browser/api/electron_api_web_frame_main.h @@ -66,6 +66,10 @@ class WebFrameMain : public gin::Wrappable { v8::Local ExecuteJavaScript(gin::Arguments* args, const base::string16& code); + v8::Local ExecuteJavaScriptInIsolatedWorld( + gin::Arguments* args, + int world_id, + const base::string16& code); bool Reload(v8::Isolate* isolate); int FrameTreeNodeID(v8::Isolate* isolate) const; diff --git a/spec-main/api-web-frame-main-spec.ts b/spec-main/api-web-frame-main-spec.ts index 66fc54d6288d..2739c80f3ac5 100644 --- a/spec-main/api-web-frame-main-spec.ts +++ b/spec-main/api-web-frame-main-spec.ts @@ -147,6 +147,19 @@ describe('webFrameMain module', () => { }); }); + describe('WebFrame.executeJavaScriptInIsolatedWorld', () => { + it('can inject code into any subframe', async () => { + const w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } }); + await w.loadFile(path.join(subframesPath, 'frame-with-frame-container.html')); + const webFrame = w.webContents.mainFrame; + + const getUrl = (frame: WebFrameMain) => frame.executeJavaScriptInIsolatedWorld(999, 'location.href'); + expect(await getUrl(webFrame)).to.equal(fileUrl('frame-with-frame-container.html')); + expect(await getUrl(webFrame.frames[0])).to.equal(fileUrl('frame-with-frame.html')); + expect(await getUrl(webFrame.frames[0].frames[0])).to.equal(fileUrl('frame.html')); + }); + }); + describe('WebFrame.reload', () => { it('reloads a frame', async () => { const w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } });