| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "extensions/renderer/bindings/test_js_runner.h" |
| |
| #include "base/bind.h" |
| #include "extensions/renderer/bindings/api_binding_test_util.h" |
| |
| namespace extensions { |
| |
| namespace { |
| |
| // NOTE(devlin): These aren't thread-safe. If we have multi-threaded unittests, |
| // we'll need to expand these. |
| bool g_allow_errors = false; |
| bool g_suspended = false; |
| |
| } // namespace |
| |
| TestJSRunner::Scope::Scope(std::unique_ptr<JSRunner> runner) |
| : runner_(std::move(runner)), |
| old_runner_(JSRunner::GetInstanceForTesting()) { |
| DCHECK_NE(runner_.get(), old_runner_); |
| JSRunner::SetInstanceForTesting(runner_.get()); |
| } |
| |
| TestJSRunner::Scope::~Scope() { |
| DCHECK_EQ(runner_.get(), JSRunner::GetInstanceForTesting()); |
| JSRunner::SetInstanceForTesting(old_runner_); |
| } |
| |
| TestJSRunner::AllowErrors::AllowErrors() { |
| DCHECK(!g_allow_errors) << "Nested AllowErrors() blocks are not allowed."; |
| g_allow_errors = true; |
| } |
| |
| TestJSRunner::AllowErrors::~AllowErrors() { |
| DCHECK(g_allow_errors); |
| g_allow_errors = false; |
| } |
| |
| TestJSRunner::Suspension::Suspension() { |
| DCHECK(!g_suspended) << "Nested Suspension() blocks are not allowed."; |
| g_suspended = true; |
| } |
| |
| TestJSRunner::Suspension::~Suspension() { |
| DCHECK(g_suspended); |
| g_suspended = false; |
| TestJSRunner* test_runner = |
| static_cast<TestJSRunner*>(JSRunner::GetInstanceForTesting()); |
| DCHECK(test_runner); |
| test_runner->Flush(); |
| } |
| |
| TestJSRunner::PendingCall::PendingCall() {} |
| TestJSRunner::PendingCall::~PendingCall() = default; |
| TestJSRunner::PendingCall::PendingCall(PendingCall&& other) = default; |
| |
| TestJSRunner::TestJSRunner() {} |
| TestJSRunner::TestJSRunner(const base::Closure& will_call_js) |
| : will_call_js_(will_call_js) {} |
| TestJSRunner::~TestJSRunner() {} |
| |
| void TestJSRunner::RunJSFunction(v8::Local<v8::Function> function, |
| v8::Local<v8::Context> context, |
| int argc, |
| v8::Local<v8::Value> argv[], |
| ResultCallback callback) { |
| if (g_suspended) { |
| // Script is suspended. Queue up the call and return. |
| v8::Isolate* isolate = context->GetIsolate(); |
| PendingCall call; |
| call.isolate = isolate; |
| call.function.Reset(isolate, function); |
| call.context.Reset(isolate, context); |
| call.arguments.reserve(argc); |
| call.callback = std::move(callback); |
| for (int i = 0; i < argc; ++i) |
| call.arguments.push_back(v8::Global<v8::Value>(isolate, argv[i])); |
| pending_calls_.push_back(std::move(call)); |
| return; |
| } |
| |
| // Functions should always run in the scope of the context. |
| v8::Context::Scope context_scope(context); |
| |
| if (will_call_js_) |
| will_call_js_.Run(); |
| |
| v8::MaybeLocal<v8::Value> result; |
| if (g_allow_errors) { |
| result = function->Call(context, context->Global(), argc, argv); |
| } else { |
| result = RunFunctionOnGlobal(function, context, argc, argv); |
| } |
| |
| if (callback) |
| std::move(callback).Run(context, result); |
| } |
| |
| v8::MaybeLocal<v8::Value> TestJSRunner::RunJSFunctionSync( |
| v8::Local<v8::Function> function, |
| v8::Local<v8::Context> context, |
| int argc, |
| v8::Local<v8::Value> argv[]) { |
| // Note: deliberately circumvent g_suspension, since this should only be used |
| // in response to JS interaction. |
| if (will_call_js_) |
| will_call_js_.Run(); |
| |
| if (g_allow_errors) { |
| v8::MaybeLocal<v8::Value> result = |
| function->Call(context, context->Global(), argc, argv); |
| return result; |
| } |
| return RunFunctionOnGlobal(function, context, argc, argv); |
| } |
| |
| void TestJSRunner::Flush() { |
| // Move pending_calls_ in case running any pending calls results in more calls |
| // into the JSRunner. |
| std::vector<PendingCall> calls = std::move(pending_calls_); |
| pending_calls_.clear(); |
| for (auto& call : calls) { |
| v8::Isolate* isolate = call.isolate; |
| v8::Local<v8::Context> context = call.context.Get(isolate); |
| v8::Context::Scope context_scope(context); |
| std::vector<v8::Local<v8::Value>> local_arguments; |
| local_arguments.reserve(call.arguments.size()); |
| for (auto& arg : call.arguments) |
| local_arguments.push_back(arg.Get(isolate)); |
| v8::MaybeLocal<v8::Value> result = |
| RunJSFunctionSync(call.function.Get(isolate), context, |
| local_arguments.size(), local_arguments.data()); |
| if (call.callback) |
| std::move(call.callback).Run(context, result); |
| } |
| } |
| |
| } // namespace extensions |