Skip to content

Commit c6c4c45

Browse files
hoxyqfacebook-github-bot
authored andcommitted
Define console.createTask (facebook#54338)
Summary: # Changelog: [Internal] Adds a basic implementation of the `console.createTask()` method. When this method is called, the `ConsoleTaskContext` is allocated. When `task.run()` is called, the corresponding `ConsoleTask` that has a pointer to `ConsoleTaskContext` will push it onto the stack in `ConsoleTaskOrchestrator`. Also adds types to corresponding Flow declarations. Differential Revision: D85481865
1 parent 12008b7 commit c6c4c45

File tree

4 files changed

+237
-0
lines changed

4 files changed

+237
-0
lines changed

packages/polyfills/console.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,11 @@ function consoleAssertPolyfill(expression, label) {
571571

572572
function stub() {}
573573

574+
// https://developer.chrome.com/docs/devtools/console/api#createtask
575+
function consoleCreateTaskStub() {
576+
return {run: cb => cb()};
577+
}
578+
574579
if (global.nativeLoggingHook) {
575580
const originalConsole = global.console;
576581
// Preserve the original `console` as `originalConsole`
@@ -587,6 +592,7 @@ if (global.nativeLoggingHook) {
587592
timeStamp: stub,
588593
count: stub,
589594
countReset: stub,
595+
createTask: consoleCreateTaskStub,
590596
...(originalConsole ?? {}),
591597
error: getNativeLogFunction(LOG_LEVELS.error),
592598
info: getNativeLogFunction(LOG_LEVELS.info),
@@ -705,6 +711,7 @@ if (global.nativeLoggingHook) {
705711
time: stub,
706712
timeEnd: stub,
707713
timeStamp: stub,
714+
createTask: consoleCreateTaskStub,
708715
};
709716

710717
Object.defineProperty(console, '_isPolyfilled', {

packages/react-native/ReactCommon/jsinspector-modern/RuntimeTargetConsole.cpp

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
#include "ConsoleTask.h"
9+
#include "ConsoleTaskContext.h"
10+
811
#include <jsinspector-modern/RuntimeTarget.h>
912
#include <jsinspector-modern/tracing/PerformanceTracer.h>
1013

@@ -521,6 +524,68 @@ void installConsoleTimeStamp(
521524
})));
522525
}
523526

527+
/**
528+
* run method of the task object returned from console.createTask().
529+
*/
530+
jsi::Value consoleTaskRun(
531+
jsi::Runtime& runtime,
532+
const jsi::Value* args,
533+
size_t count,
534+
std::shared_ptr<ConsoleTaskContext> taskContext) {
535+
if (count < 1 || !args[0].isObject()) {
536+
throw JSError(runtime, "First argument must be a function");
537+
}
538+
auto fnObj = args[0].getObject(runtime);
539+
if (!fnObj.isFunction(runtime)) {
540+
throw JSError(runtime, "First argument must be a function");
541+
}
542+
543+
ConsoleTask consoleTask{taskContext};
544+
545+
auto fn = fnObj.getFunction(runtime);
546+
return fn.call(runtime);
547+
}
548+
549+
/**
550+
* console.createTask. Non-standardized.
551+
* https://developer.chrome.com/docs/devtools/console/api#createtask
552+
*/
553+
jsi::Value consoleCreateTask(
554+
jsi::Runtime& runtime,
555+
const jsi::Value* args,
556+
size_t count,
557+
RuntimeTargetDelegate& runtimeTargetDelegate,
558+
bool /* enabled */) {
559+
if (count < 1 || !args[0].isString()) {
560+
throw JSError(runtime, "First argument must be a non-empty string");
561+
}
562+
auto name = args[0].asString(runtime).utf8(runtime);
563+
if (name.empty()) {
564+
throw JSError(runtime, "First argument must be a non-empty string");
565+
}
566+
567+
jsi::Object task{runtime};
568+
auto taskContext = std::make_shared<ConsoleTaskContext>(
569+
runtime, runtimeTargetDelegate, name);
570+
taskContext->schedule();
571+
572+
task.setProperty(
573+
runtime,
574+
"run",
575+
jsi::Function::createFromHostFunction(
576+
runtime,
577+
jsi::PropNameID::forAscii(runtime, "run"),
578+
0,
579+
[taskContext](
580+
jsi::Runtime& runtime,
581+
const jsi::Value& /*thisVal*/,
582+
const jsi::Value* args,
583+
size_t count) {
584+
return consoleTaskRun(runtime, args, count, taskContext);
585+
}));
586+
return task;
587+
}
588+
524589
} // namespace
525590

526591
void RuntimeTarget::installConsoleHandler() {
@@ -624,6 +689,33 @@ void RuntimeTarget::installConsoleHandler() {
624689
*/
625690
installConsoleTimeStamp(runtime, originalConsole, console);
626691

692+
/**
693+
* console.createTask
694+
*/
695+
console.setProperty(
696+
runtime,
697+
"createTask",
698+
jsi::Function::createFromHostFunction(
699+
runtime,
700+
jsi::PropNameID::forAscii(runtime, "createTask"),
701+
0,
702+
[state, selfWeak](
703+
jsi::Runtime& runtime,
704+
const jsi::Value& /*thisVal*/,
705+
const jsi::Value* args,
706+
size_t count) {
707+
jsi::Value task;
708+
tryExecuteSync(selfWeak, [&](auto& self) {
709+
task = consoleCreateTask(
710+
runtime,
711+
args,
712+
count,
713+
self.delegate_,
714+
self.isConsoleCreateTaskEnabled());
715+
});
716+
return task;
717+
}));
718+
627719
// Install forwarding console methods.
628720
#define FORWARDING_CONSOLE_METHOD(name, type) \
629721
installConsoleMethod(#name, console_##name);
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include <folly/executors/QueuedImmediateExecutor.h>
9+
10+
#include "JsiIntegrationTest.h"
11+
#include "engines/JsiIntegrationTestHermesEngineAdapter.h"
12+
13+
#include <jsinspector-modern/ConsoleTaskOrchestrator.h>
14+
15+
using namespace ::testing;
16+
17+
namespace facebook::react::jsinspector_modern {
18+
19+
/**
20+
* A test fixture for the console.createTask API.
21+
*/
22+
class ConsoleCreateTaskTest : public JsiIntegrationPortableTestBase<
23+
JsiIntegrationTestHermesEngineAdapter,
24+
folly::QueuedImmediateExecutor> {};
25+
26+
TEST_F(ConsoleCreateTaskTest, Installed) {
27+
auto result = eval("typeof console.createTask");
28+
auto& runtime = engineAdapter_->getRuntime();
29+
EXPECT_EQ(result.asString(runtime).utf8(runtime), "function");
30+
}
31+
32+
TEST_F(ConsoleCreateTaskTest, ReturnsTaskObject) {
33+
auto result = eval("typeof console.createTask('test-task')");
34+
auto& runtime = engineAdapter_->getRuntime();
35+
EXPECT_EQ(result.asString(runtime).utf8(runtime), "object");
36+
}
37+
38+
TEST_F(ConsoleCreateTaskTest, TaskObjectHasRunMethod) {
39+
auto result = eval("typeof console.createTask('test-task').run");
40+
auto& runtime = engineAdapter_->getRuntime();
41+
EXPECT_EQ(result.asString(runtime).utf8(runtime), "function");
42+
}
43+
44+
TEST_F(ConsoleCreateTaskTest, RunMethodExecutesFunction) {
45+
auto result = eval(R"(
46+
let executed = false;
47+
const task = console.createTask('test-task');
48+
task.run(() => { executed = true; });
49+
executed;
50+
)");
51+
EXPECT_TRUE(result.getBool());
52+
}
53+
54+
TEST_F(ConsoleCreateTaskTest, RunMethodReturnsValue) {
55+
auto result = eval(R"(
56+
const task = console.createTask('test-task');
57+
task.run(() => 42);
58+
)");
59+
EXPECT_EQ(result.getNumber(), 42);
60+
}
61+
62+
TEST_F(ConsoleCreateTaskTest, ThrowsOnNoArguments) {
63+
EXPECT_THROW(eval("console.createTask()"), facebook::jsi::JSError);
64+
}
65+
66+
TEST_F(ConsoleCreateTaskTest, ThrowsOnEmptyString) {
67+
EXPECT_THROW(eval("console.createTask('')"), facebook::jsi::JSError);
68+
}
69+
70+
TEST_F(ConsoleCreateTaskTest, ThrowsOnNonStringArgument) {
71+
EXPECT_THROW(eval("console.createTask(123)"), facebook::jsi::JSError);
72+
EXPECT_THROW(eval("console.createTask(null)"), facebook::jsi::JSError);
73+
EXPECT_THROW(eval("console.createTask(undefined)"), facebook::jsi::JSError);
74+
EXPECT_THROW(eval("console.createTask({})"), facebook::jsi::JSError);
75+
}
76+
77+
TEST_F(ConsoleCreateTaskTest, RunMethodThrowsOnNoArguments) {
78+
EXPECT_THROW(
79+
eval(R"(
80+
const task = console.createTask('test-task');
81+
task.run();
82+
)"),
83+
facebook::jsi::JSError);
84+
}
85+
86+
TEST_F(ConsoleCreateTaskTest, RunMethodThrowsOnNonFunction) {
87+
EXPECT_THROW(
88+
eval(R"(
89+
const task = console.createTask('test-task');
90+
task.run(123);
91+
)"),
92+
facebook::jsi::JSError);
93+
EXPECT_THROW(
94+
eval(R"(
95+
const task = console.createTask('test-task');
96+
task.run('not a function');
97+
)"),
98+
facebook::jsi::JSError);
99+
EXPECT_THROW(
100+
eval(R"(
101+
const task = console.createTask('test-task');
102+
task.run({});
103+
)"),
104+
facebook::jsi::JSError);
105+
}
106+
107+
TEST_F(ConsoleCreateTaskTest, MultipleTasksCanBeCreated) {
108+
auto result = eval(R"(
109+
const task1 = console.createTask('task-1');
110+
const task2 = console.createTask('task-2');
111+
let count = 0;
112+
task1.run(() => { count++; });
113+
task2.run(() => { count++; });
114+
count;
115+
)");
116+
EXPECT_EQ(result.getNumber(), 2);
117+
}
118+
119+
TEST_F(ConsoleCreateTaskTest, TaskCanBeRunMultipleTimes) {
120+
auto result = eval(R"(
121+
const task = console.createTask('test-task');
122+
let count = 0;
123+
task.run(() => { count++; });
124+
task.run(() => { count++; });
125+
task.run(() => { count++; });
126+
count;
127+
)");
128+
EXPECT_EQ(result.getNumber(), 3);
129+
}
130+
131+
} // namespace facebook::react::jsinspector_modern

packages/react-native/flow/bom.js.flow

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ type DevToolsColor =
2525
| 'warning'
2626
| 'error';
2727

28+
declare interface ConsoleTask {
29+
run<T>(f: () => T): T;
30+
}
31+
2832
// $FlowExpectedError[libdef-override] Flow core definitions are incomplete.
2933
declare var console: {
3034
// Logging
@@ -75,6 +79,9 @@ declare var console: {
7579
detail?: {[string]: mixed},
7680
): void,
7781

82+
// Stack tagging
83+
createTask(label: string): ConsoleTask,
84+
7885
...
7986
};
8087

0 commit comments

Comments
 (0)