Browse Source

feat: add support for DOM elements going over the context bridge (#26776)

* feat: add support for DOM elements going over the context bridge

* Update context-bridge.md
Samuel Attard 4 years ago
parent
commit
b9c9e7fc06

+ 1 - 0
docs/api/context-bridge.md

@@ -106,6 +106,7 @@ has been included below for completeness:
 | `Promise` | Complex | ✅ | ✅ | Promises are only proxied if they are the return value or exact parameter.  Promises nested in arrays or objects will be dropped. |
 | `Function` | Complex | ✅ | ✅ | Prototype modifications are dropped.  Sending classes or constructors will not work. |
 | [Cloneable Types](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) | Simple | ✅ | ✅ | See the linked document on cloneable types |
+| `Element` | Complex | ✅ | ✅ | Prototype modifications are dropped.  Sending custom elements will not work. |
 | `Symbol` | N/A | ❌ | ❌ | Symbols cannot be copied across contexts so they are dropped |
 
 If the type you care about is not in the above table, it is probably not supported.

+ 9 - 0
shell/renderer/api/electron_api_context_bridge.cc

@@ -22,6 +22,7 @@
 #include "shell/common/gin_helper/promise.h"
 #include "shell/common/node_includes.h"
 #include "shell/common/world_ids.h"
+#include "third_party/blink/public/web/web_element.h"
 #include "third_party/blink/public/web/web_local_frame.h"
 
 namespace electron {
@@ -319,6 +320,14 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
     return v8::MaybeLocal<v8::Value>(cloned_arr);
   }
 
+  // Custom logic to "clone" Element references
+  blink::WebElement elem = blink::WebElement::FromV8Value(value);
+  if (!elem.IsNull()) {
+    v8::Context::Scope destination_context_scope(destination_context);
+    return v8::MaybeLocal<v8::Value>(elem.ToV8Value(
+        destination_context->Global(), destination_context->GetIsolate()));
+  }
+
   // Proxy all objects
   if (IsPlainObject(value)) {
     auto object_value = v8::Local<v8::Object>::Cast(value);

+ 32 - 3
spec-main/api-context-bridge-spec.ts

@@ -517,7 +517,7 @@ describe('contextBridge', () => {
         expect(result).to.deep.equal([true, true]);
       });
 
-      it('it should handle recursive objects', async () => {
+      it('should handle recursive objects', async () => {
         await makeBindingWindow(() => {
           const o: any = { value: 135 };
           o.o = o;
@@ -531,6 +531,33 @@ describe('contextBridge', () => {
         expect(result).to.deep.equal([135, 135, 135]);
       });
 
+      it('should handle DOM elements', async () => {
+        await makeBindingWindow(() => {
+          contextBridge.exposeInMainWorld('example', {
+            getElem: () => document.body
+          });
+        });
+        const result = await callWithBindings((root: any) => {
+          return [root.example.getElem().tagName, root.example.getElem().constructor.name, typeof root.example.getElem().querySelector];
+        });
+        expect(result).to.deep.equal(['BODY', 'HTMLBodyElement', 'function']);
+      });
+
+      it('should handle DOM elements going backwards over the bridge', async () => {
+        await makeBindingWindow(() => {
+          contextBridge.exposeInMainWorld('example', {
+            getElemInfo: (fn: Function) => {
+              const elem = fn();
+              return [elem.tagName, elem.constructor.name, typeof elem.querySelector];
+            }
+          });
+        });
+        const result = await callWithBindings((root: any) => {
+          return root.example.getElemInfo(() => document.body);
+        });
+        expect(result).to.deep.equal(['BODY', 'HTMLBodyElement', 'function']);
+      });
+
       // Can only run tests which use the GCRunner in non-sandboxed environments
       if (!useSandbox) {
         it('should release the global hold on methods sent across contexts', async () => {
@@ -735,7 +762,8 @@ describe('contextBridge', () => {
             receiveArguments: (fn: any) => fn({ key: 'value' }),
             symbolKeyed: {
               [Symbol('foo')]: 123
-            }
+            },
+            getBody: () => document.body
           });
         });
         const result = await callWithBindings(async (root: any) => {
@@ -807,7 +835,8 @@ describe('contextBridge', () => {
             [(await example.object.getPromise()).arr[3], Array],
             [(await example.object.getPromise()).arr[3][0], String],
             [arg, Object],
-            [arg.key, String]
+            [arg.key, String],
+            [example.getBody(), HTMLBodyElement]
           ];
           return {
             protoMatches: protoChecks.map(([a, Constructor]) => Object.getPrototypeOf(a) === Constructor.prototype)