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); |
+} |