Browse Source

feat: add webFrameMain.executeJavaScriptInIsolatedWorld() (#26913)

Milan Burda 4 years ago
parent
commit
3d59aa5609

+ 11 - 0
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<unknown>` - 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.

+ 45 - 0
shell/browser/api/electron_api_web_frame_main.cc

@@ -108,6 +108,49 @@ v8::Local<v8::Promise> WebFrameMain::ExecuteJavaScript(
   return handle;
 }
 
+v8::Local<v8::Promise> WebFrameMain::ExecuteJavaScriptInIsolatedWorld(
+    gin::Arguments* args,
+    int world_id,
+    const base::string16& code) {
+  gin_helper::Promise<base::Value> promise(args->isolate());
+  v8::Local<v8::Promise> 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<base::Value> 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<WebFrameMain>::GetObjectTemplateBuilder(isolate)
       .SetMethod("executeJavaScript", &WebFrameMain::ExecuteJavaScript)
+      .SetMethod("executeJavaScriptInIsolatedWorld",
+                 &WebFrameMain::ExecuteJavaScriptInIsolatedWorld)
       .SetMethod("reload", &WebFrameMain::Reload)
       .SetProperty("frameTreeNodeId", &WebFrameMain::FrameTreeNodeID)
       .SetProperty("name", &WebFrameMain::Name)

+ 4 - 0
shell/browser/api/electron_api_web_frame_main.h

@@ -66,6 +66,10 @@ class WebFrameMain : public gin::Wrappable<WebFrameMain> {
 
   v8::Local<v8::Promise> ExecuteJavaScript(gin::Arguments* args,
                                            const base::string16& code);
+  v8::Local<v8::Promise> ExecuteJavaScriptInIsolatedWorld(
+      gin::Arguments* args,
+      int world_id,
+      const base::string16& code);
   bool Reload(v8::Isolate* isolate);
 
   int FrameTreeNodeID(v8::Isolate* isolate) const;

+ 13 - 0
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 } });