1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "extensions/renderer/bindings/test_js_runner.h"
6
7 #include "base/bind.h"
8 #include "extensions/renderer/bindings/api_binding_test_util.h"
9
10 namespace extensions {
11
12 namespace {
13
14 // NOTE(devlin): These aren't thread-safe. If we have multi-threaded unittests,
15 // we'll need to expand these.
16 bool g_allow_errors = false;
17 bool g_suspended = false;
18
19 } // namespace
20
Scope(std::unique_ptr<JSRunner> runner)21 TestJSRunner::Scope::Scope(std::unique_ptr<JSRunner> runner)
22 : runner_(std::move(runner)),
23 old_runner_(JSRunner::GetInstanceForTesting()) {
24 DCHECK_NE(runner_.get(), old_runner_);
25 JSRunner::SetInstanceForTesting(runner_.get());
26 }
27
~Scope()28 TestJSRunner::Scope::~Scope() {
29 DCHECK_EQ(runner_.get(), JSRunner::GetInstanceForTesting());
30 JSRunner::SetInstanceForTesting(old_runner_);
31 }
32
AllowErrors()33 TestJSRunner::AllowErrors::AllowErrors() {
34 DCHECK(!g_allow_errors) << "Nested AllowErrors() blocks are not allowed.";
35 g_allow_errors = true;
36 }
37
~AllowErrors()38 TestJSRunner::AllowErrors::~AllowErrors() {
39 DCHECK(g_allow_errors);
40 g_allow_errors = false;
41 }
42
Suspension()43 TestJSRunner::Suspension::Suspension() {
44 DCHECK(!g_suspended) << "Nested Suspension() blocks are not allowed.";
45 g_suspended = true;
46 }
47
~Suspension()48 TestJSRunner::Suspension::~Suspension() {
49 DCHECK(g_suspended);
50 g_suspended = false;
51 TestJSRunner* test_runner =
52 static_cast<TestJSRunner*>(JSRunner::GetInstanceForTesting());
53 DCHECK(test_runner);
54 test_runner->Flush();
55 }
56
PendingCall()57 TestJSRunner::PendingCall::PendingCall() {}
58 TestJSRunner::PendingCall::~PendingCall() = default;
59 TestJSRunner::PendingCall::PendingCall(PendingCall&& other) = default;
60
TestJSRunner()61 TestJSRunner::TestJSRunner() {}
TestJSRunner(const base::Closure & will_call_js)62 TestJSRunner::TestJSRunner(const base::Closure& will_call_js)
63 : will_call_js_(will_call_js) {}
~TestJSRunner()64 TestJSRunner::~TestJSRunner() {}
65
RunJSFunction(v8::Local<v8::Function> function,v8::Local<v8::Context> context,int argc,v8::Local<v8::Value> argv[],ResultCallback callback)66 void TestJSRunner::RunJSFunction(v8::Local<v8::Function> function,
67 v8::Local<v8::Context> context,
68 int argc,
69 v8::Local<v8::Value> argv[],
70 ResultCallback callback) {
71 if (g_suspended) {
72 // Script is suspended. Queue up the call and return.
73 v8::Isolate* isolate = context->GetIsolate();
74 PendingCall call;
75 call.isolate = isolate;
76 call.function.Reset(isolate, function);
77 call.context.Reset(isolate, context);
78 call.arguments.reserve(argc);
79 call.callback = std::move(callback);
80 for (int i = 0; i < argc; ++i)
81 call.arguments.push_back(v8::Global<v8::Value>(isolate, argv[i]));
82 pending_calls_.push_back(std::move(call));
83 return;
84 }
85
86 // Functions should always run in the scope of the context.
87 v8::Context::Scope context_scope(context);
88
89 if (will_call_js_)
90 will_call_js_.Run();
91
92 v8::MaybeLocal<v8::Value> result;
93 if (g_allow_errors) {
94 result = function->Call(context, context->Global(), argc, argv);
95 } else {
96 result = RunFunctionOnGlobal(function, context, argc, argv);
97 }
98
99 if (callback)
100 std::move(callback).Run(context, result);
101 }
102
RunJSFunctionSync(v8::Local<v8::Function> function,v8::Local<v8::Context> context,int argc,v8::Local<v8::Value> argv[])103 v8::MaybeLocal<v8::Value> TestJSRunner::RunJSFunctionSync(
104 v8::Local<v8::Function> function,
105 v8::Local<v8::Context> context,
106 int argc,
107 v8::Local<v8::Value> argv[]) {
108 // Note: deliberately circumvent g_suspension, since this should only be used
109 // in response to JS interaction.
110 if (will_call_js_)
111 will_call_js_.Run();
112
113 if (g_allow_errors) {
114 v8::MaybeLocal<v8::Value> result =
115 function->Call(context, context->Global(), argc, argv);
116 return result;
117 }
118 return RunFunctionOnGlobal(function, context, argc, argv);
119 }
120
Flush()121 void TestJSRunner::Flush() {
122 // Move pending_calls_ in case running any pending calls results in more calls
123 // into the JSRunner.
124 std::vector<PendingCall> calls = std::move(pending_calls_);
125 pending_calls_.clear();
126 for (auto& call : calls) {
127 v8::Isolate* isolate = call.isolate;
128 v8::Local<v8::Context> context = call.context.Get(isolate);
129 v8::Context::Scope context_scope(context);
130 std::vector<v8::Local<v8::Value>> local_arguments;
131 local_arguments.reserve(call.arguments.size());
132 for (auto& arg : call.arguments)
133 local_arguments.push_back(arg.Get(isolate));
134 v8::MaybeLocal<v8::Value> result =
135 RunJSFunctionSync(call.function.Get(isolate), context,
136 local_arguments.size(), local_arguments.data());
137 if (call.callback)
138 std::move(call.callback).Run(context, result);
139 }
140 }
141
142 } // namespace extensions
143