| Index: src/JsEngine.cpp |
| =================================================================== |
| --- a/src/JsEngine.cpp |
| +++ b/src/JsEngine.cpp |
| @@ -18,6 +18,8 @@ |
| #include <AdblockPlus.h> |
| #include "GlobalJsObject.h" |
| #include "JsContext.h" |
| +#include "JsEngineInternal.h" |
| +#include "JsEngineTransition.h" |
| #include "JsError.h" |
| #include "Scheduler.h" |
| #include "Utils.h" |
| @@ -74,6 +76,13 @@ |
| }; |
| } |
| +V8ExecutionScope::V8ExecutionScope(JsEngineInternal* engine) |
| + : lock(engine->GetIsolate()), |
| + isolateScope(engine->GetIsolate()), |
| + handleScope(engine->GetIsolate()), |
| + contextScope(engine->GetContextAsLocal()) |
| +{} |
| + |
| AdblockPlus::ScopedV8Isolate::ScopedV8Isolate() |
| { |
| V8Initializer::Init(); |
| @@ -88,27 +97,94 @@ |
| AdblockPlus::JsEngine::JsEngine(const ScopedV8IsolatePtr& isolate) |
| : isolate(isolate), |
| - scheduler(new SchedulerImpl()) // use std::make_unique after we upgrade out of VS2012 |
| + scheduler(new SchedulerImpl()) // TODO: make_unique once available |
| {} |
| +JsEngineInternal::JsEngineInternal(const AdblockPlus::ScopedV8IsolatePtr& isolate) |
| + : AdblockPlus::JsEngine(isolate), |
| + context(isolate->Get(),v8::Context::New(isolate->Get())) |
| +{ |
| + /* |
| + * Enter v8 scope for our context so that we can initialize its global object. |
| + */ |
| + const v8::Context::Scope contextScope(GetContextAsLocal()); |
| + auto globalObject = GetGlobalObject(); |
| + auto propertyName = AdblockPlus::Utils::ToV8String(GetIsolate(), "setTimeout"); |
| + globalObject->Set(propertyName, MakeCallback(::CallbackForSetTimeout)); |
|
Eric
2016/12/19 22:39:53
New initialization of global object without any us
|
| + // TODO: Move the rest of the global object initializations here |
| +} |
| + |
| +/** |
| + * \par Design Notes |
| + * It is technically necessary to construct JsEngine instances *only* within a factory. |
| + * Initialization requires that certain transient v8 scopes be set up |
| + * before initialization and torn down afterwards. |
| + * C++ has no syntax to use anything like a sentry object in the constructor itself. |
| + * Thus we need to establish v8 scope within every C++ scope that constructs an object. |
| + */ |
| AdblockPlus::JsEnginePtr AdblockPlus::JsEngine::New(const AppInfo& appInfo, const ScopedV8IsolatePtr& isolate) |
| { |
| - JsEnginePtr result(new JsEngine(isolate)); |
| + auto isolateP = isolate->Get(); |
| + /* |
| + * TODO: Remove `locker`. |
| + * Until #3595 is fixed, unit tests allocate isolates outside of this class. |
| + * Once we're no longer doing that, we may assume that this class holds |
| + * exclusive access to the nascent isolate. |
| + * The factory and constructor constitute a single-threaded usage, |
| + * and there will be no need to lock the isolate. |
| + */ |
| + const v8::Locker locker(isolateP); |
| + /* |
| + * Set up v8 scopes for isolate and handle. |
| + * We cannot set up the v8 scope for context because it doesn't exist |
| + * until after the constructor for `JsEngineInternal` returns. |
| + */ |
| + const v8::Isolate::Scope isolateScope(isolateP); |
| + const v8::HandleScope handleScope(isolateP); |
| - const v8::Locker locker(result->GetIsolate()); |
| - const v8::Isolate::Scope isolateScope(result->GetIsolate()); |
| - const v8::HandleScope handleScope(result->GetIsolate()); |
| + std::shared_ptr<JsEngineInternal> engine(std::make_shared<JsEngineInternal>(isolate)); |
| - result->context.reset(new v8::Persistent<v8::Context>(result->GetIsolate(), |
| - v8::Context::New(result->GetIsolate()))); |
| + JsEnginePtr result(engine); |
| + // Establish a context scope for the legacy setup of the global object |
| + const v8::Context::Scope contextScope(engine->GetContextAsLocal()); |
| AdblockPlus::GlobalJsObject::Setup(result, appInfo, result->GetGlobalObject()); |
| return result; |
| } |
| +v8::Local<v8::Context> JsEngineInternal::GetContextAsLocal() const |
| +{ |
| + return v8::Local<v8::Context>::New(isolate->Get(), context); |
| +} |
| + |
| AdblockPlus::JsValuePtr AdblockPlus::JsEngine::GetGlobalObject() |
| { |
| - JsContext context(shared_from_this()); |
| - return JsValuePtr(new JsValue(shared_from_this(), context.GetV8Context()->Global())); |
| + return JsValuePtr(new JsValue(shared_from_this(), ToInternal(this)->GetGlobalObject())); |
| +} |
| + |
| +v8::Local<v8::Object> JsEngineInternal::GetGlobalObject() |
| +{ |
| + return GetContextAsLocal()->Global(); |
| +} |
| + |
| +v8::Local<v8::Value> JsEngineInternal::ApplyFunction( |
| + v8::Local<v8::Function> func, |
| + AllocatedArray<v8::Local<v8::Value>> args) |
| +{ |
| + return ApplyFunction(GetGlobalObject(), func, std::move(args)); |
| +} |
| + |
| +v8::Local<v8::Value> JsEngineInternal::ApplyFunction( |
| + v8::Local<v8::Object> thisObject, |
| + v8::Local<v8::Function> func, |
| + AllocatedArray<v8::Local<v8::Value>> args) |
| +{ |
| + const v8::TryCatch tryCatch; |
| + v8::Local<v8::Value> result = func->Call(thisObject, args.Size(), args.Get()); |
| + if (tryCatch.HasCaught()) |
| + { |
| + throw AdblockPlus::JsError(tryCatch.Exception(), tryCatch.Message()); |
| + } |
| + return result; |
| } |
| AdblockPlus::JsValuePtr AdblockPlus::JsEngine::Evaluate(const std::string& source, |
| @@ -199,6 +275,28 @@ |
| return result; |
| } |
| +/** |
| + * \par Implementation Notes |
| + * We initialize the `Data()` element of the callback arguments with `this`, |
| + * which raises an issue about life cycle. |
| + * The result of `FunctionTemplate::New` is to create a function |
| + * whose lifespan is the same as the context it's created in. |
| + * The context is an instance member of the engine, |
| + * so as long as the engine exists, so does the context. |
| + * Since evaluation in v8 only occurs during the ordinary lifespan of the engine |
| + * (i.e. after the constructor and before the destructor), |
| + * the `this` pointer will be valid whenever the callback function might be called. |
| + */ |
| +v8::Local<v8::Function> JsEngineInternal::MakeCallback(v8::InvocationCallback callback) |
| +{ |
| + return v8::FunctionTemplate::New(callback, v8::External::New(this))->GetFunction(); |
| +} |
| + |
| +JsEngineInternal* JsEngineInternal::ExtractEngine(const v8::Arguments& arguments) |
| +{ |
| + return static_cast<JsEngineInternal*>(v8::Local<v8::External>::Cast(arguments.Data())->Value()); |
| +} |
| + |
| AdblockPlus::JsValueList AdblockPlus::JsEngine::ConvertArguments(const v8::Arguments& arguments) |
| { |
| const JsContext context(shared_from_this()); |
| @@ -253,10 +351,10 @@ |
| logSystem = val; |
| } |
| - |
| void AdblockPlus::JsEngine::SetGlobalProperty(const std::string& name, |
| AdblockPlus::JsValuePtr value) |
| { |
| + JsContext jsContext(shared_from_this()); |
| auto global = GetGlobalObject(); |
| if (!global) |
| throw std::runtime_error("Global object cannot be null"); |
| @@ -275,3 +373,13 @@ |
| { |
| scheduler->JoinAll(); |
| } |
| + |
| +JsEngineInternal* ToInternal(AdblockPlus::JsEnginePtr p) |
| +{ |
| + return static_cast<JsEngineInternal*>(p.get()); |
| +} |
| + |
| +JsEngineInternal* ToInternal(AdblockPlus::JsEngine* p) |
| +{ |
| + return static_cast<JsEngineInternal*>(p); |
| +} |