mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Implement Runtime.addBinding (global bindings only) (#43065)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/43065 Changelog: [Internal] Implements support for [`Runtime.addBinding`](https://cdpstatus.reactnative.dev/devtools-protocol/tot/Runtime#method-addBinding) in the new RN CDP backend. This implementation is mostly complete and matches Chrome's behaviour, but does not include the ability to target bindings by execution context (the optional `executionContextId` and `executionContextName` params) - that will come in a separate diff for ease of review/landing. Incidentally, this diff also introduces the `JsiIntegrationPortableTest::expectMessageFromPage` helper, which allows us to "asynchronously" extract the contents of an expected message. For consistency and clarity, we refactor all the other `EXPECT_CALL(this->fromPage(), onMessage(JsonEq(...)))` assertions to use it as well. Reviewed By: huntie Differential Revision: D53266709 fbshipit-source-id: 046326acdf5dacc18e179e43589cdd2d012f353a
This commit is contained in:
committed by
Facebook GitHub Bot
parent
828ad04cef
commit
da03d7b829
@@ -52,4 +52,5 @@ Pod::Spec.new do |s|
|
||||
s.dependency "React-featureflags"
|
||||
s.dependency "DoubleConversion"
|
||||
s.dependency "React-runtimeexecutor", version
|
||||
s.dependency "React-jsi"
|
||||
end
|
||||
|
||||
@@ -11,24 +11,65 @@ namespace facebook::react::jsinspector_modern {
|
||||
|
||||
RuntimeAgent::RuntimeAgent(
|
||||
FrontendChannel frontendChannel,
|
||||
RuntimeTarget& target,
|
||||
RuntimeTargetController& targetController,
|
||||
const ExecutionContextDescription& executionContextDescription,
|
||||
SessionState& sessionState,
|
||||
std::unique_ptr<RuntimeAgentDelegate> delegate)
|
||||
: frontendChannel_(std::move(frontendChannel)),
|
||||
target_(target),
|
||||
targetController_(targetController),
|
||||
sessionState_(sessionState),
|
||||
delegate_(std::move(delegate)),
|
||||
executionContextDescription_(executionContextDescription) {
|
||||
(void)target_;
|
||||
(void)sessionState_;
|
||||
for (auto& name : sessionState_.subscribedBindingNames) {
|
||||
targetController_.installBindingHandler(name);
|
||||
}
|
||||
}
|
||||
|
||||
bool RuntimeAgent::handleRequest(const cdp::PreparsedRequest& req) {
|
||||
if (req.method == "Runtime.addBinding") {
|
||||
std::string bindingName = req.params["name"].getString();
|
||||
// TODO: Respect @cdp Runtime.addBinding's executionContextId and
|
||||
// executionContextName params.
|
||||
sessionState_.subscribedBindingNames.emplace(bindingName);
|
||||
|
||||
targetController_.installBindingHandler(bindingName);
|
||||
|
||||
folly::dynamic res = folly::dynamic::object("id", req.id)(
|
||||
"result", folly::dynamic::object());
|
||||
std::string json = folly::toJson(res);
|
||||
frontendChannel_(json);
|
||||
|
||||
return true;
|
||||
}
|
||||
if (req.method == "Runtime.removeBinding") {
|
||||
sessionState_.subscribedBindingNames.erase(req.params["name"].getString());
|
||||
|
||||
folly::dynamic res = folly::dynamic::object("id", req.id)(
|
||||
"result", folly::dynamic::object());
|
||||
std::string json = folly::toJson(res);
|
||||
frontendChannel_(json);
|
||||
|
||||
return true;
|
||||
}
|
||||
if (delegate_) {
|
||||
return delegate_->handleRequest(req);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void RuntimeAgent::notifyBindingCalled(
|
||||
const std::string& bindingName,
|
||||
const std::string& payload) {
|
||||
if (!sessionState_.subscribedBindingNames.count(bindingName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
frontendChannel_(
|
||||
folly::toJson(folly::dynamic::object("method", "Runtime.bindingCalled")(
|
||||
"params",
|
||||
folly::dynamic::object(
|
||||
"executionContextId", executionContextDescription_.id)(
|
||||
"name", bindingName)("payload", payload))));
|
||||
}
|
||||
|
||||
} // namespace facebook::react::jsinspector_modern
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
namespace facebook::react::jsinspector_modern {
|
||||
|
||||
class RuntimeTarget;
|
||||
class RuntimeTargetController;
|
||||
|
||||
/**
|
||||
* An Agent that handles requests from the Chrome DevTools Protocol
|
||||
@@ -30,9 +30,9 @@ class RuntimeAgent final {
|
||||
/**
|
||||
* \param frontendChannel A channel used to send responses and events to the
|
||||
* frontend.
|
||||
* \param target The RuntimeTarget that this agent is attached to. The
|
||||
* caller is responsible for ensuring that the RuntimeTarget outlives this
|
||||
* object.
|
||||
* \param targetController An interface to the RuntimeTarget that this agent
|
||||
* is attached to. The caller is responsible for ensuring that the
|
||||
* RuntimeTarget and controller outlive this object.
|
||||
* \param executionContextDescription A description of the execution context
|
||||
* represented by this runtime. This is used for disambiguating the
|
||||
* source/destination of CDP messages when there are multiple runtimes
|
||||
@@ -43,7 +43,7 @@ class RuntimeAgent final {
|
||||
*/
|
||||
RuntimeAgent(
|
||||
FrontendChannel frontendChannel,
|
||||
RuntimeTarget& target,
|
||||
RuntimeTargetController& targetController,
|
||||
const ExecutionContextDescription& executionContextDescription,
|
||||
SessionState& sessionState,
|
||||
std::unique_ptr<RuntimeAgentDelegate> delegate);
|
||||
@@ -65,9 +65,13 @@ class RuntimeAgent final {
|
||||
return executionContextDescription_;
|
||||
}
|
||||
|
||||
void notifyBindingCalled(
|
||||
const std::string& bindingName,
|
||||
const std::string& payload);
|
||||
|
||||
private:
|
||||
FrontendChannel frontendChannel_;
|
||||
RuntimeTarget& target_;
|
||||
RuntimeTargetController& targetController_;
|
||||
SessionState& sessionState_;
|
||||
const std::unique_ptr<RuntimeAgentDelegate> delegate_;
|
||||
const ExecutionContextDescription executionContextDescription_;
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#include <jsinspector-modern/RuntimeTarget.h>
|
||||
|
||||
using namespace facebook::jsi;
|
||||
|
||||
namespace facebook::react::jsinspector_modern {
|
||||
|
||||
std::shared_ptr<RuntimeTarget> RuntimeTarget::create(
|
||||
@@ -33,7 +35,7 @@ std::shared_ptr<RuntimeAgent> RuntimeTarget::createAgent(
|
||||
SessionState& sessionState) {
|
||||
auto runtimeAgent = std::make_shared<RuntimeAgent>(
|
||||
channel,
|
||||
*this,
|
||||
controller_,
|
||||
executionContextDescription_,
|
||||
sessionState,
|
||||
delegate_.createAgentDelegate(
|
||||
@@ -50,4 +52,51 @@ RuntimeTarget::~RuntimeTarget() {
|
||||
"RuntimeAgent objects must be destroyed before their RuntimeTarget. Did you call InstanceTarget::unregisterRuntime()?");
|
||||
}
|
||||
|
||||
void RuntimeTarget::installBindingHandler(const std::string& bindingName) {
|
||||
jsExecutor_([bindingName,
|
||||
selfExecutor = executorFromThis()](jsi::Runtime& runtime) {
|
||||
auto globalObj = runtime.global();
|
||||
try {
|
||||
auto bindingNamePropID = jsi::PropNameID::forUtf8(runtime, bindingName);
|
||||
globalObj.setProperty(
|
||||
runtime,
|
||||
bindingNamePropID,
|
||||
jsi::Function::createFromHostFunction(
|
||||
runtime,
|
||||
bindingNamePropID,
|
||||
1,
|
||||
[bindingName, selfExecutor](
|
||||
jsi::Runtime& rt,
|
||||
const jsi::Value&,
|
||||
const jsi::Value* args,
|
||||
size_t count) -> jsi::Value {
|
||||
if (count != 1 || !args[0].isString()) {
|
||||
throw jsi::JSError(
|
||||
rt, "Invalid arguments: should be exactly one string.");
|
||||
}
|
||||
std::string payload = args[0].getString(rt).utf8(rt);
|
||||
|
||||
selfExecutor([bindingName, payload](auto& self) {
|
||||
self.agents_.forEach([bindingName, payload](auto& agent) {
|
||||
agent.notifyBindingCalled(bindingName, payload);
|
||||
});
|
||||
});
|
||||
|
||||
return jsi::Value::undefined();
|
||||
}));
|
||||
} catch (jsi::JSError&) {
|
||||
// Per Chrome's implementation, @cdp Runtime.createBinding swallows
|
||||
// JavaScript exceptions that occur while setting up the binding.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RuntimeTargetController::RuntimeTargetController(RuntimeTarget& target)
|
||||
: target_(target) {}
|
||||
|
||||
void RuntimeTargetController::installBindingHandler(
|
||||
const std::string& bindingName) {
|
||||
target_.installBindingHandler(bindingName);
|
||||
}
|
||||
|
||||
} // namespace facebook::react::jsinspector_modern
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace facebook::react::jsinspector_modern {
|
||||
|
||||
class RuntimeAgent;
|
||||
class RuntimeAgentDelegate;
|
||||
class RuntimeTarget;
|
||||
|
||||
/**
|
||||
* Receives events from a RuntimeTarget. This is a shared interface that
|
||||
@@ -49,6 +50,24 @@ class RuntimeTargetDelegate {
|
||||
const ExecutionContextDescription& executionContextDescription) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* The limited interface that RuntimeTarget exposes to its connected agents.
|
||||
*/
|
||||
class RuntimeTargetController {
|
||||
public:
|
||||
explicit RuntimeTargetController(RuntimeTarget& target);
|
||||
|
||||
/**
|
||||
* Adds a function with the given name on the runtime's global object, that
|
||||
* when called will send a Runtime.bindingCalled event through all connected
|
||||
* sessions that have registered to receive binding events for that name.
|
||||
*/
|
||||
void installBindingHandler(const std::string& bindingName);
|
||||
|
||||
private:
|
||||
RuntimeTarget& target_;
|
||||
};
|
||||
|
||||
/**
|
||||
* A Target corresponding to a JavaScript runtime.
|
||||
*/
|
||||
@@ -123,6 +142,19 @@ class JSINSPECTOR_EXPORT RuntimeTarget
|
||||
RuntimeTargetDelegate& delegate_;
|
||||
RuntimeExecutor jsExecutor_;
|
||||
WeakList<RuntimeAgent> agents_;
|
||||
RuntimeTargetController controller_{*this};
|
||||
|
||||
/**
|
||||
* Adds a function with the given name on the runtime's global object, that
|
||||
* when called will send a Runtime.bindingCalled event through all connected
|
||||
* sessions that have registered to receive binding events for that name.
|
||||
*/
|
||||
void installBindingHandler(const std::string& bindingName);
|
||||
|
||||
// Necessary to allow RuntimeAgent to access RuntimeTarget's internals in a
|
||||
// controlled way (i.e. only RuntimeTargetController gets friend access, while
|
||||
// RuntimeAgent itself doesn't).
|
||||
friend class RuntimeTargetController;
|
||||
};
|
||||
|
||||
} // namespace facebook::react::jsinspector_modern
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace facebook::react::jsinspector_modern {
|
||||
|
||||
@@ -17,6 +19,15 @@ struct SessionState {
|
||||
bool isLogDomainEnabled{false};
|
||||
bool isRuntimeDomainEnabled{false};
|
||||
|
||||
/**
|
||||
* The set of bindings registered during this session using @cdp
|
||||
* Runtime.addBinding. Even though bindings get added to the global scope as
|
||||
* functions that can outlive a session, they are treated as session state,
|
||||
* matching Chrome's behaviour (a binding not added by the current session
|
||||
* will not emit events on it).
|
||||
*/
|
||||
std::unordered_set<std::string> subscribedBindingNames;
|
||||
|
||||
// Here, we will eventually allow RuntimeAgents to store their own arbitrary
|
||||
// state (e.g. some sort of K/V storage of folly::dynamic?)
|
||||
|
||||
|
||||
+261
-71
@@ -5,7 +5,9 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include <folly/dynamic.h>
|
||||
#include <folly/executors/QueuedImmediateExecutor.h>
|
||||
#include <folly/json.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -109,6 +111,22 @@ class JsiIntegrationPortableTest : public Test, private PageTargetDelegate {
|
||||
std::make_shared<jsi::StringBuffer>(std::string(code)), "<eval>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Expect a message matching the provided gmock \c matcher and return a holder
|
||||
* that will eventually contain the parsed JSON payload.
|
||||
*/
|
||||
template <typename Matcher>
|
||||
std::shared_ptr<const std::optional<folly::dynamic>> expectMessageFromPage(
|
||||
Matcher&& matcher) {
|
||||
std::shared_ptr result =
|
||||
std::make_shared<std::optional<folly::dynamic>>(std::nullopt);
|
||||
EXPECT_CALL(fromPage(), onMessage(matcher))
|
||||
.WillOnce(
|
||||
([result](auto message) { *result = folly::parseJson(message); }))
|
||||
.RetiresOnSaturation();
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<PageTarget> page_ =
|
||||
PageTarget::create(*this, inspectorExecutor_);
|
||||
InstanceTarget* instance_{};
|
||||
@@ -162,11 +180,8 @@ TYPED_TEST(JsiIntegrationPortableTest, ConnectWithoutCrashing) {
|
||||
TYPED_TEST(JsiIntegrationPortableTest, ErrorOnUnknownMethod) {
|
||||
this->connect();
|
||||
|
||||
EXPECT_CALL(
|
||||
this->fromPage(),
|
||||
onMessage(JsonParsed(
|
||||
AllOf(AtJsonPtr("/id", 1), AtJsonPtr("/error/code", -32601)))))
|
||||
.RetiresOnSaturation();
|
||||
this->expectMessageFromPage(
|
||||
JsonParsed(AllOf(AtJsonPtr("/id", 1), AtJsonPtr("/error/code", -32601))));
|
||||
|
||||
this->toPage_->sendMessage(R"({
|
||||
"id": 1,
|
||||
@@ -179,65 +194,58 @@ TYPED_TEST(JsiIntegrationPortableTest, ExecutionContextNotifications) {
|
||||
|
||||
InSequence s;
|
||||
|
||||
EXPECT_CALL(this->fromPage(), onMessage(JsonEq(R"({
|
||||
"method": "Runtime.executionContextCreated",
|
||||
"params": {
|
||||
"context": {
|
||||
"id": 1,
|
||||
"origin": "",
|
||||
"name": "main"
|
||||
}
|
||||
}
|
||||
})")))
|
||||
.RetiresOnSaturation();
|
||||
EXPECT_CALL(this->fromPage(), onMessage(JsonEq(R"({
|
||||
"id": 1,
|
||||
"result": {}
|
||||
})")));
|
||||
this->expectMessageFromPage(JsonEq(R"({
|
||||
"method": "Runtime.executionContextCreated",
|
||||
"params": {
|
||||
"context": {
|
||||
"id": 1,
|
||||
"origin": "",
|
||||
"name": "main"
|
||||
}
|
||||
}
|
||||
})"));
|
||||
this->expectMessageFromPage(JsonEq(R"({
|
||||
"id": 1,
|
||||
"result": {}
|
||||
})"));
|
||||
this->toPage_->sendMessage(R"({
|
||||
"id": 1,
|
||||
"method": "Runtime.enable"
|
||||
})");
|
||||
|
||||
EXPECT_CALL(this->fromPage(), onMessage(JsonEq(R"({
|
||||
"method": "Runtime.executionContextDestroyed",
|
||||
"params": {
|
||||
"executionContextId": 1
|
||||
}
|
||||
})")))
|
||||
.RetiresOnSaturation();
|
||||
EXPECT_CALL(this->fromPage(), onMessage(JsonEq(R"({
|
||||
"method": "Runtime.executionContextsCleared"
|
||||
})")))
|
||||
.RetiresOnSaturation();
|
||||
this->expectMessageFromPage(JsonEq(R"({
|
||||
"method": "Runtime.executionContextDestroyed",
|
||||
"params": {
|
||||
"executionContextId": 1
|
||||
}
|
||||
})"));
|
||||
this->expectMessageFromPage(JsonEq(R"({
|
||||
"method": "Runtime.executionContextsCleared"
|
||||
})"));
|
||||
|
||||
EXPECT_CALL(this->fromPage(), onMessage(JsonEq(R"({
|
||||
"method": "Runtime.executionContextCreated",
|
||||
"params": {
|
||||
"context": {
|
||||
"id": 2,
|
||||
"origin": "",
|
||||
"name": "main"
|
||||
}
|
||||
}
|
||||
})")))
|
||||
.RetiresOnSaturation();
|
||||
this->expectMessageFromPage(JsonEq(R"({
|
||||
"method": "Runtime.executionContextCreated",
|
||||
"params": {
|
||||
"context": {
|
||||
"id": 2,
|
||||
"origin": "",
|
||||
"name": "main"
|
||||
}
|
||||
}
|
||||
})"));
|
||||
// Simulate a reload triggered by the app (not by the debugger).
|
||||
this->reload();
|
||||
|
||||
EXPECT_CALL(this->fromPage(), onMessage(JsonEq(R"({
|
||||
"method": "Runtime.executionContextDestroyed",
|
||||
"params": {
|
||||
"executionContextId": 2
|
||||
}
|
||||
})")))
|
||||
.RetiresOnSaturation();
|
||||
|
||||
EXPECT_CALL(this->fromPage(), onMessage(JsonEq(R"({
|
||||
"method": "Runtime.executionContextsCleared"
|
||||
})")))
|
||||
.RetiresOnSaturation();
|
||||
EXPECT_CALL(this->fromPage(), onMessage(JsonEq(R"({
|
||||
this->expectMessageFromPage(JsonEq(R"({
|
||||
"method": "Runtime.executionContextDestroyed",
|
||||
"params": {
|
||||
"executionContextId": 2
|
||||
}
|
||||
})"));
|
||||
this->expectMessageFromPage(JsonEq(R"({
|
||||
"method": "Runtime.executionContextsCleared"
|
||||
})"));
|
||||
this->expectMessageFromPage(JsonEq(R"({
|
||||
"method": "Runtime.executionContextCreated",
|
||||
"params": {
|
||||
"context": {
|
||||
@@ -246,33 +254,215 @@ TYPED_TEST(JsiIntegrationPortableTest, ExecutionContextNotifications) {
|
||||
"name": "main"
|
||||
}
|
||||
}
|
||||
})")))
|
||||
.RetiresOnSaturation();
|
||||
EXPECT_CALL(this->fromPage(), onMessage(JsonEq(R"({
|
||||
"id": 2,
|
||||
"result": {}
|
||||
})")))
|
||||
.RetiresOnSaturation();
|
||||
})"));
|
||||
this->expectMessageFromPage(JsonEq(R"({
|
||||
"id": 2,
|
||||
"result": {}
|
||||
})"));
|
||||
this->toPage_->sendMessage(R"({
|
||||
"id": 2,
|
||||
"method": "Page.reload"
|
||||
})");
|
||||
}
|
||||
|
||||
TYPED_TEST(JsiIntegrationPortableTest, AddBinding) {
|
||||
this->connect();
|
||||
|
||||
InSequence s;
|
||||
|
||||
auto executionContextInfo = this->expectMessageFromPage(JsonParsed(
|
||||
AllOf(AtJsonPtr("/method", "Runtime.executionContextCreated"))));
|
||||
this->expectMessageFromPage(JsonEq(R"({
|
||||
"id": 1,
|
||||
"result": {}
|
||||
})"));
|
||||
this->toPage_->sendMessage(R"({
|
||||
"id": 1,
|
||||
"method": "Runtime.enable"
|
||||
})");
|
||||
auto executionContextId =
|
||||
executionContextInfo->value()["params"]["context"]["id"];
|
||||
|
||||
this->expectMessageFromPage(JsonEq(R"({
|
||||
"id": 2,
|
||||
"result": {}
|
||||
})"));
|
||||
this->toPage_->sendMessage(R"({
|
||||
"id": 2,
|
||||
"method": "Runtime.addBinding",
|
||||
"params": {"name": "foo"}
|
||||
})");
|
||||
|
||||
this->expectMessageFromPage(JsonParsed(AllOf(
|
||||
AtJsonPtr("/method", "Runtime.bindingCalled"),
|
||||
AtJsonPtr("/params/name", "foo"),
|
||||
AtJsonPtr("/params/payload", "bar"),
|
||||
AtJsonPtr("/params/executionContextId", executionContextId))));
|
||||
this->eval("globalThis.foo('bar');");
|
||||
}
|
||||
|
||||
TYPED_TEST(JsiIntegrationPortableTest, AddedBindingSurvivesReload) {
|
||||
this->connect();
|
||||
|
||||
InSequence s;
|
||||
|
||||
this->expectMessageFromPage(JsonEq(R"({
|
||||
"id": 1,
|
||||
"result": {}
|
||||
})"));
|
||||
this->toPage_->sendMessage(R"({
|
||||
"id": 1,
|
||||
"method": "Runtime.addBinding",
|
||||
"params": {"name": "foo"}
|
||||
})");
|
||||
|
||||
this->reload();
|
||||
|
||||
// Get the new context ID by sending Runtime.enable now.
|
||||
auto executionContextInfo = this->expectMessageFromPage(JsonParsed(
|
||||
AllOf(AtJsonPtr("/method", "Runtime.executionContextCreated"))));
|
||||
this->expectMessageFromPage(JsonEq(R"({
|
||||
"id": 1,
|
||||
"result": {}
|
||||
})"));
|
||||
this->toPage_->sendMessage(R"({
|
||||
"id": 1,
|
||||
"method": "Runtime.enable"
|
||||
})");
|
||||
auto executionContextId =
|
||||
executionContextInfo->value()["params"]["context"]["id"];
|
||||
|
||||
this->expectMessageFromPage(JsonParsed(AllOf(
|
||||
AtJsonPtr("/method", "Runtime.bindingCalled"),
|
||||
AtJsonPtr("/params/name", "foo"),
|
||||
AtJsonPtr("/params/payload", "bar"),
|
||||
AtJsonPtr("/params/executionContextId", executionContextId))));
|
||||
this->eval("globalThis.foo('bar');");
|
||||
}
|
||||
|
||||
TYPED_TEST(JsiIntegrationPortableTest, RemovedBindingRemainsInstalled) {
|
||||
this->connect();
|
||||
|
||||
InSequence s;
|
||||
|
||||
this->expectMessageFromPage(JsonEq(R"({
|
||||
"id": 1,
|
||||
"result": {}
|
||||
})"));
|
||||
this->toPage_->sendMessage(R"({
|
||||
"id": 1,
|
||||
"method": "Runtime.addBinding",
|
||||
"params": {"name": "foo"}
|
||||
})");
|
||||
|
||||
this->expectMessageFromPage(JsonEq(R"({
|
||||
"id": 2,
|
||||
"result": {}
|
||||
})"));
|
||||
this->toPage_->sendMessage(R"({
|
||||
"id": 2,
|
||||
"method": "Runtime.removeBinding",
|
||||
"params": {"name": "foo"}
|
||||
})");
|
||||
|
||||
this->eval("globalThis.foo('bar');");
|
||||
}
|
||||
|
||||
TYPED_TEST(JsiIntegrationPortableTest, RemovedBindingDoesNotSurviveReload) {
|
||||
this->connect();
|
||||
|
||||
InSequence s;
|
||||
|
||||
this->expectMessageFromPage(JsonEq(R"({
|
||||
"id": 1,
|
||||
"result": {}
|
||||
})"));
|
||||
this->toPage_->sendMessage(R"({
|
||||
"id": 1,
|
||||
"method": "Runtime.addBinding",
|
||||
"params": {"name": "foo"}
|
||||
})");
|
||||
|
||||
this->expectMessageFromPage(JsonEq(R"({
|
||||
"id": 2,
|
||||
"result": {}
|
||||
})"));
|
||||
this->toPage_->sendMessage(R"({
|
||||
"id": 2,
|
||||
"method": "Runtime.removeBinding",
|
||||
"params": {"name": "foo"}
|
||||
})");
|
||||
|
||||
this->reload();
|
||||
|
||||
EXPECT_TRUE(this->eval("typeof globalThis.foo === 'undefined'").getBool());
|
||||
}
|
||||
|
||||
TYPED_TEST(JsiIntegrationPortableTest, AddBindingClobbersExistingProperty) {
|
||||
this->connect();
|
||||
|
||||
InSequence s;
|
||||
|
||||
this->eval(R"(
|
||||
globalThis.foo = 'clobbered value';
|
||||
)");
|
||||
|
||||
this->expectMessageFromPage(JsonEq(R"({
|
||||
"id": 1,
|
||||
"result": {}
|
||||
})"));
|
||||
this->toPage_->sendMessage(R"({
|
||||
"id": 1,
|
||||
"method": "Runtime.addBinding",
|
||||
"params": {"name": "foo"}
|
||||
})");
|
||||
|
||||
this->expectMessageFromPage(JsonParsed(AllOf(
|
||||
AtJsonPtr("/method", "Runtime.bindingCalled"),
|
||||
AtJsonPtr("/params/name", "foo"),
|
||||
AtJsonPtr("/params/payload", "bar"))));
|
||||
this->eval("globalThis.foo('bar');");
|
||||
}
|
||||
|
||||
TYPED_TEST(JsiIntegrationPortableTest, ExceptionDuringAddBindingIsIgnored) {
|
||||
this->connect();
|
||||
|
||||
InSequence s;
|
||||
|
||||
this->eval(R"(
|
||||
Object.defineProperty(globalThis, 'foo', {
|
||||
get: function () { return 42; },
|
||||
set: function () { throw new Error('nope'); },
|
||||
});
|
||||
)");
|
||||
|
||||
this->expectMessageFromPage(JsonEq(R"({
|
||||
"id": 1,
|
||||
"result": {}
|
||||
})"));
|
||||
this->toPage_->sendMessage(R"({
|
||||
"id": 1,
|
||||
"method": "Runtime.addBinding",
|
||||
"params": {"name": "foo"}
|
||||
})");
|
||||
|
||||
EXPECT_TRUE(this->eval("globalThis.foo === 42").getBool());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
TEST_F(JsiIntegrationHermesTest, EvaluateExpression) {
|
||||
connect();
|
||||
|
||||
EXPECT_CALL(fromPage(), onMessage(JsonEq(R"({
|
||||
"id": 1,
|
||||
"result": {
|
||||
"result": {
|
||||
"type": "number",
|
||||
"value": 42
|
||||
}
|
||||
}
|
||||
})")));
|
||||
expectMessageFromPage(JsonEq(R"({
|
||||
"id": 1,
|
||||
"result": {
|
||||
"result": {
|
||||
"type": "number",
|
||||
"value": 42
|
||||
}
|
||||
}
|
||||
})"));
|
||||
toPage_->sendMessage(R"({
|
||||
"id": 1,
|
||||
"method": "Runtime.evaluate",
|
||||
|
||||
Reference in New Issue
Block a user