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