mirror of
https://github.com/denoland/rusty_v8.git
synced 2024-12-23 15:50:11 -05:00
Add FunctionBuilder (#512)
v8::Function and v8::FunctionTemplate have a lot of options that can be configured. The builder() associated functions on those types help with that.
This commit is contained in:
parent
530c876aff
commit
f37601cab2
4 changed files with 223 additions and 49 deletions
|
@ -7,6 +7,7 @@
|
|||
#include "v8/include/v8-inspector.h"
|
||||
#include "v8/include/v8-platform.h"
|
||||
#include "v8/include/v8-profiler.h"
|
||||
#include "v8/include/v8-fast-api-calls.h"
|
||||
#include "v8/include/v8.h"
|
||||
#include "v8/src/execution/isolate-utils-inl.h"
|
||||
#include "v8/src/execution/isolate-utils.h"
|
||||
|
@ -56,6 +57,9 @@ static_assert(sizeof(v8::Location) == sizeof(size_t) * 1,
|
|||
static_assert(sizeof(v8::SnapshotCreator) == sizeof(size_t) * 1,
|
||||
"SnapshotCreator size mismatch");
|
||||
|
||||
static_assert(sizeof(v8::CFunction) == sizeof(size_t) * 2,
|
||||
"CFunction size mismatch");
|
||||
|
||||
static_assert(sizeof(three_pointers_t) == sizeof(v8_inspector::StringView),
|
||||
"StringView size mismatch");
|
||||
|
||||
|
@ -1268,12 +1272,17 @@ const v8::StackTrace* v8__Exception__GetStackTrace(const v8::Value& exception) {
|
|||
return local_to_ptr(v8::Exception::GetStackTrace(ptr_to_local(&exception)));
|
||||
}
|
||||
|
||||
const v8::Function* v8__Function__New(const v8::Context& context,
|
||||
v8::FunctionCallback callback,
|
||||
const v8::Value* maybe_data) {
|
||||
const v8::Function* v8__Function__New(
|
||||
const v8::Context& context,
|
||||
v8::FunctionCallback callback,
|
||||
const v8::Value* data_or_null,
|
||||
int length,
|
||||
v8::ConstructorBehavior constructor_behavior,
|
||||
v8::SideEffectType side_effect_type) {
|
||||
return maybe_local_to_ptr(
|
||||
v8::Function::New(ptr_to_local(&context), callback,
|
||||
ptr_to_local(maybe_data)));
|
||||
ptr_to_local(data_or_null), length,
|
||||
constructor_behavior, side_effect_type));
|
||||
}
|
||||
|
||||
const v8::Value* v8__Function__Call(const v8::Function& self,
|
||||
|
@ -1294,8 +1303,20 @@ const v8::Object* v8__Function__NewInstance(const v8::Function& self,
|
|||
}
|
||||
|
||||
const v8::FunctionTemplate* v8__FunctionTemplate__New(
|
||||
v8::Isolate* isolate, v8::FunctionCallback callback = nullptr) {
|
||||
return local_to_ptr(v8::FunctionTemplate::New(isolate, callback));
|
||||
v8::Isolate* isolate,
|
||||
v8::FunctionCallback callback,
|
||||
const v8::Value* data_or_null,
|
||||
const v8::Signature* signature_or_null,
|
||||
int length,
|
||||
v8::ConstructorBehavior constructor_behavior,
|
||||
v8::SideEffectType side_effect_type,
|
||||
const v8::CFunction* c_function_or_null) {
|
||||
return local_to_ptr(
|
||||
v8::FunctionTemplate::New(isolate, callback,
|
||||
ptr_to_local(data_or_null),
|
||||
ptr_to_local(signature_or_null), length,
|
||||
constructor_behavior, side_effect_type,
|
||||
c_function_or_null));
|
||||
}
|
||||
|
||||
const v8::Function* v8__FunctionTemplate__GetFunction(
|
||||
|
|
149
src/function.rs
149
src/function.rs
|
@ -14,13 +14,17 @@ use crate::HandleScope;
|
|||
use crate::Local;
|
||||
use crate::Name;
|
||||
use crate::Object;
|
||||
use crate::Signature;
|
||||
use crate::Value;
|
||||
|
||||
extern "C" {
|
||||
fn v8__Function__New(
|
||||
context: *const Context,
|
||||
callback: FunctionCallback,
|
||||
data: *const Value,
|
||||
data_or_null: *const Value,
|
||||
length: i32,
|
||||
constructor_behavior: ConstructorBehavior,
|
||||
side_effect_type: SideEffectType,
|
||||
) -> *const Function;
|
||||
fn v8__Function__Call(
|
||||
this: *const Function,
|
||||
|
@ -63,6 +67,38 @@ extern "C" {
|
|||
fn v8__ReturnValue__Get(this: *const ReturnValue) -> *const Value;
|
||||
}
|
||||
|
||||
// Ad-libbed - V8 does not document ConstructorBehavior.
|
||||
/// ConstructorBehavior::Allow creates a regular API function.
|
||||
///
|
||||
/// ConstructorBehavior::Throw creates a "concise" API function, a function
|
||||
/// without a ".prototype" property, that is somewhat faster to create and has
|
||||
/// a smaller footprint. Functionally equivalent to ConstructorBehavior::Allow
|
||||
/// followed by a call to FunctionTemplate::RemovePrototype().
|
||||
#[repr(C)]
|
||||
pub enum ConstructorBehavior {
|
||||
Throw,
|
||||
Allow,
|
||||
}
|
||||
|
||||
/// Options for marking whether callbacks may trigger JS-observable side
|
||||
/// effects. Side-effect-free callbacks are allowlisted during debug evaluation
|
||||
/// with throwOnSideEffect. It applies when calling a Function,
|
||||
/// FunctionTemplate, or an Accessor callback. For Interceptors, please see
|
||||
/// PropertyHandlerFlags's kHasNoSideEffect.
|
||||
/// Callbacks that only cause side effects to the receiver are allowlisted if
|
||||
/// invoked on receiver objects that are created within the same debug-evaluate
|
||||
/// call, as these objects are temporary and the side effect does not escape.
|
||||
#[repr(C)]
|
||||
pub enum SideEffectType {
|
||||
HasSideEffect,
|
||||
HasNoSideEffect,
|
||||
HasSideEffectToReceiver,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
pub(crate) struct CFunction([usize; 2]);
|
||||
|
||||
// Note: the 'cb lifetime is required because the ReturnValue object must not
|
||||
// outlive the FunctionCallbackInfo/PropertyCallbackInfo object from which it
|
||||
// is derived.
|
||||
|
@ -286,41 +322,96 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// A builder to construct the properties of a Function or FunctionTemplate.
|
||||
pub struct FunctionBuilder<'s, T> {
|
||||
pub(crate) callback: FunctionCallback,
|
||||
pub(crate) data: Option<Local<'s, Value>>,
|
||||
pub(crate) signature: Option<Local<'s, Signature>>,
|
||||
pub(crate) length: i32,
|
||||
pub(crate) constructor_behavior: ConstructorBehavior,
|
||||
pub(crate) side_effect_type: SideEffectType,
|
||||
phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'s, T> FunctionBuilder<'s, T> {
|
||||
/// Create a new FunctionBuilder.
|
||||
pub fn new(callback: impl MapFnTo<FunctionCallback>) -> Self {
|
||||
Self {
|
||||
callback: callback.map_fn_to(),
|
||||
data: None,
|
||||
signature: None,
|
||||
length: 0,
|
||||
constructor_behavior: ConstructorBehavior::Allow,
|
||||
side_effect_type: SideEffectType::HasSideEffect,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the associated data. The default is no associated data.
|
||||
pub fn data(mut self, data: Local<'s, Value>) -> Self {
|
||||
self.data = Some(data);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the function length. The default is 0.
|
||||
pub fn length(mut self, length: i32) -> Self {
|
||||
self.length = length;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the constructor behavior. The default is ConstructorBehavior::Allow.
|
||||
pub fn constructor_behavior(
|
||||
mut self,
|
||||
constructor_behavior: ConstructorBehavior,
|
||||
) -> Self {
|
||||
self.constructor_behavior = constructor_behavior;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the side effect type. The default is SideEffectType::HasSideEffect.
|
||||
pub fn side_effect_type(mut self, side_effect_type: SideEffectType) -> Self {
|
||||
self.side_effect_type = side_effect_type;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> FunctionBuilder<'s, Function> {
|
||||
/// Create the function in the current execution context.
|
||||
pub fn build(
|
||||
self,
|
||||
scope: &mut HandleScope<'s>,
|
||||
) -> Option<Local<'s, Function>> {
|
||||
unsafe {
|
||||
scope.cast_local(|sd| {
|
||||
v8__Function__New(
|
||||
sd.get_current_context(),
|
||||
self.callback,
|
||||
self.data.map_or_else(null, |p| &*p),
|
||||
self.length,
|
||||
self.constructor_behavior,
|
||||
self.side_effect_type,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Function {
|
||||
// TODO: add remaining arguments from C++
|
||||
/// Create a FunctionBuilder to configure a Function.
|
||||
/// This is the same as FunctionBuilder::<Function>::new().
|
||||
pub fn builder<'s>(
|
||||
callback: impl MapFnTo<FunctionCallback>,
|
||||
) -> FunctionBuilder<'s, Self> {
|
||||
FunctionBuilder::new(callback)
|
||||
}
|
||||
|
||||
/// Create a function in the current execution context
|
||||
/// for a given FunctionCallback.
|
||||
pub fn new<'s>(
|
||||
scope: &mut HandleScope<'s>,
|
||||
callback: impl MapFnTo<FunctionCallback>,
|
||||
) -> Option<Local<'s, Function>> {
|
||||
unsafe {
|
||||
scope.cast_local(|sd| {
|
||||
v8__Function__New(
|
||||
sd.get_current_context(),
|
||||
callback.map_fn_to(),
|
||||
null(),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a function in the current execution context
|
||||
/// for a given FunctionCallback and associated data.
|
||||
pub fn new_with_data<'s>(
|
||||
scope: &mut HandleScope<'s>,
|
||||
data: Local<Value>,
|
||||
callback: impl MapFnTo<FunctionCallback>,
|
||||
) -> Option<Local<'s, Function>> {
|
||||
unsafe {
|
||||
scope.cast_local(|sd| {
|
||||
v8__Function__New(
|
||||
sd.get_current_context(),
|
||||
callback.map_fn_to(),
|
||||
&*data,
|
||||
)
|
||||
})
|
||||
}
|
||||
Self::builder(callback).build(scope)
|
||||
}
|
||||
|
||||
pub fn call<'s>(
|
||||
|
|
|
@ -6,16 +6,23 @@ use crate::data::Template;
|
|||
use crate::isolate::Isolate;
|
||||
use crate::support::int;
|
||||
use crate::support::MapFnTo;
|
||||
use crate::CFunction;
|
||||
use crate::ConstructorBehavior;
|
||||
use crate::Context;
|
||||
use crate::Function;
|
||||
use crate::FunctionBuilder;
|
||||
use crate::FunctionCallback;
|
||||
use crate::HandleScope;
|
||||
use crate::Local;
|
||||
use crate::Object;
|
||||
use crate::PropertyAttribute;
|
||||
use crate::SideEffectType;
|
||||
use crate::Signature;
|
||||
use crate::String;
|
||||
use crate::Value;
|
||||
use crate::NONE;
|
||||
use std::convert::TryFrom;
|
||||
use std::ptr::null;
|
||||
|
||||
extern "C" {
|
||||
fn v8__Template__Set(
|
||||
|
@ -28,6 +35,12 @@ extern "C" {
|
|||
fn v8__FunctionTemplate__New(
|
||||
isolate: *mut Isolate,
|
||||
callback: FunctionCallback,
|
||||
data_or_null: *const Value,
|
||||
signature_or_null: *const Signature,
|
||||
length: i32,
|
||||
constructor_behavior: ConstructorBehavior,
|
||||
side_effect_type: SideEffectType,
|
||||
c_function_or_null: *const CFunction,
|
||||
) -> *const FunctionTemplate;
|
||||
fn v8__FunctionTemplate__GetFunction(
|
||||
this: *const FunctionTemplate,
|
||||
|
@ -72,18 +85,51 @@ impl Template {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'s> FunctionBuilder<'s, FunctionTemplate> {
|
||||
/// Set the function call signature. The default is no signature.
|
||||
pub fn signature(mut self, signature: Local<'s, Signature>) -> Self {
|
||||
self.signature = Some(signature);
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates the function template.
|
||||
pub fn build(
|
||||
self,
|
||||
scope: &mut HandleScope<'s, ()>,
|
||||
) -> Local<'s, FunctionTemplate> {
|
||||
unsafe {
|
||||
scope.cast_local(|sd| {
|
||||
v8__FunctionTemplate__New(
|
||||
sd.get_isolate_ptr(),
|
||||
self.callback,
|
||||
self.data.map_or_else(null, |p| &*p),
|
||||
self.signature.map_or_else(null, |p| &*p),
|
||||
self.length,
|
||||
self.constructor_behavior,
|
||||
self.side_effect_type,
|
||||
null(),
|
||||
)
|
||||
})
|
||||
}
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionTemplate {
|
||||
/// Create a FunctionBuilder to configure a FunctionTemplate.
|
||||
/// This is the same as FunctionBuilder::<FunctionTemplate>::new().
|
||||
pub fn builder<'s>(
|
||||
callback: impl MapFnTo<FunctionCallback>,
|
||||
) -> FunctionBuilder<'s, Self> {
|
||||
FunctionBuilder::new(callback)
|
||||
}
|
||||
|
||||
/// Creates a function template.
|
||||
pub fn new<'s>(
|
||||
scope: &mut HandleScope<'s, ()>,
|
||||
callback: impl MapFnTo<FunctionCallback>,
|
||||
) -> Local<'s, FunctionTemplate> {
|
||||
unsafe {
|
||||
scope.cast_local(|sd| {
|
||||
v8__FunctionTemplate__New(sd.get_isolate_ptr(), callback.map_fn_to())
|
||||
})
|
||||
}
|
||||
.unwrap()
|
||||
Self::builder(callback).build(scope)
|
||||
}
|
||||
|
||||
/// Returns the unique function instance in the current execution context.
|
||||
|
|
|
@ -1637,6 +1637,7 @@ fn function() {
|
|||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let global = context.global(scope);
|
||||
let recv: v8::Local<v8::Value> = global.into();
|
||||
|
||||
// create function using template
|
||||
let fn_template = v8::FunctionTemplate::new(scope, fn_callback);
|
||||
let function = fn_template
|
||||
|
@ -1648,6 +1649,7 @@ fn function() {
|
|||
function
|
||||
.call(scope, recv, &[])
|
||||
.expect("Function call failed");
|
||||
|
||||
// create function without a template
|
||||
let function = v8::Function::new(scope, fn_callback2)
|
||||
.expect("Unable to create function");
|
||||
|
@ -1656,19 +1658,33 @@ fn function() {
|
|||
let value = function
|
||||
.call(scope, recv, &[arg1.into(), arg2.into()])
|
||||
.unwrap();
|
||||
let value_str = value.to_rust_string_lossy(scope);
|
||||
assert_eq!(value_str, "Hello callback!".to_string());
|
||||
let value_str = value.to_string(scope).unwrap();
|
||||
let rust_str = value_str.to_rust_string_lossy(scope);
|
||||
assert_eq!(rust_str, "Hello callback!".to_string());
|
||||
|
||||
// create a function with associated data
|
||||
let true_data = v8::Boolean::new(scope, true);
|
||||
let function = v8::Function::new_with_data(
|
||||
scope,
|
||||
true_data.into(),
|
||||
data_is_true_callback,
|
||||
)
|
||||
.expect("Unable to create function with data");
|
||||
let function = v8::Function::builder(data_is_true_callback)
|
||||
.data(true_data.into())
|
||||
.build(scope)
|
||||
.expect("Unable to create function with data");
|
||||
function
|
||||
.call(scope, recv, &[])
|
||||
.expect("Function call failed");
|
||||
|
||||
// create a prototype-less function that throws on new
|
||||
let function = v8::Function::builder(fn_callback)
|
||||
.length(42)
|
||||
.constructor_behavior(v8::ConstructorBehavior::Throw)
|
||||
.build(scope)
|
||||
.unwrap();
|
||||
let name = v8::String::new(scope, "f").unwrap();
|
||||
global.set(scope, name.into(), function.into()).unwrap();
|
||||
let result = eval(scope, "f.length").unwrap();
|
||||
assert_eq!(42, result.integer_value(scope).unwrap());
|
||||
let result = eval(scope, "f.prototype").unwrap();
|
||||
assert!(result.is_undefined());
|
||||
assert!(eval(scope, "new f()").is_none()); // throws
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue