diff --git a/IntegrationTests/GlobalEvalWithSourceUrlTest.js b/IntegrationTests/GlobalEvalWithSourceUrlTest.js
new file mode 100644
index 00000000000..3b633e9acc2
--- /dev/null
+++ b/IntegrationTests/GlobalEvalWithSourceUrlTest.js
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @format
+ * @flow strict-local
+ */
+
+'use strict';
+
+const React = require('react');
+const ReactNative = require('react-native');
+const parseErrorStack = require('react-native/Libraries/Core/Devtools/parseErrorStack');
+const {View} = ReactNative;
+
+const {TestModule} = ReactNative.NativeModules;
+
+class GlobalEvalWithSourceUrlTest extends React.Component<{}> {
+ componentDidMount() {
+ if (typeof global.globalEvalWithSourceUrl !== 'function') {
+ throw new Error(
+ 'Expected to find globalEvalWithSourceUrl function on global object but found ' +
+ typeof global.globalEvalWithSourceUrl,
+ );
+ }
+ const value = global.globalEvalWithSourceUrl('42');
+ if (value !== 42) {
+ throw new Error(
+ 'Expected globalEvalWithSourceUrl(expression) to return a value',
+ );
+ }
+ let syntaxError;
+ try {
+ global.globalEvalWithSourceUrl('{');
+ } catch (e) {
+ syntaxError = e;
+ }
+ if (!syntaxError) {
+ throw new Error(
+ 'Expected globalEvalWithSourceUrl to throw on a syntax error',
+ );
+ }
+ if (!(syntaxError instanceof SyntaxError)) {
+ throw new Error(
+ 'Expected globalEvalWithSourceUrl to throw SyntaxError on a syntax error',
+ );
+ }
+ const url = 'http://example.com/foo.js';
+ let error;
+ try {
+ global.globalEvalWithSourceUrl('throw new Error()', url);
+ } catch (e) {
+ error = e;
+ }
+ if (!error) {
+ throw new Error(
+ 'Expected globalEvalWithSourceUrl to throw an Error object',
+ );
+ }
+ const parsedStack = parseErrorStack(error);
+ if (parsedStack[0].file !== url) {
+ throw new Error(
+ `Expected first eval stack frame to be in ${url} but found ${
+ parsedStack[0].file
+ }`,
+ );
+ }
+ TestModule.markTestCompleted();
+ }
+
+ render(): React.Node {
+ return ;
+ }
+}
+
+GlobalEvalWithSourceUrlTest.displayName = 'GlobalEvalWithSourceUrlTest';
+
+module.exports = GlobalEvalWithSourceUrlTest;
diff --git a/IntegrationTests/IntegrationTestsApp.js b/IntegrationTests/IntegrationTestsApp.js
index d1f736bd789..c8af81bf126 100644
--- a/IntegrationTests/IntegrationTestsApp.js
+++ b/IntegrationTests/IntegrationTestsApp.js
@@ -36,6 +36,7 @@ const TESTS = [
require('./SyncMethodTest'),
require('./WebSocketTest'),
require('./AccessibilityManagerTest'),
+ require('./GlobalEvalWithSourceUrlTest'),
];
TESTS.forEach(
diff --git a/RNTester/RNTesterIntegrationTests/RNTesterIntegrationTests.m b/RNTester/RNTesterIntegrationTests/RNTesterIntegrationTests.m
index de28e9a7425..b8a55652aa8 100644
--- a/RNTester/RNTesterIntegrationTests/RNTesterIntegrationTests.m
+++ b/RNTester/RNTesterIntegrationTests/RNTesterIntegrationTests.m
@@ -74,6 +74,6 @@ RCT_TEST(SyncMethodTest)
RCT_TEST(PromiseTest)
RCT_TEST_ONLY_WITH_PACKAGER(WebSocketTest) // Requires a WebSocket test server, see scripts/objc-test.sh
RCT_TEST(AccessibilityManagerTest)
+RCT_TEST(GlobalEvalWithSourceUrlTest)
@end
-
diff --git a/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp b/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp
index e261dc574b0..c31a740509b 100644
--- a/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp
+++ b/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp
@@ -113,6 +113,21 @@ void JSIExecutor::loadApplicationScript(
const jsi::Value *args,
size_t count) { return nativeCallSyncHook(args, count); }));
+#if DEBUG
+ runtime_->global().setProperty(
+ *runtime_,
+ "globalEvalWithSourceUrl",
+ Function::createFromHostFunction(
+ *runtime_,
+ PropNameID::forAscii(*runtime_, "globalEvalWithSourceUrl"),
+ 1,
+ [this](
+ jsi::Runtime &,
+ const jsi::Value &,
+ const jsi::Value *args,
+ size_t count) { return globalEvalWithSourceUrl(args, count); }));
+#endif
+
if (runtimeInstaller_) {
runtimeInstaller_(*runtime_);
}
@@ -358,6 +373,24 @@ Value JSIExecutor::nativeCallSyncHook(const Value *args, size_t count) {
return valueFromDynamic(*runtime_, result.value());
}
+#if DEBUG
+Value JSIExecutor::globalEvalWithSourceUrl(const Value *args, size_t count) {
+ if (count != 1 && count != 2) {
+ throw std::invalid_argument(
+ "globalEvalWithSourceUrl arg count must be 1 or 2");
+ }
+
+ auto code = args[0].asString(*runtime_).utf8(*runtime_);
+ std::string url;
+ if (count > 1 && args[1].isString()) {
+ url = args[1].asString(*runtime_).utf8(*runtime_);
+ }
+
+ return runtime_->evaluateJavaScript(
+ std::make_unique(std::move(code)), url);
+}
+#endif
+
void bindNativeLogger(Runtime &runtime, Logger logger) {
runtime.global().setProperty(
runtime,
diff --git a/ReactCommon/jsiexecutor/jsireact/JSIExecutor.h b/ReactCommon/jsiexecutor/jsireact/JSIExecutor.h
index a290c409b8d..9e932964252 100644
--- a/ReactCommon/jsiexecutor/jsireact/JSIExecutor.h
+++ b/ReactCommon/jsiexecutor/jsireact/JSIExecutor.h
@@ -112,6 +112,9 @@ class JSIExecutor : public JSExecutor {
void callNativeModules(const jsi::Value &queue, bool isEndOfBatch);
jsi::Value nativeCallSyncHook(const jsi::Value *args, size_t count);
jsi::Value nativeRequire(const jsi::Value *args, size_t count);
+#ifdef DEBUG
+ jsi::Value globalEvalWithSourceUrl(const jsi::Value *args, size_t count);
+#endif
std::shared_ptr runtime_;
std::shared_ptr delegate_;