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
This commit is contained in:
Samuel Attard 2020-12-10 13:03:00 -08:00 committed by GitHub
parent 51db2a6b34
commit b9c9e7fc06
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 42 additions and 3 deletions

View file

@ -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. | | `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. | | `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 | | [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 | | `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. If the type you care about is not in the above table, it is probably not supported.

View file

@ -22,6 +22,7 @@
#include "shell/common/gin_helper/promise.h" #include "shell/common/gin_helper/promise.h"
#include "shell/common/node_includes.h" #include "shell/common/node_includes.h"
#include "shell/common/world_ids.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" #include "third_party/blink/public/web/web_local_frame.h"
namespace electron { namespace electron {
@ -319,6 +320,14 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
return v8::MaybeLocal<v8::Value>(cloned_arr); 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 // Proxy all objects
if (IsPlainObject(value)) { if (IsPlainObject(value)) {
auto object_value = v8::Local<v8::Object>::Cast(value); auto object_value = v8::Local<v8::Object>::Cast(value);

View file

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