From b9c9e7fc06772a45ad003fcbe422c114ca65ea5a Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Thu, 10 Dec 2020 13:03:00 -0800 Subject: [PATCH] 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 --- docs/api/context-bridge.md | 1 + .../api/electron_api_context_bridge.cc | 9 +++++ spec-main/api-context-bridge-spec.ts | 35 +++++++++++++++++-- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/docs/api/context-bridge.md b/docs/api/context-bridge.md index 9f25516af58e1..ab157f79f64f4 100644 --- a/docs/api/context-bridge.md +++ b/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. diff --git a/shell/renderer/api/electron_api_context_bridge.cc b/shell/renderer/api/electron_api_context_bridge.cc index 92148de997675..1414cc09f04ae 100644 --- a/shell/renderer/api/electron_api_context_bridge.cc +++ b/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 PassValueToOtherContext( return v8::MaybeLocal(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(elem.ToV8Value( + destination_context->Global(), destination_context->GetIsolate())); + } + // Proxy all objects if (IsPlainObject(value)) { auto object_value = v8::Local::Cast(value); diff --git a/spec-main/api-context-bridge-spec.ts b/spec-main/api-context-bridge-spec.ts index c98f646f5eced..3f73b42e9502b 100644 --- a/spec-main/api-context-bridge-spec.ts +++ b/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)