mirror of
https://github.com/servo/servo.git
synced 2025-07-23 07:13:52 +01:00
Add a permanent root to WebIDL callbacks, ensuring they are always safe to store.
This commit is contained in:
parent
550df86ac8
commit
3f35c3eee2
4 changed files with 75 additions and 28 deletions
|
@ -5,16 +5,16 @@
|
||||||
//! Base classes to work with IDL callbacks.
|
//! Base classes to work with IDL callbacks.
|
||||||
|
|
||||||
use dom::bindings::error::{Error, Fallible, report_pending_exception};
|
use dom::bindings::error::{Error, Fallible, report_pending_exception};
|
||||||
use dom::bindings::js::Root;
|
use dom::bindings::js::{Root, MutHeapJSVal};
|
||||||
use dom::bindings::reflector::DomObject;
|
use dom::bindings::reflector::DomObject;
|
||||||
use dom::bindings::settings_stack::AutoEntryScript;
|
use dom::bindings::settings_stack::AutoEntryScript;
|
||||||
use dom::globalscope::GlobalScope;
|
use dom::globalscope::GlobalScope;
|
||||||
use js::jsapi::{Heap, MutableHandleObject};
|
use js::jsapi::{Heap, MutableHandleObject};
|
||||||
use js::jsapi::{IsCallable, JSContext, JSObject, JS_WrapObject};
|
use js::jsapi::{IsCallable, JSContext, JSObject, JS_WrapObject, AddRawValueRoot};
|
||||||
use js::jsapi::{JSCompartment, JS_EnterCompartment, JS_LeaveCompartment};
|
use js::jsapi::{JSCompartment, JS_EnterCompartment, JS_LeaveCompartment, RemoveRawValueRoot};
|
||||||
use js::jsapi::JSAutoCompartment;
|
use js::jsapi::JSAutoCompartment;
|
||||||
use js::jsapi::JS_GetProperty;
|
use js::jsapi::JS_GetProperty;
|
||||||
use js::jsval::{JSVal, UndefinedValue};
|
use js::jsval::{JSVal, UndefinedValue, ObjectValue};
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::mem::drop;
|
use std::mem::drop;
|
||||||
|
@ -33,22 +33,52 @@ pub enum ExceptionHandling {
|
||||||
|
|
||||||
/// A common base class for representing IDL callback function and
|
/// A common base class for representing IDL callback function and
|
||||||
/// callback interface types.
|
/// callback interface types.
|
||||||
#[derive(Default, JSTraceable)]
|
#[derive(JSTraceable)]
|
||||||
|
#[must_root]
|
||||||
pub struct CallbackObject {
|
pub struct CallbackObject {
|
||||||
/// The underlying `JSObject`.
|
/// The underlying `JSObject`.
|
||||||
callback: Heap<*mut JSObject>,
|
callback: Heap<*mut JSObject>,
|
||||||
|
permanent_js_root: MutHeapJSVal,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CallbackObject {
|
||||||
|
#[allow(unrooted_must_root)]
|
||||||
|
fn default() -> CallbackObject {
|
||||||
|
CallbackObject::new()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CallbackObject {
|
impl CallbackObject {
|
||||||
|
#[allow(unrooted_must_root)]
|
||||||
fn new() -> CallbackObject {
|
fn new() -> CallbackObject {
|
||||||
CallbackObject {
|
CallbackObject {
|
||||||
callback: Heap::default(),
|
callback: Heap::default(),
|
||||||
|
permanent_js_root: MutHeapJSVal::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self) -> *mut JSObject {
|
pub fn get(&self) -> *mut JSObject {
|
||||||
self.callback.get()
|
self.callback.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
unsafe fn init(&mut self, cx: *mut JSContext, callback: *mut JSObject) {
|
||||||
|
self.callback.set(callback);
|
||||||
|
self.permanent_js_root.set(ObjectValue(callback));
|
||||||
|
assert!(AddRawValueRoot(cx, self.permanent_js_root.get_unsafe(),
|
||||||
|
b"CallbackObject::root\n" as *const _ as *const _));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for CallbackObject {
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
let cx = GlobalScope::from_object(self.callback.get()).get_cx();
|
||||||
|
RemoveRawValueRoot(cx, self.permanent_js_root.get_unsafe());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for CallbackObject {
|
impl PartialEq for CallbackObject {
|
||||||
|
@ -62,7 +92,7 @@ impl PartialEq for CallbackObject {
|
||||||
/// callback interface types.
|
/// callback interface types.
|
||||||
pub trait CallbackContainer {
|
pub trait CallbackContainer {
|
||||||
/// Create a new CallbackContainer object for the given `JSObject`.
|
/// Create a new CallbackContainer object for the given `JSObject`.
|
||||||
fn new(callback: *mut JSObject) -> Rc<Self>;
|
unsafe fn new(cx: *mut JSContext, callback: *mut JSObject) -> Rc<Self>;
|
||||||
/// Returns the underlying `CallbackObject`.
|
/// Returns the underlying `CallbackObject`.
|
||||||
fn callback_holder(&self) -> &CallbackObject;
|
fn callback_holder(&self) -> &CallbackObject;
|
||||||
/// Returns the underlying `JSObject`.
|
/// Returns the underlying `JSObject`.
|
||||||
|
@ -74,12 +104,14 @@ pub trait CallbackContainer {
|
||||||
|
|
||||||
/// A common base class for representing IDL callback function types.
|
/// A common base class for representing IDL callback function types.
|
||||||
#[derive(JSTraceable, PartialEq)]
|
#[derive(JSTraceable, PartialEq)]
|
||||||
|
#[must_root]
|
||||||
pub struct CallbackFunction {
|
pub struct CallbackFunction {
|
||||||
object: CallbackObject,
|
object: CallbackObject,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CallbackFunction {
|
impl CallbackFunction {
|
||||||
/// Create a new `CallbackFunction` for this object.
|
/// Create a new `CallbackFunction` for this object.
|
||||||
|
#[allow(unrooted_must_root)]
|
||||||
pub fn new() -> CallbackFunction {
|
pub fn new() -> CallbackFunction {
|
||||||
CallbackFunction {
|
CallbackFunction {
|
||||||
object: CallbackObject::new(),
|
object: CallbackObject::new(),
|
||||||
|
@ -93,14 +125,17 @@ impl CallbackFunction {
|
||||||
|
|
||||||
/// Initialize the callback function with a value.
|
/// Initialize the callback function with a value.
|
||||||
/// Should be called once this object is done moving.
|
/// Should be called once this object is done moving.
|
||||||
pub fn init(&mut self, callback: *mut JSObject) {
|
pub unsafe fn init(&mut self, cx: *mut JSContext, callback: *mut JSObject) {
|
||||||
self.object.callback.set(callback);
|
self.object.init(cx, callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// A common base class for representing IDL callback interface types.
|
/// A common base class for representing IDL callback interface types.
|
||||||
#[derive(JSTraceable, PartialEq)]
|
#[derive(JSTraceable, PartialEq)]
|
||||||
|
#[must_root]
|
||||||
pub struct CallbackInterface {
|
pub struct CallbackInterface {
|
||||||
object: CallbackObject,
|
object: CallbackObject,
|
||||||
}
|
}
|
||||||
|
@ -120,8 +155,8 @@ impl CallbackInterface {
|
||||||
|
|
||||||
/// Initialize the callback function with a value.
|
/// Initialize the callback function with a value.
|
||||||
/// Should be called once this object is done moving.
|
/// Should be called once this object is done moving.
|
||||||
pub fn init(&mut self, callback: *mut JSObject) {
|
pub unsafe fn init(&mut self, cx: *mut JSContext, callback: *mut JSObject) {
|
||||||
self.object.callback.set(callback);
|
self.object.init(cx, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the property with the given `name`, if it is a callable object,
|
/// Returns the property with the given `name`, if it is a callable object,
|
||||||
|
|
|
@ -776,7 +776,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
|
||||||
if descriptor.interface.isCallback():
|
if descriptor.interface.isCallback():
|
||||||
name = descriptor.nativeType
|
name = descriptor.nativeType
|
||||||
declType = CGWrapper(CGGeneric(name), pre="Rc<", post=">")
|
declType = CGWrapper(CGGeneric(name), pre="Rc<", post=">")
|
||||||
template = "%s::new(${val}.get().to_object())" % name
|
template = "%s::new(cx, ${val}.get().to_object())" % name
|
||||||
if type.nullable():
|
if type.nullable():
|
||||||
declType = CGWrapper(declType, pre="Option<", post=">")
|
declType = CGWrapper(declType, pre="Option<", post=">")
|
||||||
template = wrapObjectTemplate("Some(%s)" % template, "None",
|
template = wrapObjectTemplate("Some(%s)" % template, "None",
|
||||||
|
@ -2195,7 +2195,7 @@ class CGGeneric(CGThing):
|
||||||
|
|
||||||
class CGCallbackTempRoot(CGGeneric):
|
class CGCallbackTempRoot(CGGeneric):
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
CGGeneric.__init__(self, "%s::new(${val}.get().to_object())" % name)
|
CGGeneric.__init__(self, "%s::new(cx, ${val}.get().to_object())" % name)
|
||||||
|
|
||||||
|
|
||||||
def getAllTypes(descriptors, dictionaries, callbacks, typedefs):
|
def getAllTypes(descriptors, dictionaries, callbacks, typedefs):
|
||||||
|
@ -4444,10 +4444,11 @@ class ClassConstructor(ClassItem):
|
||||||
"});\n"
|
"});\n"
|
||||||
"// Note: callback cannot be moved after calling init.\n"
|
"// Note: callback cannot be moved after calling init.\n"
|
||||||
"match Rc::get_mut(&mut ret) {\n"
|
"match Rc::get_mut(&mut ret) {\n"
|
||||||
" Some(ref mut callback) => callback.parent.init(%s),\n"
|
" Some(ref mut callback) => unsafe { callback.parent.init(%s, %s) },\n"
|
||||||
" None => unreachable!(),\n"
|
" None => unreachable!(),\n"
|
||||||
"};\n"
|
"};\n"
|
||||||
"ret") % (cgClass.name, '\n'.join(initializers), self.args[0].name))
|
"ret") % (cgClass.name, '\n'.join(initializers),
|
||||||
|
self.args[0].name, self.args[1].name))
|
||||||
|
|
||||||
def declare(self, cgClass):
|
def declare(self, cgClass):
|
||||||
args = ', '.join([a.declare() for a in self.args])
|
args = ', '.join([a.declare() for a in self.args])
|
||||||
|
@ -6236,11 +6237,11 @@ class CGCallback(CGClass):
|
||||||
bases=[ClassBase(baseName)],
|
bases=[ClassBase(baseName)],
|
||||||
constructors=self.getConstructors(),
|
constructors=self.getConstructors(),
|
||||||
methods=realMethods + getters + setters,
|
methods=realMethods + getters + setters,
|
||||||
decorators="#[derive(JSTraceable, PartialEq)]")
|
decorators="#[derive(JSTraceable, PartialEq)]\n#[allow_unrooted_interior]")
|
||||||
|
|
||||||
def getConstructors(self):
|
def getConstructors(self):
|
||||||
return [ClassConstructor(
|
return [ClassConstructor(
|
||||||
[Argument("*mut JSObject", "aCallback")],
|
[Argument("*mut JSContext", "aCx"), Argument("*mut JSObject", "aCallback")],
|
||||||
bodyInHeader=True,
|
bodyInHeader=True,
|
||||||
visibility="pub",
|
visibility="pub",
|
||||||
explicit=False,
|
explicit=False,
|
||||||
|
@ -6336,8 +6337,8 @@ class CGCallbackFunctionImpl(CGGeneric):
|
||||||
def __init__(self, callback):
|
def __init__(self, callback):
|
||||||
impl = string.Template("""\
|
impl = string.Template("""\
|
||||||
impl CallbackContainer for ${type} {
|
impl CallbackContainer for ${type} {
|
||||||
fn new(callback: *mut JSObject) -> Rc<${type}> {
|
unsafe fn new(cx: *mut JSContext, callback: *mut JSObject) -> Rc<${type}> {
|
||||||
${type}::new(callback)
|
${type}::new(cx, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn callback_holder(&self) -> &CallbackObject {
|
fn callback_holder(&self) -> &CallbackObject {
|
||||||
|
|
|
@ -441,13 +441,13 @@ impl EventTarget {
|
||||||
assert!(!funobj.is_null());
|
assert!(!funobj.is_null());
|
||||||
// Step 1.14
|
// Step 1.14
|
||||||
if is_error {
|
if is_error {
|
||||||
Some(CommonEventHandler::ErrorEventHandler(OnErrorEventHandlerNonNull::new(funobj)))
|
Some(CommonEventHandler::ErrorEventHandler(OnErrorEventHandlerNonNull::new(cx, funobj)))
|
||||||
} else {
|
} else {
|
||||||
if ty == &atom!("beforeunload") {
|
if ty == &atom!("beforeunload") {
|
||||||
Some(CommonEventHandler::BeforeUnloadEventHandler(
|
Some(CommonEventHandler::BeforeUnloadEventHandler(
|
||||||
OnBeforeUnloadEventHandlerNonNull::new(funobj)))
|
OnBeforeUnloadEventHandlerNonNull::new(cx, funobj)))
|
||||||
} else {
|
} else {
|
||||||
Some(CommonEventHandler::EventHandler(EventHandlerNonNull::new(funobj)))
|
Some(CommonEventHandler::EventHandler(EventHandlerNonNull::new(cx, funobj)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -455,36 +455,47 @@ impl EventTarget {
|
||||||
pub fn set_event_handler_common<T: CallbackContainer>(
|
pub fn set_event_handler_common<T: CallbackContainer>(
|
||||||
&self, ty: &str, listener: Option<Rc<T>>)
|
&self, ty: &str, listener: Option<Rc<T>>)
|
||||||
{
|
{
|
||||||
|
let cx = self.global().get_cx();
|
||||||
|
|
||||||
let event_listener = listener.map(|listener|
|
let event_listener = listener.map(|listener|
|
||||||
InlineEventListener::Compiled(
|
InlineEventListener::Compiled(
|
||||||
CommonEventHandler::EventHandler(
|
CommonEventHandler::EventHandler(
|
||||||
EventHandlerNonNull::new(listener.callback()))));
|
EventHandlerNonNull::new(cx, listener.callback()))));
|
||||||
self.set_inline_event_listener(Atom::from(ty), event_listener);
|
self.set_inline_event_listener(Atom::from(ty), event_listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_error_event_handler<T: CallbackContainer>(
|
pub fn set_error_event_handler<T: CallbackContainer>(
|
||||||
&self, ty: &str, listener: Option<Rc<T>>)
|
&self, ty: &str, listener: Option<Rc<T>>)
|
||||||
{
|
{
|
||||||
|
let cx = self.global().get_cx();
|
||||||
|
|
||||||
let event_listener = listener.map(|listener|
|
let event_listener = listener.map(|listener|
|
||||||
InlineEventListener::Compiled(
|
InlineEventListener::Compiled(
|
||||||
CommonEventHandler::ErrorEventHandler(
|
CommonEventHandler::ErrorEventHandler(
|
||||||
OnErrorEventHandlerNonNull::new(listener.callback()))));
|
OnErrorEventHandlerNonNull::new(cx, listener.callback()))));
|
||||||
self.set_inline_event_listener(Atom::from(ty), event_listener);
|
self.set_inline_event_listener(Atom::from(ty), event_listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_beforeunload_event_handler<T: CallbackContainer>(&self, ty: &str,
|
pub fn set_beforeunload_event_handler<T: CallbackContainer>(&self, ty: &str,
|
||||||
listener: Option<Rc<T>>) {
|
listener: Option<Rc<T>>) {
|
||||||
|
let cx = self.global().get_cx();
|
||||||
|
|
||||||
let event_listener = listener.map(|listener|
|
let event_listener = listener.map(|listener|
|
||||||
InlineEventListener::Compiled(
|
InlineEventListener::Compiled(
|
||||||
CommonEventHandler::BeforeUnloadEventHandler(
|
CommonEventHandler::BeforeUnloadEventHandler(
|
||||||
OnBeforeUnloadEventHandlerNonNull::new(listener.callback())))
|
OnBeforeUnloadEventHandlerNonNull::new(cx, listener.callback())))
|
||||||
);
|
);
|
||||||
self.set_inline_event_listener(Atom::from(ty), event_listener);
|
self.set_inline_event_listener(Atom::from(ty), event_listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unsafe_code)]
|
||||||
pub fn get_event_handler_common<T: CallbackContainer>(&self, ty: &str) -> Option<Rc<T>> {
|
pub fn get_event_handler_common<T: CallbackContainer>(&self, ty: &str) -> Option<Rc<T>> {
|
||||||
|
let cx = self.global().get_cx();
|
||||||
let listener = self.get_inline_event_listener(&Atom::from(ty));
|
let listener = self.get_inline_event_listener(&Atom::from(ty));
|
||||||
listener.map(|listener| CallbackContainer::new(listener.parent().callback_holder().get()))
|
unsafe {
|
||||||
|
listener.map(|listener|
|
||||||
|
CallbackContainer::new(cx, listener.parent().callback_holder().get()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_handlers(&self) -> bool {
|
pub fn has_handlers(&self) -> bool {
|
||||||
|
|
|
@ -176,7 +176,7 @@ impl PromiseJobQueue {
|
||||||
/// promise job queue, and enqueues a runnable to perform a microtask checkpoint if one
|
/// promise job queue, and enqueues a runnable to perform a microtask checkpoint if one
|
||||||
/// is not already pending.
|
/// is not already pending.
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
unsafe extern "C" fn enqueue_job(_cx: *mut JSContext,
|
unsafe extern "C" fn enqueue_job(cx: *mut JSContext,
|
||||||
job: HandleObject,
|
job: HandleObject,
|
||||||
_allocation_site: HandleObject,
|
_allocation_site: HandleObject,
|
||||||
_data: *mut c_void) -> bool {
|
_data: *mut c_void) -> bool {
|
||||||
|
@ -184,7 +184,7 @@ unsafe extern "C" fn enqueue_job(_cx: *mut JSContext,
|
||||||
let global = GlobalScope::from_object(job.get());
|
let global = GlobalScope::from_object(job.get());
|
||||||
let pipeline = global.pipeline_id();
|
let pipeline = global.pipeline_id();
|
||||||
global.enqueue_promise_job(EnqueuedPromiseCallback {
|
global.enqueue_promise_job(EnqueuedPromiseCallback {
|
||||||
callback: PromiseJobCallback::new(job.get()),
|
callback: PromiseJobCallback::new(cx, job.get()),
|
||||||
pipeline: pipeline,
|
pipeline: pipeline,
|
||||||
});
|
});
|
||||||
true
|
true
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue