feat: add a new contextBridge module (#20307)
* feat: add a new contextBridge module * chore: fix docs linting * feat: add support for function arguments being proxied * chore: ensure that contextBridge can only be used when contextIsolation is enabled * docs: getReverseBinding can be null * docs: fix broken links in md file * feat: add support for promises in function parameters * fix: linting failure for explicit constructor * Update atom_api_context_bridge.cc * chore: update docs and API design as per feedback * refactor: remove reverse bindings and handle GC'able functions across the bridge * chore: only expose debugGC in testing builds * fix: do not proxy promises as objects * spec: add complete spec coverage for contextBridge * spec: add tests for null/undefined and the anti-overwrite logic * chore: fix linting * spec: add complex nested back-and-forth function calling * fix: expose contextBridge in sandboxed renderers * refactor: improve security of default_app using the new contextBridge module * s/bindAPIInMainWorld/exposeInMainWorld * chore: sorry for this commit, its a big one, I fixed like everything and refactored a lot * chore: remove PassedValueCache as it is unused now Values transferred from context A to context B are now cachde in the RenderFramePersistenceStore * chore: move to anonymous namespace * refactor: remove PassValueToOtherContextWithCache * chore: remove commented unused code blocks * chore: remove .only * chore: remote commented code * refactor: extract RenderFramePersistenceStore * spec: ensure it works with numbered keys * fix: handle number keys correctly * fix: sort out the linter * spec: update default_app asar spec for removed file * refactor: change signatures to return v8 objects directly rather than the mate dictionary handle * refactor: use the v8 serializer to support cloneable buffers and other object types * chore: fix linting * fix: handle hash collisions with a linked list in the map * fix: enforce a recursion limit on the context bridge * chore: fix linting * chore: remove TODO * chore: adapt for PR feedback * chore: remove .only * chore: clean up docs and clean up the proxy map when objects are released * chore: ensure we cache object values that are cloned through the V8 serializer
This commit is contained in:
parent
8099e6137d
commit
0090616f7b
21 changed files with 1680 additions and 38 deletions
|
@ -0,0 +1,145 @@
|
|||
// Copyright (c) 2019 Slack Technologies, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/renderer/api/context_bridge/render_frame_context_bridge_store.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "shell/common/api/remote/object_life_monitor.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace api {
|
||||
|
||||
namespace context_bridge {
|
||||
|
||||
namespace {
|
||||
|
||||
class CachedProxyLifeMonitor final : public ObjectLifeMonitor {
|
||||
public:
|
||||
static void BindTo(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> target,
|
||||
RenderFramePersistenceStore* store,
|
||||
WeakGlobalPairNode* node,
|
||||
int hash) {
|
||||
new CachedProxyLifeMonitor(isolate, target, store, node, hash);
|
||||
}
|
||||
|
||||
protected:
|
||||
CachedProxyLifeMonitor(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> target,
|
||||
RenderFramePersistenceStore* store,
|
||||
WeakGlobalPairNode* node,
|
||||
int hash)
|
||||
: ObjectLifeMonitor(isolate, target),
|
||||
store_(store),
|
||||
node_(node),
|
||||
hash_(hash) {}
|
||||
|
||||
void RunDestructor() override {
|
||||
if (node_->detached) {
|
||||
delete node_;
|
||||
}
|
||||
if (node_->prev) {
|
||||
node_->prev->next = node_->next;
|
||||
}
|
||||
if (node_->next) {
|
||||
node_->next->prev = node_->prev;
|
||||
}
|
||||
if (!node_->prev && !node_->next) {
|
||||
// Must be a single length linked list
|
||||
store_->proxy_map()->erase(hash_);
|
||||
}
|
||||
node_->detached = true;
|
||||
}
|
||||
|
||||
private:
|
||||
RenderFramePersistenceStore* store_;
|
||||
WeakGlobalPairNode* node_;
|
||||
int hash_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
WeakGlobalPairNode::WeakGlobalPairNode(WeakGlobalPair pair) {
|
||||
this->pair = std::move(pair);
|
||||
}
|
||||
|
||||
WeakGlobalPairNode::~WeakGlobalPairNode() {
|
||||
if (next) {
|
||||
delete next;
|
||||
}
|
||||
}
|
||||
|
||||
RenderFramePersistenceStore::RenderFramePersistenceStore(
|
||||
content::RenderFrame* render_frame)
|
||||
: content::RenderFrameObserver(render_frame) {}
|
||||
|
||||
RenderFramePersistenceStore::~RenderFramePersistenceStore() = default;
|
||||
|
||||
void RenderFramePersistenceStore::OnDestruct() {
|
||||
delete this;
|
||||
}
|
||||
|
||||
void RenderFramePersistenceStore::CacheProxiedObject(
|
||||
v8::Local<v8::Value> from,
|
||||
v8::Local<v8::Value> proxy_value) {
|
||||
if (from->IsObject() && !from->IsNullOrUndefined()) {
|
||||
auto obj = v8::Local<v8::Object>::Cast(from);
|
||||
int hash = obj->GetIdentityHash();
|
||||
auto global_from = v8::Global<v8::Value>(v8::Isolate::GetCurrent(), from);
|
||||
auto global_proxy =
|
||||
v8::Global<v8::Value>(v8::Isolate::GetCurrent(), proxy_value);
|
||||
// Do not retain
|
||||
global_from.SetWeak();
|
||||
global_proxy.SetWeak();
|
||||
auto iter = proxy_map_.find(hash);
|
||||
auto* node = new WeakGlobalPairNode(
|
||||
std::make_tuple(std::move(global_from), std::move(global_proxy)));
|
||||
CachedProxyLifeMonitor::BindTo(v8::Isolate::GetCurrent(), obj, this, node,
|
||||
hash);
|
||||
CachedProxyLifeMonitor::BindTo(v8::Isolate::GetCurrent(),
|
||||
v8::Local<v8::Object>::Cast(proxy_value),
|
||||
this, node, hash);
|
||||
if (iter == proxy_map_.end()) {
|
||||
proxy_map_.emplace(hash, node);
|
||||
} else {
|
||||
WeakGlobalPairNode* target = iter->second;
|
||||
while (target->next) {
|
||||
target = target->next;
|
||||
}
|
||||
target->next = node;
|
||||
node->prev = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v8::MaybeLocal<v8::Value> RenderFramePersistenceStore::GetCachedProxiedObject(
|
||||
v8::Local<v8::Value> from) {
|
||||
if (!from->IsObject() || from->IsNullOrUndefined())
|
||||
return v8::MaybeLocal<v8::Value>();
|
||||
|
||||
auto obj = v8::Local<v8::Object>::Cast(from);
|
||||
int hash = obj->GetIdentityHash();
|
||||
auto iter = proxy_map_.find(hash);
|
||||
if (iter == proxy_map_.end())
|
||||
return v8::MaybeLocal<v8::Value>();
|
||||
WeakGlobalPairNode* target = iter->second;
|
||||
while (target) {
|
||||
auto from_cmp = std::get<0>(target->pair).Get(v8::Isolate::GetCurrent());
|
||||
if (from_cmp == from) {
|
||||
if (std::get<1>(target->pair).IsEmpty())
|
||||
return v8::MaybeLocal<v8::Value>();
|
||||
return std::get<1>(target->pair).Get(v8::Isolate::GetCurrent());
|
||||
}
|
||||
target = target->next;
|
||||
}
|
||||
return v8::MaybeLocal<v8::Value>();
|
||||
}
|
||||
|
||||
} // namespace context_bridge
|
||||
|
||||
} // namespace api
|
||||
|
||||
} // namespace electron
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright (c) 2019 Slack Technologies, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef SHELL_RENDERER_API_CONTEXT_BRIDGE_RENDER_FRAME_CONTEXT_BRIDGE_STORE_H_
|
||||
#define SHELL_RENDERER_API_CONTEXT_BRIDGE_RENDER_FRAME_CONTEXT_BRIDGE_STORE_H_
|
||||
|
||||
#include <map>
|
||||
#include <tuple>
|
||||
|
||||
#include "content/public/renderer/render_frame.h"
|
||||
#include "content/public/renderer/render_frame_observer.h"
|
||||
#include "shell/renderer/atom_render_frame_observer.h"
|
||||
#include "third_party/blink/public/web/web_local_frame.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace api {
|
||||
|
||||
namespace context_bridge {
|
||||
|
||||
using FunctionContextPair =
|
||||
std::tuple<v8::Global<v8::Function>, v8::Global<v8::Context>>;
|
||||
|
||||
using WeakGlobalPair = std::tuple<v8::Global<v8::Value>, v8::Global<v8::Value>>;
|
||||
|
||||
struct WeakGlobalPairNode {
|
||||
explicit WeakGlobalPairNode(WeakGlobalPair pair_);
|
||||
~WeakGlobalPairNode();
|
||||
WeakGlobalPair pair;
|
||||
bool detached = false;
|
||||
struct WeakGlobalPairNode* prev = nullptr;
|
||||
struct WeakGlobalPairNode* next = nullptr;
|
||||
};
|
||||
|
||||
class RenderFramePersistenceStore final : public content::RenderFrameObserver {
|
||||
public:
|
||||
explicit RenderFramePersistenceStore(content::RenderFrame* render_frame);
|
||||
~RenderFramePersistenceStore() override;
|
||||
|
||||
// RenderFrameObserver implementation.
|
||||
void OnDestruct() override;
|
||||
|
||||
size_t take_func_id() { return next_func_id_++; }
|
||||
|
||||
std::map<size_t, FunctionContextPair>& functions() { return functions_; }
|
||||
std::map<int, WeakGlobalPairNode*>* proxy_map() { return &proxy_map_; }
|
||||
|
||||
void CacheProxiedObject(v8::Local<v8::Value> from,
|
||||
v8::Local<v8::Value> proxy_value);
|
||||
v8::MaybeLocal<v8::Value> GetCachedProxiedObject(v8::Local<v8::Value> from);
|
||||
|
||||
private:
|
||||
// func_id ==> { function, owning_context }
|
||||
std::map<size_t, FunctionContextPair> functions_;
|
||||
size_t next_func_id_ = 1;
|
||||
|
||||
// proxy maps are weak globals, i.e. these are not retained beyond
|
||||
// there normal JS lifetime. You must check IsEmpty()
|
||||
|
||||
// object_identity ==> [from_value, proxy_value]
|
||||
std::map<int, WeakGlobalPairNode*> proxy_map_;
|
||||
};
|
||||
|
||||
} // namespace context_bridge
|
||||
|
||||
} // namespace api
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // SHELL_RENDERER_API_CONTEXT_BRIDGE_RENDER_FRAME_CONTEXT_BRIDGE_STORE_H_
|
Loading…
Add table
Add a link
Reference in a new issue