Commit 9d72d08a authored by Daniel Clark's avatar Daniel Clark Committed by Commit Bot

[modules] Add ResolveModuleCallback that takes import assertions

This change completes the necessary API changes for import assertions
discussed in
https://docs.google.com/document/d/1yuXgNHSbTAPubT1Mg0JXp5uTrfirkvO1g5cHHCe-LmY.

The old ResolveCallback is deprecated and replaced with a
ResolveModuleCallback that includes import assertions.  Until
ResolveCallback is removed, InstantiateModule and associated functions
are modified to accept both types of callback, using the new one if it
was supplied and the old one otherwise.  An alternative that I chose not
to go with would be to just duplicate InstantiateModule and associated
functions for both callback types.

SyntheticModule::PrepareInstantiate's callback parameter was unused so I
removed it.

Bug: v8:10958
Change-Id: I8e9fbaf9c2853b076b13da02473fbbe039b9db57
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2551919Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Reviewed-by: 's avatarMarja Hölttä <marja@chromium.org>
Commit-Queue: Dan Clark <daniec@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#71506}
parent d35aaf74
......@@ -1628,9 +1628,13 @@ class V8_EXPORT Module : public Data {
*/
int GetIdentityHash() const;
V8_DEPRECATE_SOON("Use ResolveModuleCallback")
typedef MaybeLocal<Module> (*ResolveCallback)(Local<Context> context,
Local<String> specifier,
Local<Module> referrer);
typedef MaybeLocal<Module> (*ResolveModuleCallback)(
Local<Context> context, Local<String> specifier,
Local<FixedArray> import_assertions, Local<Module> referrer);
/**
* Instantiates the module and its dependencies.
......@@ -1639,8 +1643,13 @@ class V8_EXPORT Module : public Data {
* instantiation. (In the case where the callback throws an exception, that
* exception is propagated.)
*/
V8_DEPRECATE_SOON(
"Use the version of InstantiateModule that takes a ResolveModuleCallback "
"parameter")
V8_WARN_UNUSED_RESULT Maybe<bool> InstantiateModule(Local<Context> context,
ResolveCallback callback);
V8_WARN_UNUSED_RESULT Maybe<bool> InstantiateModule(
Local<Context> context, ResolveModuleCallback callback);
/**
* Evaluates the module and its dependencies.
......
......@@ -104,7 +104,8 @@ MAKE_TO_LOCAL(ToLocal, BigInt, BigInt)
MAKE_TO_LOCAL(ExternalToLocal, JSObject, External)
MAKE_TO_LOCAL(CallableToLocal, JSReceiver, Function)
MAKE_TO_LOCAL(ToLocalPrimitive, Object, Primitive)
MAKE_TO_LOCAL(ToLocal, FixedArray, PrimitiveArray)
MAKE_TO_LOCAL(FixedArrayToLocal, FixedArray, FixedArray)
MAKE_TO_LOCAL(PrimitiveArrayToLocal, FixedArray, PrimitiveArray)
MAKE_TO_LOCAL(ScriptOrModuleToLocal, Script, ScriptOrModule)
#undef MAKE_TO_LOCAL_TYPED_ARRAY
......
......@@ -380,7 +380,7 @@ static ScriptOrigin GetScriptOriginForScript(i::Isolate* isolate,
script->column_offset(), options.IsSharedCrossOrigin(), script->id(),
Utils::ToLocal(source_map_url), options.IsOpaque(),
script->type() == i::Script::TYPE_WASM, options.IsModule(),
Utils::ToLocal(host_defined_options));
Utils::PrimitiveArrayToLocal(host_defined_options));
return origin;
}
......@@ -2396,10 +2396,23 @@ int Module::GetIdentityHash() const { return Utils::OpenHandle(this)->hash(); }
Maybe<bool> Module::InstantiateModule(Local<Context> context,
Module::ResolveCallback callback) {
auto isolate = reinterpret_cast<i::Isolate*>(context->GetIsolate());
ENTER_V8(isolate, context, Module, InstantiateModule, Nothing<bool>(),
i::HandleScope);
ResolveModuleCallback callback_with_import_assertions = nullptr;
has_pending_exception =
!i::Module::Instantiate(isolate, Utils::OpenHandle(this), context,
callback_with_import_assertions, callback);
RETURN_ON_FAILED_EXECUTION_PRIMITIVE(bool);
return Just(true);
}
Maybe<bool> Module::InstantiateModule(Local<Context> context,
Module::ResolveModuleCallback callback) {
auto isolate = reinterpret_cast<i::Isolate*>(context->GetIsolate());
ENTER_V8(isolate, context, Module, InstantiateModule, Nothing<bool>(),
i::HandleScope);
has_pending_exception = !i::Module::Instantiate(
isolate, Utils::OpenHandle(this), context, callback);
isolate, Utils::OpenHandle(this), context, callback, nullptr);
RETURN_ON_FAILED_EXECUTION_PRIMITIVE(bool);
return Just(true);
}
......
......@@ -242,7 +242,9 @@ class Utils {
v8::internal::Handle<v8::internal::JSReceiver> obj);
static inline Local<Primitive> ToLocalPrimitive(
v8::internal::Handle<v8::internal::Object> obj);
static inline Local<PrimitiveArray> ToLocal(
static inline Local<FixedArray> FixedArrayToLocal(
v8::internal::Handle<v8::internal::FixedArray> obj);
static inline Local<PrimitiveArray> PrimitiveArrayToLocal(
v8::internal::Handle<v8::internal::FixedArray> obj);
static inline Local<ScriptOrModule> ScriptOrModuleToLocal(
v8::internal::Handle<v8::internal::Script> obj);
......
......@@ -869,7 +869,9 @@ void DisposeModuleEmbedderData(Local<Context> context) {
MaybeLocal<Module> ResolveModuleCallback(Local<Context> context,
Local<String> specifier,
Local<FixedArray> import_assertions,
Local<Module> referrer) {
// TODO(v8:11189) Consider JSON modules support in d8.
Isolate* isolate = context->GetIsolate();
ModuleEmbedderData* d = GetModuleDataFromContext(context);
auto specifier_it =
......
......@@ -174,14 +174,16 @@ MaybeHandle<Cell> Module::ResolveExport(Isolate* isolate, Handle<Module> module,
}
}
bool Module::Instantiate(Isolate* isolate, Handle<Module> module,
v8::Local<v8::Context> context,
v8::Module::ResolveCallback callback) {
bool Module::Instantiate(
Isolate* isolate, Handle<Module> module, v8::Local<v8::Context> context,
v8::Module::ResolveModuleCallback callback,
DeprecatedResolveCallback callback_without_import_assertions) {
#ifdef DEBUG
PrintStatusMessage(*module, "Instantiating module ");
#endif // DEBUG
if (!PrepareInstantiate(isolate, module, context, callback)) {
if (!PrepareInstantiate(isolate, module, context, callback,
callback_without_import_assertions)) {
ResetGraph(isolate, module);
DCHECK_EQ(module->status(), kUninstantiated);
return false;
......@@ -200,9 +202,10 @@ bool Module::Instantiate(Isolate* isolate, Handle<Module> module,
return true;
}
bool Module::PrepareInstantiate(Isolate* isolate, Handle<Module> module,
v8::Local<v8::Context> context,
v8::Module::ResolveCallback callback) {
bool Module::PrepareInstantiate(
Isolate* isolate, Handle<Module> module, v8::Local<v8::Context> context,
v8::Module::ResolveModuleCallback callback,
DeprecatedResolveCallback callback_without_import_assertions) {
DCHECK_NE(module->status(), kEvaluating);
DCHECK_NE(module->status(), kInstantiating);
if (module->status() >= kPreInstantiating) return true;
......@@ -211,10 +214,11 @@ bool Module::PrepareInstantiate(Isolate* isolate, Handle<Module> module,
if (module->IsSourceTextModule()) {
return SourceTextModule::PrepareInstantiate(
isolate, Handle<SourceTextModule>::cast(module), context, callback);
isolate, Handle<SourceTextModule>::cast(module), context, callback,
callback_without_import_assertions);
} else {
return SyntheticModule::PrepareInstantiate(
isolate, Handle<SyntheticModule>::cast(module), context, callback);
isolate, Handle<SyntheticModule>::cast(module), context);
}
}
......
......@@ -69,13 +69,24 @@ class Module : public HeapObject {
// i.e. has a top-level await.
V8_WARN_UNUSED_RESULT bool IsGraphAsync(Isolate* isolate) const;
// While deprecating v8::ResolveCallback in v8.h we still need to support the
// version of the API that uses it, but we can't directly reference the
// deprecated version because of the enusing build warnings. So, we declare
// this matching typedef for temporary internal use.
// TODO(v8:10958) Delete this typedef and all references to it once
// v8::ResolveCallback is removed.
typedef MaybeLocal<v8::Module> (*DeprecatedResolveCallback)(
Local<v8::Context> context, Local<v8::String> specifier,
Local<v8::Module> referrer);
// Implementation of spec operation ModuleDeclarationInstantiation.
// Returns false if an exception occurred during instantiation, true
// otherwise. (In the case where the callback throws an exception, that
// exception is propagated.)
static V8_WARN_UNUSED_RESULT bool Instantiate(
Isolate* isolate, Handle<Module> module, v8::Local<v8::Context> context,
v8::Module::ResolveCallback callback);
v8::Module::ResolveModuleCallback callback,
DeprecatedResolveCallback callback_without_import_assertions);
// Implementation of spec operation ModuleEvaluation.
static V8_WARN_UNUSED_RESULT MaybeHandle<Object> Evaluate(
......@@ -114,7 +125,8 @@ class Module : public HeapObject {
static V8_WARN_UNUSED_RESULT bool PrepareInstantiate(
Isolate* isolate, Handle<Module> module, v8::Local<v8::Context> context,
v8::Module::ResolveCallback callback);
v8::Module::ResolveModuleCallback callback,
DeprecatedResolveCallback callback_without_import_assertions);
static V8_WARN_UNUSED_RESULT bool FinishInstantiate(
Isolate* isolate, Handle<Module> module,
ZoneForwardList<Handle<SourceTextModule>>* stack, unsigned* dfs_index,
......
......@@ -312,7 +312,9 @@ MaybeHandle<Cell> SourceTextModule::ResolveExportUsingStarExports(
bool SourceTextModule::PrepareInstantiate(
Isolate* isolate, Handle<SourceTextModule> module,
v8::Local<v8::Context> context, v8::Module::ResolveCallback callback) {
v8::Local<v8::Context> context, v8::Module::ResolveModuleCallback callback,
Module::DeprecatedResolveCallback callback_without_import_assertions) {
DCHECK_EQ(callback != nullptr, callback_without_import_assertions == nullptr);
// Obtain requested modules.
Handle<SourceTextModuleInfo> module_info(module->info(), isolate);
Handle<FixedArray> module_requests(module_info->module_requests(), isolate);
......@@ -321,13 +323,25 @@ bool SourceTextModule::PrepareInstantiate(
Handle<ModuleRequest> module_request(
ModuleRequest::cast(module_requests->get(i)), isolate);
Handle<String> specifier(module_request->specifier(), isolate);
// TODO(v8:10958) Pass import assertions to the callback
v8::Local<v8::Module> api_requested_module;
if (!callback(context, v8::Utils::ToLocal(specifier),
v8::Utils::ToLocal(Handle<Module>::cast(module)))
.ToLocal(&api_requested_module)) {
isolate->PromoteScheduledException();
return false;
if (callback) {
Handle<FixedArray> import_assertions(module_request->import_assertions(),
isolate);
if (!callback(context, v8::Utils::ToLocal(specifier),
v8::Utils::FixedArrayToLocal(import_assertions),
v8::Utils::ToLocal(Handle<Module>::cast(module)))
.ToLocal(&api_requested_module)) {
isolate->PromoteScheduledException();
return false;
}
} else {
if (!callback_without_import_assertions(
context, v8::Utils::ToLocal(specifier),
v8::Utils::ToLocal(Handle<Module>::cast(module)))
.ToLocal(&api_requested_module)) {
isolate->PromoteScheduledException();
return false;
}
}
Handle<Module> requested_module = Utils::OpenHandle(*api_requested_module);
requested_modules->set(i, *requested_module);
......@@ -338,7 +352,8 @@ bool SourceTextModule::PrepareInstantiate(
Handle<Module> requested_module(Module::cast(requested_modules->get(i)),
isolate);
if (!Module::PrepareInstantiate(isolate, requested_module, context,
callback)) {
callback,
callback_without_import_assertions)) {
return false;
}
}
......
......@@ -138,7 +138,9 @@ class SourceTextModule
static V8_WARN_UNUSED_RESULT bool PrepareInstantiate(
Isolate* isolate, Handle<SourceTextModule> module,
v8::Local<v8::Context> context, v8::Module::ResolveCallback callback);
v8::Local<v8::Context> context,
v8::Module::ResolveModuleCallback callback,
Module::DeprecatedResolveCallback callback_without_import_assertions);
static V8_WARN_UNUSED_RESULT bool FinishInstantiate(
Isolate* isolate, Handle<SourceTextModule> module,
ZoneForwardList<Handle<SourceTextModule>>* stack, unsigned* dfs_index,
......
......@@ -70,8 +70,7 @@ MaybeHandle<Cell> SyntheticModule::ResolveExport(
// https://heycam.github.io/webidl/#smr-instantiate
bool SyntheticModule::PrepareInstantiate(Isolate* isolate,
Handle<SyntheticModule> module,
v8::Local<v8::Context> context,
v8::Module::ResolveCallback callback) {
v8::Local<v8::Context> context) {
Handle<ObjectHashTable> exports(module->exports(), isolate);
Handle<FixedArray> export_names(module->export_names(), isolate);
// Spec step 7: For each export_name in module->export_names...
......
......@@ -56,7 +56,7 @@ class SyntheticModule
static V8_WARN_UNUSED_RESULT bool PrepareInstantiate(
Isolate* isolate, Handle<SyntheticModule> module,
v8::Local<v8::Context> context, v8::Module::ResolveCallback callback);
v8::Local<v8::Context> context);
static V8_WARN_UNUSED_RESULT bool FinishInstantiate(
Isolate* isolate, Handle<SyntheticModule> module);
......
......@@ -82,6 +82,7 @@ using ::v8::Boolean;
using ::v8::BooleanObject;
using ::v8::Context;
using ::v8::Extension;
using ::v8::FixedArray;
using ::v8::Function;
using ::v8::FunctionTemplate;
using ::v8::HandleScope;
......@@ -23525,6 +23526,11 @@ class TestSourceStream : public v8::ScriptCompiler::ExternalSourceStream {
unsigned index_;
};
v8::MaybeLocal<Module> UnexpectedModuleResolveCallback(
Local<Context> context, Local<String> specifier,
Local<FixedArray> import_assertions, Local<Module> referrer) {
CHECK_WITH_MSG(false, "Unexpected call to resolve callback");
}
// Helper function for running streaming tests.
void RunStreamingTest(const char** chunks, i::ScriptType type,
......@@ -23577,7 +23583,10 @@ void RunStreamingTest(const char** chunks, i::ScriptType type,
env.local(), &source, v8_str(full_source), origin);
if (expected_success) {
v8::Local<v8::Module> module = maybe_module.ToLocalChecked();
CHECK(module->InstantiateModule(env.local(), nullptr).FromJust());
CHECK(
module
->InstantiateModule(env.local(), UnexpectedModuleResolveCallback)
.FromJust());
CHECK_EQ(Module::kInstantiated, module->GetStatus());
v8::Local<Value> result = module->Evaluate(env.local()).ToLocalChecked();
CHECK_EQ(Module::kEvaluated, module->GetStatus());
......@@ -24038,12 +24047,6 @@ TEST(CodeCache) {
isolate2->Dispose();
}
v8::MaybeLocal<Module> UnexpectedModuleResolveCallback(Local<Context> context,
Local<String> specifier,
Local<Module> referrer) {
CHECK_WITH_MSG(false, "Unexpected call to resolve callback");
}
v8::MaybeLocal<Value> UnexpectedSyntheticModuleEvaluationStepsCallback(
Local<Context> context, Local<Module> module) {
CHECK_WITH_MSG(false, "Unexpected call to synthetic module re callback");
......@@ -24126,9 +24129,9 @@ Local<Module> CompileAndInstantiateModuleFromCache(
} // namespace
v8::MaybeLocal<Module> SyntheticModuleResolveCallback(Local<Context> context,
Local<String> specifier,
Local<Module> referrer) {
v8::MaybeLocal<Module> SyntheticModuleResolveCallback(
Local<Context> context, Local<String> specifier,
Local<FixedArray> import_assertions, Local<Module> referrer) {
std::vector<v8::Local<v8::String>> export_names{v8_str("test_export")};
Local<Module> module = CreateAndInstantiateSyntheticModule(
context->GetIsolate(),
......@@ -24138,7 +24141,8 @@ v8::MaybeLocal<Module> SyntheticModuleResolveCallback(Local<Context> context,
}
v8::MaybeLocal<Module> SyntheticModuleThatThrowsDuringEvaluateResolveCallback(
Local<Context> context, Local<String> specifier, Local<Module> referrer) {
Local<Context> context, Local<String> specifier,
Local<FixedArray> import_assertions, Local<Module> referrer) {
std::vector<v8::Local<v8::String>> export_names{v8_str("test_export")};
Local<Module> module = CreateAndInstantiateSyntheticModule(
context->GetIsolate(),
......@@ -35,7 +35,9 @@ static Local<Module> dep1;
static Local<Module> dep2;
MaybeLocal<Module> ResolveCallback(Local<Context> context,
Local<String> specifier,
Local<FixedArray> import_assertions,
Local<Module> referrer) {
CHECK_EQ(0, import_assertions->Length());
Isolate* isolate = CcTest::isolate();
if (specifier->StrictEquals(v8_str("./dep1.js"))) {
return dep1;
......@@ -131,7 +133,46 @@ TEST(ModuleInstantiationFailures1) {
i::FLAG_harmony_top_level_await = prev_top_level_await;
}
TEST(ModuleInstantiationFailures1WithImportAssertions) {
static Local<Module> fooModule;
static Local<Module> barModule;
MaybeLocal<Module> ResolveCallbackWithImportAssertions(
Local<Context> context, Local<String> specifier,
Local<FixedArray> import_assertions, Local<Module> referrer) {
Isolate* isolate = CcTest::isolate();
LocalContext env;
if (specifier->StrictEquals(v8_str("./foo.js"))) {
CHECK_EQ(0, import_assertions->Length());
return fooModule;
} else if (specifier->StrictEquals(v8_str("./bar.js"))) {
CHECK_EQ(3, import_assertions->Length());
Local<String> assertion_key =
import_assertions->Get(env.local(), 0).As<Value>().As<String>();
CHECK(v8_str("a")->StrictEquals(assertion_key));
Local<String> assertion_value =
import_assertions->Get(env.local(), 1).As<Value>().As<String>();
CHECK(v8_str("b")->StrictEquals(assertion_value));
Local<Data> assertion_source_offset_object =
import_assertions->Get(env.local(), 2);
Local<Int32> assertion_source_offset_int32 =
assertion_source_offset_object.As<Value>()
->ToInt32(context)
.ToLocalChecked();
int32_t assertion_source_offset = assertion_source_offset_int32->Value();
CHECK_EQ(65, assertion_source_offset);
Location loc = referrer->SourceOffsetToLocation(assertion_source_offset);
CHECK_EQ(1, loc.GetLineNumber());
CHECK_EQ(35, loc.GetColumnNumber());
return barModule;
} else {
isolate->ThrowException(v8_str("boom"));
return MaybeLocal<Module>();
}
}
TEST(ModuleInstantiationWithImportAssertions) {
bool prev_top_level_await = i::FLAG_harmony_top_level_await;
bool prev_import_assertions = i::FLAG_harmony_import_assertions;
i::FLAG_harmony_import_assertions = true;
......@@ -194,16 +235,40 @@ TEST(ModuleInstantiationFailures1WithImportAssertions) {
CHECK_EQ(35, loc.GetColumnNumber());
}
// Instantiation should fail.
// foo.js
{
v8::TryCatch inner_try_catch(isolate);
CHECK(
module->InstantiateModule(env.local(), ResolveCallback).IsNothing());
CHECK(inner_try_catch.HasCaught());
CHECK(inner_try_catch.Exception()->StrictEquals(v8_str("boom")));
CHECK_EQ(Module::kUninstantiated, module->GetStatus());
Local<String> source_text = v8_str("Object.expando = 40");
ScriptOrigin origin = ModuleOrigin(v8_str("foo.js"), CcTest::isolate());
ScriptCompiler::Source source(source_text, origin);
fooModule =
ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
}
// bar.js
{
Local<String> source_text = v8_str("Object.expando += 2");
ScriptOrigin origin = ModuleOrigin(v8_str("bar.js"), CcTest::isolate());
ScriptCompiler::Source source(source_text, origin);
barModule =
ScriptCompiler::CompileModule(isolate, &source).ToLocalChecked();
}
CHECK(module
->InstantiateModule(env.local(),
ResolveCallbackWithImportAssertions)
.FromJust());
CHECK_EQ(Module::kInstantiated, module->GetStatus());
MaybeLocal<Value> result = module->Evaluate(env.local());
CHECK_EQ(Module::kEvaluated, module->GetStatus());
if (i::FLAG_harmony_top_level_await) {
Local<Promise> promise = Local<Promise>::Cast(result.ToLocalChecked());
CHECK_EQ(promise->State(), v8::Promise::kFulfilled);
CHECK(promise->Result()->IsUndefined());
} else {
CHECK(!result.IsEmpty());
ExpectInt32("Object.expando", 42);
}
CHECK(!try_catch.HasCaught());
}
i::FLAG_harmony_top_level_await = prev_top_level_await;
......@@ -296,7 +361,9 @@ TEST(ModuleInstantiationFailures2) {
}
static MaybeLocal<Module> CompileSpecifierAsModuleResolveCallback(
Local<Context> context, Local<String> specifier, Local<Module> referrer) {
Local<Context> context, Local<String> specifier,
Local<FixedArray> import_assertions, Local<Module> referrer) {
CHECK_EQ(0, import_assertions->Length());
ScriptOrigin origin = ModuleOrigin(v8_str("module.js"), CcTest::isolate());
ScriptCompiler::Source source(specifier, origin);
return ScriptCompiler::CompileModule(CcTest::isolate(), &source)
......@@ -417,7 +484,9 @@ TEST(ModuleEvaluationError1) {
static Local<Module> failure_module;
static Local<Module> dependent_module;
MaybeLocal<Module> ResolveCallbackForModuleEvaluationError2(
Local<Context> context, Local<String> specifier, Local<Module> referrer) {
Local<Context> context, Local<String> specifier,
Local<FixedArray> import_assertions, Local<Module> referrer) {
CHECK_EQ(0, import_assertions->Length());
if (specifier->StrictEquals(v8_str("./failure.js"))) {
return failure_module;
} else {
......@@ -1087,7 +1156,9 @@ static Local<Module> cycle_self_module;
static Local<Module> cycle_one_module;
static Local<Module> cycle_two_module;
MaybeLocal<Module> ResolveCallbackForIsGraphAsyncTopLevelAwait(
Local<Context> context, Local<String> specifier, Local<Module> referrer) {
Local<Context> context, Local<String> specifier,
Local<FixedArray> import_assertions, Local<Module> referrer) {
CHECK_EQ(0, import_assertions->Length());
if (specifier->StrictEquals(v8_str("./async_leaf.js"))) {
return async_leaf_module;
} else if (specifier->StrictEquals(v8_str("./sync_leaf.js"))) {
......
......@@ -129,7 +129,9 @@ void IsolateData::RegisterModule(v8::Local<v8::Context> context,
// static
v8::MaybeLocal<v8::Module> IsolateData::ModuleResolveCallback(
v8::Local<v8::Context> context, v8::Local<v8::String> specifier,
v8::Local<v8::FixedArray> import_assertions,
v8::Local<v8::Module> referrer) {
// TODO(v8:11189) Consider JSON modules support in the InspectorClient
IsolateData* data = IsolateData::FromContext(context);
std::string str = *v8::String::Utf8Value(data->isolate(), specifier);
return data->modules_[ToVector(data->isolate(), specifier)].Get(
......
......@@ -100,6 +100,7 @@ class IsolateData : public v8_inspector::V8InspectorClient {
private:
static v8::MaybeLocal<v8::Module> ModuleResolveCallback(
v8::Local<v8::Context> context, v8::Local<v8::String> specifier,
v8::Local<v8::FixedArray> import_assertions,
v8::Local<v8::Module> referrer);
static void MessageHandler(v8::Local<v8::Message> message,
v8::Local<v8::Value> exception);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment