diff options
author | bors-servo <release+servo@mozilla.com> | 2014-05-03 14:25:22 -0400 |
---|---|---|
committer | bors-servo <release+servo@mozilla.com> | 2014-05-03 14:25:22 -0400 |
commit | 731e66ff132e41cdc49bc5324c0e15be19c46ec2 (patch) | |
tree | ccce9b42e8a6c54245e53620082efe0b9840eae1 /src/components/script/dom/bindings | |
parent | 4051a8096d7ba7e7f9c86e76d0b4bffd83e85805 (diff) | |
parent | 91278da9dd55582401154e07f9eea34425a332c2 (diff) | |
download | servo-731e66ff132e41cdc49bc5324c0e15be19c46ec2.tar.gz servo-731e66ff132e41cdc49bc5324c0e15be19c46ec2.zip |
auto merge of #2101 : jdm/servo/newroot_rebase, r=Ms2ger
As described in #1764, this strategy uses the following properties:
* DOM members are `JS<T>` types. These cannot be used with being explicitly rooted, but they are required for compiler-derived trace hooks.
* Methods that take DOM type arguments receive `&[mut] JSRef<T>`. These are rooted value references that are cloneable but cannot escape.
* Methods that return DOM values use `Unrooted<T>`. These are values that may or may not be rooted elsewhere, but callers must root them in order to interact with them in any way. One unsoundness hole exists - `Unrooted` values must be rooted ASAP, or there exists the danger that JSAPI calls could be made that could cause the underlying JS value to be GCed.
* All methods are implemented on `JSRef<T>`, enforcing the requirement that all DOM values are rooted for the duration of a method call (with a few exceptions for layout-related code, which cannot root values and therefore interacts with `JS<T>` and `&T` values - this is safe under the assumption that layout code interacts with DOM nodes that are in the tree, therefore rooted, and does not run concurrently with content code)
Diffstat (limited to 'src/components/script/dom/bindings')
-rw-r--r-- | src/components/script/dom/bindings/codegen/Bindings.conf | 107 | ||||
-rw-r--r-- | src/components/script/dom/bindings/codegen/CodegenRust.py | 239 | ||||
-rw-r--r-- | src/components/script/dom/bindings/codegen/Configuration.py | 13 | ||||
-rw-r--r-- | src/components/script/dom/bindings/conversions.rs | 20 | ||||
-rw-r--r-- | src/components/script/dom/bindings/js.rs | 465 | ||||
-rw-r--r-- | src/components/script/dom/bindings/trace.rs | 24 | ||||
-rw-r--r-- | src/components/script/dom/bindings/utils.rs | 50 |
7 files changed, 660 insertions, 258 deletions
diff --git a/src/components/script/dom/bindings/codegen/Bindings.conf b/src/components/script/dom/bindings/codegen/Bindings.conf index 053fabae1b2..c41856ccf86 100644 --- a/src/components/script/dom/bindings/codegen/Bindings.conf +++ b/src/components/script/dom/bindings/codegen/Bindings.conf @@ -9,8 +9,6 @@ # The configuration table maps each interface name to a |descriptor|. # # Valid fields for all descriptors: -# * needsAbstract: a list of members that require a JS<>-wrapped version of -# self to be passed to the native code. # * createGlobal: True for global objects. # * outerObjectHook: string to use in place of default value for outerObject and thisObject # JS class hooks @@ -26,123 +24,44 @@ DOMInterfaces = { 'ClientRect': {}, 'ClientRectList': {}, 'Console': {}, -'Document': { - 'needsAbstract': [ - 'adoptNode', - 'anchors', - 'applets', - 'body', - 'children', - 'createComment', - 'createDocumentFragment', - 'createElement', - 'createElementNS', - 'createProcessingInstruction', - 'createTextNode', - 'embeds', - 'forms', - 'getElementsByClassName', - 'getElementsByTagName', - 'getElementsByTagNameNS', - 'images', - 'importNode', - 'links', - 'location', - 'plugins', - 'scripts', - 'title', - ], -}, +'Document': {}, 'DOMException': {}, 'DOMImplementation': {}, 'DOMParser': {}, -'Element': { - 'needsAbstract': [ - 'attributes', - 'children', - 'className', - 'getAttribute', - 'getAttributeNS', - 'getBoundingClientRect', - 'getClientRects', - 'getElementsByClassName', - 'getElementsByTagName', - 'getElementsByTagNameNS', - 'hasAttribute', - 'hasAttributeNS', - 'id', - 'innerHTML', - 'outerHTML', - 'removeAttribute', - 'removeAttributeNS', - 'setAttribute', - 'setAttributeNS', - ] -}, +'Element': {}, 'Event': {}, 'EventListener': { 'nativeType': 'EventListenerBinding::EventListener', }, -'EventTarget': { - 'needsAbstract': ['dispatchEvent'] -}, +'EventTarget': {}, 'FormData': {}, 'HTMLCollection': {}, 'Location': {}, 'MouseEvent': {}, 'Navigator': {}, -'Node': { - 'needsAbstract': [ - 'appendChild', - 'childNodes', - 'cloneNode', - 'compareDocumentPosition', - 'contains', - 'insertBefore', - 'isEqualNode', - 'namespaceURI', - 'nodeName', - 'nodeValue', - 'normalize', - 'removeChild', - 'replaceChild', - 'textContent', - ] -}, - +'Node': {}, 'NodeList': {}, - 'UIEvent': {}, 'ValidityState': {}, 'Window': { 'createGlobal': True, 'outerObjectHook': 'Some(bindings::utils::outerize_global)', - 'needsAbstract': [ - 'console', - 'location', - 'navigator', - 'self', - 'window', - ], }, 'XMLHttpRequest': {}, 'XMLHttpRequestEventTarget': {}, 'XMLHttpRequestUpload': {}, +#FIXME(jdm): This should be 'register': False, but then we don't generate enum types 'TestBinding': {}, } # FIXME: This should be renamed: https://github.com/mozilla/servo/issues/1625 -def addHTMLElement(element, concrete=None, needsAbstract=[]): - DOMInterfaces[element] = { - 'nativeType': 'JS<%s>' % element, - 'concreteType': concrete if concrete else element, - 'needsAbstract': needsAbstract - } +def addHTMLElement(element): + DOMInterfaces[element] = {} addHTMLElement('Comment') -addHTMLElement('DocumentFragment', concrete='DocumentFragment', needsAbstract=['children']) +addHTMLElement('DocumentFragment') addHTMLElement('DocumentType') addHTMLElement('Text') addHTMLElement('ProcessingInstruction') @@ -158,12 +77,12 @@ addHTMLElement('HTMLBRElement') addHTMLElement('HTMLCanvasElement') addHTMLElement('HTMLDataElement') addHTMLElement('HTMLDivElement') -addHTMLElement('HTMLDataListElement', needsAbstract=['options']) +addHTMLElement('HTMLDataListElement') addHTMLElement('HTMLDirectoryElement') addHTMLElement('HTMLDListElement') addHTMLElement('HTMLElement') addHTMLElement('HTMLEmbedElement') -addHTMLElement('HTMLFieldSetElement', needsAbstract=['elements']) +addHTMLElement('HTMLFieldSetElement') addHTMLElement('HTMLFontElement') addHTMLElement('HTMLFormElement') addHTMLElement('HTMLFrameElement') @@ -172,8 +91,8 @@ addHTMLElement('HTMLHeadElement') addHTMLElement('HTMLHeadingElement') addHTMLElement('HTMLHtmlElement') addHTMLElement('HTMLHRElement') -addHTMLElement('HTMLIFrameElement', needsAbstract=['sandbox']) -addHTMLElement('HTMLImageElement', needsAbstract=['alt', 'src', 'useMap', 'isMap', 'width', 'height', 'name', 'align', 'hspace', 'vspace', 'longDesc', 'border']) +addHTMLElement('HTMLIFrameElement') +addHTMLElement('HTMLImageElement') addHTMLElement('HTMLInputElement') addHTMLElement('HTMLLabelElement') addHTMLElement('HTMLLegendElement') @@ -195,7 +114,7 @@ addHTMLElement('HTMLParamElement') addHTMLElement('HTMLPreElement') addHTMLElement('HTMLProgressElement') addHTMLElement('HTMLQuoteElement') -addHTMLElement('HTMLScriptElement', needsAbstract=['src']) +addHTMLElement('HTMLScriptElement') addHTMLElement('HTMLSelectElement') addHTMLElement('HTMLSourceElement') addHTMLElement('HTMLSpanElement') diff --git a/src/components/script/dom/bindings/codegen/CodegenRust.py b/src/components/script/dom/bindings/codegen/CodegenRust.py index cb3fc862c64..55bfa9eac91 100644 --- a/src/components/script/dom/bindings/codegen/CodegenRust.py +++ b/src/components/script/dom/bindings/codegen/CodegenRust.py @@ -394,6 +394,9 @@ def typeIsSequenceOrHasSequenceMember(type): type.flatMemberTypes) return False +def typeNeedsRooting(type, descriptorProvider): + return type.isGeckoInterface() and descriptorProvider.getDescriptor(type.name).needsRooting + def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, isDefinitelyObject=False, isMember=False, @@ -481,6 +484,8 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, if exceptionCode is None: exceptionCode = "return 0;" + needsRooting = typeNeedsRooting(type, descriptorProvider) + def handleOptional(template, declType, isOptional): if isOptional: template = "Some(%s)" % template @@ -489,7 +494,7 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, else: initialValue = None - return (template, declType, isOptional, initialValue) + return (template, declType, isOptional, initialValue, needsRooting) # Unfortunately, .capitalize() on a string will lowercase things inside the # string, which we do not want. @@ -551,7 +556,7 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, templateBody += ( "} else {\n" + CGIndenter(onFailureNotAnObject(failureCode)).define() + - "}") + "}\n") if type.nullable(): templateBody = handleDefaultNull(templateBody, "None") else: @@ -601,6 +606,8 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, failureCode) return handleOptional(template, declType, isOptional) + descriptorType = descriptor.memberType if isMember else descriptor.nativeType + templateBody = "" if descriptor.interface.isConsequential(): raise TypeError("Consequential interface %s being used as an " @@ -616,11 +623,14 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, descriptor, "(${val}).to_object()")) - declType = CGGeneric(descriptor.nativeType) + declType = CGGeneric(descriptorType) if type.nullable(): templateBody = "Some(%s)" % templateBody declType = CGWrapper(declType, pre="Option<", post=">") + if isMember: + templateBody += ".root()" + templateBody = wrapObjectTemplate(templateBody, isDefinitelyObject, type, failureCode) @@ -745,7 +755,7 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, "} else {\n" " ${declName} = NULL;\n" "}" % haveCallable, - CGGeneric("JSObject*"), isOptional, None) + CGGeneric("JSObject*"), isOptional, None, needsRooting) if type.isAny(): assert not isEnforceRange and not isClamp @@ -789,7 +799,7 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, assert not isOptional # This one only happens for return values, and its easy: Just # ignore the jsval. - return ("", None, False, None) + return ("", None, False, None, False) if not type.isPrimitive(): raise TypeError("Need conversion for argument type '%s'" % str(type)) @@ -842,7 +852,7 @@ def instantiateJSToNativeConversionTemplate(templateTuple, replacements, replace ${argc} and ${index}, where ${index} is the index of this argument (0-based) and ${argc} is the total number of arguments. """ - (templateBody, declType, dealWithOptional, initialValue) = templateTuple + (templateBody, declType, dealWithOptional, initialValue, needsRooting) = templateTuple if dealWithOptional and argcAndIndex is None: raise TypeError("Have to deal with optional things, but don't know how") @@ -888,6 +898,12 @@ def instantiateJSToNativeConversionTemplate(templateTuple, replacements, # Add an empty CGGeneric to get an extra newline after the argument # conversion. result.append(CGGeneric("")) + + if needsRooting: + rootBody = "let ${declName} = ${declName}.root();" + result.append(CGGeneric(string.Template(rootBody).substitute(replacements))) + result.append(CGGeneric("")) + return result; def convertConstIDLValueToJSVal(value): @@ -985,6 +1001,13 @@ def typeNeedsCx(type, retVal=False): return True return type.isCallback() or type.isAny() or type.isObject() +def typeRetValNeedsRooting(type): + if type is None: + return False + if type.nullable(): + type = type.inner + return type.isGeckoInterface() and not type.isCallback() + def memberIsCreator(member): return member.getExtendedAttribute("Creator") is not None @@ -1016,7 +1039,7 @@ def getRetvalDeclarationForType(returnType, descriptorProvider): if returnType.isGeckoInterface(): descriptor = descriptorProvider.getDescriptor( returnType.unroll().inner.identifier.name) - result = CGGeneric(descriptor.nativeType) + result = CGGeneric(descriptor.returnType) if returnType.nullable(): result = CGWrapper(result, pre="Option<", post=">") return result @@ -1365,7 +1388,7 @@ class CGImports(CGWrapper): """ Generates the appropriate import/use statements. """ - def __init__(self, child, imports): + def __init__(self, child, descriptors, imports): """ Adds a set of imports. """ @@ -1385,6 +1408,10 @@ class CGImports(CGWrapper): 'dead_code', ] + for d in descriptors: + name = d.interface.identifier.name + imports.append('dom::%s::%sMethods' % (name.lower(), name)) + statements = ['#![allow(%s)]' % ','.join(ignored_warnings)] statements.extend('use %s;' % i for i in sorted(imports)) @@ -1763,7 +1790,7 @@ class CGAbstractMethod(CGThing): def _returnType(self): return (" -> %s" % self.returnType) if self.returnType != "void" else "" def _unsafe_open(self): - return "\n unsafe {" if self.unsafe else "" + return "\n unsafe {\n" if self.unsafe else "" def _unsafe_close(self): return "\n }\n" if self.unsafe else "" @@ -1783,7 +1810,7 @@ def CreateBindingJSObject(descriptor, parent=None): if descriptor.proxy: assert not descriptor.createGlobal handler = """ - let js_info = aScope.get().page().js_info(); + let js_info = aScope.deref().page().js_info(); let handler = js_info.get_ref().dom_static.proxy_handlers.deref().get(&(PrototypeList::id::%s as uint)); """ % descriptor.name create += handler + """ let obj = NewProxyObject(aCx, *handler, @@ -1809,7 +1836,7 @@ class CGWrapMethod(CGAbstractMethod): def __init__(self, descriptor): assert descriptor.interface.hasInterfacePrototypeObject() if not descriptor.createGlobal: - args = [Argument('*JSContext', 'aCx'), Argument('&JS<Window>', 'aScope'), + args = [Argument('*JSContext', 'aCx'), Argument('&JSRef<Window>', 'aScope'), Argument("~" + descriptor.concreteType, 'aObject', mutable=True)] else: args = [Argument('*JSContext', 'aCx'), @@ -2210,6 +2237,9 @@ class CGCallGenerator(CGThing): self.cgRoot.append(CGGeneric("}")) self.cgRoot.append(CGGeneric("result = result_fallible.unwrap();")) + if typeRetValNeedsRooting(returnType): + self.cgRoot.append(CGGeneric("let result = result.root();")) + def define(self): return self.cgRoot.define() @@ -2277,7 +2307,12 @@ class CGPerSignatureCall(CGThing): def getArgc(self): return "argc" def getArguments(self): - return [(a, "arg" + str(i)) for (i, a) in enumerate(self.arguments)] + def process(arg, i): + argVal = "arg" + str(i) + if arg.type.isGeckoInterface() and not arg.type.unroll().inner.isCallback(): + argVal += ".root_ref()" + return argVal + return [(a, process(a, i)) for (i, a) in enumerate(self.arguments)] def isFallible(self): return not 'infallible' in self.extendedAttributes @@ -2447,17 +2482,10 @@ class CGSpecializedMethod(CGAbstractExternMethod): def definition_body(self): name = self.method.identifier.name - nativeName = MakeNativeName(name) - extraPre = '' - argsPre = [] - if name in self.descriptor.needsAbstract: - abstractName = re.sub(r'<\w+>', '', self.descriptor.nativeType) - extraPre = ' let mut abstract_this = %s::from_raw(this);\n' % abstractName - argsPre = ['&mut abstract_this'] - return CGWrapper(CGMethodCall(argsPre, nativeName, self.method.isStatic(), + return CGWrapper(CGMethodCall([], MakeNativeName(name), self.method.isStatic(), self.descriptor, self.method), - pre=extraPre + - " let this = &mut *this;\n").define() + pre=" let this = JS::from_raw(this);\n" + + " let mut this = this.root();\n").define() class CGGenericGetter(CGAbstractBindingMethod): """ @@ -2480,10 +2508,8 @@ class CGGenericGetter(CGAbstractBindingMethod): def generate_code(self): return CGIndenter(CGGeneric( - "return with_gc_disabled(cx, || {\n" - " let info: *JSJitInfo = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, &*vp));\n" - " CallJitPropertyOp(info, cx, obj, this.unsafe_get() as *libc::c_void, &*vp)\n" - "});\n")) + "let info: *JSJitInfo = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, &*vp));\n" + "return CallJitPropertyOp(info, cx, obj, this.unsafe_get() as *libc::c_void, &*vp);\n")) class CGSpecializedGetter(CGAbstractExternMethod): """ @@ -2502,21 +2528,15 @@ class CGSpecializedGetter(CGAbstractExternMethod): def definition_body(self): name = self.attr.identifier.name nativeName = MakeNativeName(name) - extraPre = '' - argsPre = [] infallible = ('infallible' in self.descriptor.getExtendedAttributes(self.attr, getter=True)) - if name in self.descriptor.needsAbstract: - abstractName = re.sub(r'<\w+>', '', self.descriptor.nativeType) - extraPre = ' let mut abstract_this = %s::from_raw(this);\n' % abstractName - argsPre = ['&mut abstract_this'] if self.attr.type.nullable() or not infallible: nativeName = "Get" + nativeName - return CGWrapper(CGIndenter(CGGetterCall(argsPre, self.attr.type, nativeName, + return CGWrapper(CGIndenter(CGGetterCall([], self.attr.type, nativeName, self.descriptor, self.attr)), - pre=extraPre + - " let this = &mut *this;\n").define() + pre=" let this = JS::from_raw(this);\n" + + " let mut this = this.root();\n").define() class CGGenericSetter(CGAbstractBindingMethod): """ @@ -2541,10 +2561,7 @@ class CGGenericSetter(CGAbstractBindingMethod): "let undef = UndefinedValue();\n" "let argv: *JSVal = if argc != 0 { JS_ARGV(cx, vp as *JSVal) } else { &undef as *JSVal };\n" "let info: *JSJitInfo = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp as *JSVal));\n" - "let ok = with_gc_disabled(cx, || {\n" - " CallJitPropertyOp(info, cx, obj, this.unsafe_get() as *libc::c_void, argv)\n" - "});\n" - "if ok == 0 {\n" + "if CallJitPropertyOp(info, cx, obj, this.unsafe_get() as *libc::c_void, argv) == 0 {\n" " return 0;\n" "}\n" "*vp = UndefinedValue();\n" @@ -2566,17 +2583,11 @@ class CGSpecializedSetter(CGAbstractExternMethod): def definition_body(self): name = self.attr.identifier.name - nativeName = "Set" + MakeNativeName(name) - argsPre = [] - extraPre = '' - if name in self.descriptor.needsAbstract: - abstractName = re.sub(r'<\w+>', '', self.descriptor.nativeType) - extraPre = ' let mut abstract_this = %s::from_raw(this);\n' % abstractName - argsPre = ['&mut abstract_this'] - return CGWrapper(CGIndenter(CGSetterCall(argsPre, self.attr.type, nativeName, + return CGWrapper(CGIndenter(CGSetterCall([], self.attr.type, + "Set" + MakeNativeName(name), self.descriptor, self.attr)), - pre=extraPre + - " let this = &mut *this;\n").define() + pre=" let this = JS::from_raw(this);\n" + + " let mut this = this.root();\n").define() class CGMemberJITInfo(CGThing): @@ -2744,7 +2755,7 @@ def getUnionTypeTemplateVars(type, descriptorProvider): name = type.name typeName = "/*" + type.name + "*/" - (template, _, _, _) = getJSToNativeConversionTemplate( + (template, _, _, _, _) = getJSToNativeConversionTemplate( type, descriptorProvider, failureCode="return Ok(None);", exceptionCode='return Err(());', isDefinitelyObject=True, isOptional=False) @@ -3411,7 +3422,12 @@ class CGProxySpecialOperation(CGPerSignatureCall): self.cgRoot.prepend(CGGeneric("let mut found = false;")) def getArguments(self): - args = [(a, a.identifier.name) for a in self.arguments] + def process(arg): + argVal = arg.identifier.name + if arg.type.isGeckoInterface() and not arg.type.unroll().inner.isCallback(): + argVal += ".root_ref()" + return argVal + args = [(a, process(a)) for a in self.arguments] if self.idlNode.isGetter(): args.append((FakeArgument(BuiltinTypes[IDLBuiltinType.Types.boolean], self.idlNode), @@ -3461,14 +3477,14 @@ class CGProxyNamedSetter(CGProxySpecialOperation): class CGProxyUnwrap(CGAbstractMethod): def __init__(self, descriptor): args = [Argument('*JSObject', 'obj')] - CGAbstractMethod.__init__(self, descriptor, "UnwrapProxy", '*' + descriptor.concreteType, args, alwaysInline=True) + CGAbstractMethod.__init__(self, descriptor, "UnwrapProxy", '*mut ' + descriptor.concreteType, args, alwaysInline=True) def definition_body(self): return """ /*if (xpc::WrapperFactory::IsXrayWrapper(obj)) { obj = js::UnwrapObject(obj); }*/ //MOZ_ASSERT(IsProxy(obj)); - let box_: *%s = cast::transmute(GetProxyPrivate(obj).to_private()); + let box_: *mut %s = cast::transmute(GetProxyPrivate(obj).to_private()); return box_;""" % (self.descriptor.concreteType) class CGDOMJSProxyHandler_getOwnPropertyDescriptor(CGAbstractExternMethod): @@ -3493,9 +3509,11 @@ class CGDOMJSProxyHandler_getOwnPropertyDescriptor(CGAbstractExternMethod): templateValues = {'jsvalRef': '(*desc).value', 'successCode': fillDescriptor} get = ("if index.is_some() {\n" + " let index = index.unwrap();\n" + - " let this: *%s = UnwrapProxy(proxy);\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let mut this = this.root();\n" + CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define() + "\n" + - "}\n") % (self.descriptor.concreteType) + "}\n") if indexedSetter or self.descriptor.operations['NamedSetter']: setOrIndexedGet += "if set != 0 {\n" @@ -3538,9 +3556,11 @@ class CGDOMJSProxyHandler_getOwnPropertyDescriptor(CGAbstractExternMethod): namedGet = ("\n" + "if set == 0 && RUST_JSID_IS_STRING(id) != 0 && !HasPropertyOnPrototype(cx, proxy, id) {\n" + " let name = Some(jsid_to_str(cx, id));\n" + - " let this: *%s = UnwrapProxy(proxy);\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let mut this = this.root();\n" + CGIndenter(CGProxyNamedGetter(self.descriptor, templateValues)).define() + "\n" + - "}\n") % (self.descriptor.concreteType) + "}\n") else: namedGet = "" @@ -3581,10 +3601,12 @@ class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod): set += ("let index = GetArrayIndexFromId(cx, id);\n" + "if index.is_some() {\n" + " let index = index.unwrap();\n" + - " let this: *mut %s = UnwrapProxy(proxy) as *mut %s;\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let mut this = this.root();\n" + CGIndenter(CGProxyIndexedSetter(self.descriptor)).define() + " return 1;\n" + - "}\n") % (self.descriptor.concreteType, self.descriptor.concreteType) + "}\n") elif self.descriptor.operations['IndexedGetter']: set += ("if GetArrayIndexFromId(cx, id).is_some() {\n" + " return 0;\n" + @@ -3597,20 +3619,24 @@ class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod): raise TypeError("Can't handle creator that's different from the setter") set += ("if RUST_JSID_IS_STRING(id) != 0 {\n" + " let name = Some(jsid_to_str(cx, id));\n" + - " let this: *%s = UnwrapProxy(proxy);\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let mut this = this.root();\n" + CGIndenter(CGProxyNamedSetter(self.descriptor)).define() + "\n" + - "}\n") % (self.descriptor.concreteType) + "}\n") elif self.descriptor.operations['NamedGetter']: set += ("if RUST_JSID_IS_STRING(id) {\n" + " let name = Some(jsid_to_str(cx, id));\n" + - " let this: %%s = UnwrapProxy(proxy);\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let mut this = this.root();\n" + CGIndenter(CGProxyNamedGetter(self.descriptor)).define() + " if (found) {\n" " return 0;\n" + " //return ThrowErrorMessage(cx, MSG_NO_PROPERTY_SETTER, \"%s\");\n" + " }\n" + " return 1;\n" - "}\n") % (self.descriptor.concreteType, self.descriptor.name) + "}\n") % (self.descriptor.name) return set + """return proxyhandler::defineProperty_(%s);""" % ", ".join(a.name for a in self.args) def definition_body(self): @@ -3628,11 +3654,13 @@ class CGDOMJSProxyHandler_hasOwn(CGAbstractExternMethod): indexed = ("let index = GetArrayIndexFromId(cx, id);\n" + "if index.is_some() {\n" + " let index = index.unwrap();\n" + - " let this: *%s = UnwrapProxy(proxy);\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let mut this = this.root();\n" + CGIndenter(CGProxyIndexedGetter(self.descriptor)).define() + "\n" + " *bp = found as JSBool;\n" + " return 1;\n" + - "}\n\n") % (self.descriptor.concreteType) + "}\n\n") else: indexed = "" @@ -3640,12 +3668,14 @@ class CGDOMJSProxyHandler_hasOwn(CGAbstractExternMethod): if namedGetter: named = ("if RUST_JSID_IS_STRING(id) != 0 && !HasPropertyOnPrototype(cx, proxy, id) {\n" + " let name = Some(jsid_to_str(cx, id));\n" + - " let this: *%s = UnwrapProxy(proxy);\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let mut this = this.root();\n" + CGIndenter(CGProxyNamedGetter(self.descriptor)).define() + "\n" + " *bp = found as JSBool;\n" " return 1;\n" "}\n" + - "\n") % (self.descriptor.concreteType) + "\n") else: named = "" @@ -3693,6 +3723,8 @@ if expando.is_not_null() { "if index.is_some() {\n" + " let index = index.unwrap();\n" + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let mut this = this.root();\n" + CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define()) getIndexedOrExpando += """ // Even if we don't have this index, we don't forward the @@ -3709,6 +3741,8 @@ if expando.is_not_null() { getNamed = ("if (JSID_IS_STRING(id)) {\n" + " let name = Some(jsid_to_str(cx, id));\n" + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let mut this = this.root();\n" + CGIndenter(CGProxyNamedGetter(self.descriptor, templateValues)).define() + "}\n") % (self.descriptor.concreteType) else: @@ -3825,11 +3859,11 @@ class CGClassConstructHook(CGAbstractExternMethod): def generate_code(self): preamble = """ - let global = global_object_for_js_object(JS_CALLEE(cx, &*vp).to_object()); - let obj = global.reflector().get_jsobject(); + let global = global_object_for_js_object(JS_CALLEE(cx, &*vp).to_object()).root(); + let obj = global.deref().reflector().get_jsobject(); """ nativeName = MakeNativeName(self._ctor.identifier.name) - callGenerator = CGMethodCall(["&global"], nativeName, True, + callGenerator = CGMethodCall(["&global.root_ref()"], nativeName, True, self.descriptor, self._ctor) return preamble + callGenerator.define(); @@ -4030,7 +4064,7 @@ class CGDictionary(CGThing): def struct(self): d = self.dictionary if d.parent: - inheritance = " pub parent: %s::%s,\n" % (self.makeModuleName(d.parent), + inheritance = " pub parent: %s::%s<'a, 'b>,\n" % (self.makeModuleName(d.parent), self.makeClassName(d.parent)) else: inheritance = "" @@ -4039,7 +4073,7 @@ class CGDictionary(CGThing): for m in self.memberInfo] return (string.Template( - "pub struct ${selfName} {\n" + + "pub struct ${selfName}<'a, 'b> {\n" + "${inheritance}" + "\n".join(memberDecls) + "\n" + "}").substitute( { "selfName": self.makeClassName(d), @@ -4065,7 +4099,7 @@ class CGDictionary(CGThing): memberInits = CGList([memberInit(m) for m in self.memberInfo]) return string.Template( - "impl ${selfName} {\n" + "impl<'a, 'b> ${selfName}<'a, 'b> {\n" " pub fn new(cx: *JSContext, val: JSVal) -> Result<${selfName}, ()> {\n" " let object = if val.is_null_or_undefined() {\n" " ptr::null()\n" @@ -4104,14 +4138,14 @@ class CGDictionary(CGThing): def getMemberType(self, memberInfo): (member, (templateBody, declType, - dealWithOptional, initialValue)) = memberInfo + dealWithOptional, initialValue, _)) = memberInfo if dealWithOptional: declType = CGWrapper(declType, pre="Optional< ", post=" >") return declType.define() def getMemberConversion(self, memberInfo): (member, (templateBody, declType, - dealWithOptional, initialValue)) = memberInfo + dealWithOptional, initialValue, _)) = memberInfo replacements = { "val": "value.unwrap()" } if member.defaultValue: replacements["haveValue"] = "value.is_some()" @@ -4238,7 +4272,7 @@ class CGBindingRoot(CGThing): # Add imports #XXXjdm This should only import the namespace for the current binding, # not every binding ever. - curr = CGImports(curr, [ + curr = CGImports(curr, descriptors, [ 'js', 'js::{JS_ARGV, JS_CALLEE, JS_THIS_OBJECT}', 'js::{JSCLASS_GLOBAL_SLOT_COUNT, JSCLASS_IS_DOMJSCLASS}', @@ -4266,7 +4300,9 @@ class CGBindingRoot(CGThing): 'js::glue::{RUST_JS_NumberValue, RUST_JSID_IS_STRING}', 'dom::types::*', 'dom::bindings', - 'dom::bindings::js::JS', + 'dom::bindings::js::{JS, JSRef, Root, RootedReference, Temporary}', + 'dom::bindings::js::{OptionalRootable, OptionalRootedRootable, ResultRootable}', + 'dom::bindings::js::{OptionalRootedReference, OptionalOptionalRootedRootable}', 'dom::bindings::utils::{CreateDOMGlobal, CreateInterfaceObjects2}', 'dom::bindings::utils::{ConstantSpec, cx_for_dom_object, Default}', 'dom::bindings::utils::{dom_object_slot, DOM_OBJECT_SLOT, DOMClass}', @@ -4279,8 +4315,7 @@ class CGBindingRoot(CGThing): 'dom::bindings::utils::{Reflectable}', 'dom::bindings::utils::{squirrel_away_unique}', 'dom::bindings::utils::{ThrowingConstructor, unwrap, unwrap_jsmanaged}', - 'dom::bindings::utils::{VoidVal, with_gc_disabled}', - 'dom::bindings::utils::{with_gc_enabled}', + 'dom::bindings::utils::VoidVal', 'dom::bindings::utils::get_dictionary_property', 'dom::bindings::trace::JSTraceable', 'dom::bindings::callback::{CallbackContainer,CallbackInterface}', @@ -4549,7 +4584,7 @@ class CGNativeMember(ClassMethod): else: typeDecl = "%s" descriptor = self.descriptorProvider.getDescriptor(iface.identifier.name) - return (typeDecl % descriptor.nativeType, + return (typeDecl % descriptor.argumentType, False, False) if type.isSpiderMonkeyInterface(): @@ -5057,11 +5092,8 @@ class CallbackMethod(CallbackMember): replacements["argc"] = "0" return string.Template("${getCallable}" "let ok = unsafe {\n" - " //JS_AllowGC(cx); // It's unsafe to enable GC at arbitrary points during Rust execution; leave it disabled\n" - " let ok = JS_CallFunctionValue(cx, ${thisObj}, callable,\n" - " ${argc}, ${argv}, &rval);\n" - " //JS_InhibitGC(cx);\n" - " ok\n" + " JS_CallFunctionValue(cx, ${thisObj}, callable,\n" + " ${argc}, ${argv}, &rval)\n" "};\n" "if ok == 0 {\n" " return${errorReturn};\n" @@ -5207,9 +5239,9 @@ class GlobalGenRoots(): @staticmethod def RegisterBindings(config): # TODO - Generate the methods we want - return CGImports(CGRegisterProtos(config), [ + return CGImports(CGRegisterProtos(config), [], [ 'dom::bindings::codegen', - 'dom::bindings::js::JS', + 'dom::bindings::js::{JS, JSRef}', 'dom::window::Window', 'script_task::JSPageInfo', ]) @@ -5241,8 +5273,9 @@ class GlobalGenRoots(): descriptors = config.getDescriptors(register=True, hasInterfaceObject=True) allprotos = [CGGeneric("#![allow(unused_imports)]\n"), CGGeneric("use dom::types::*;\n"), - CGGeneric("use dom::bindings::js::JS;\n"), + CGGeneric("use dom::bindings::js::{JS, JSRef, Temporary};\n"), CGGeneric("use dom::bindings::trace::JSTraceable;\n"), + CGGeneric("use dom::bindings::utils::Reflectable;\n"), CGGeneric("use serialize::{Encodable, Encoder};\n"), CGGeneric("use js::jsapi::JSTracer;\n\n")] for descriptor in descriptors: @@ -5269,22 +5302,34 @@ class GlobalGenRoots(): cast = [CGGeneric(string.Template('''pub trait ${castTraitName} { #[inline(always)] - fn from<T: ${fromBound}>(derived: &JS<T>) -> JS<Self> { - unsafe { derived.clone().transmute() } + fn to_ref<'a, 'b, T: ${toBound}+Reflectable>(base: &'a JSRef<'b, T>) -> Option<&'a JSRef<'b, Self>> { + match base.deref().${checkFn}() { + true => unsafe { Some(base.transmute()) }, + false => None + } } #[inline(always)] - fn to<T: ${toBound}>(base: &JS<T>) -> Option<JS<Self>> { - match base.get().${checkFn}() { - true => unsafe { Some(base.clone().transmute()) }, + fn to_mut_ref<'a, 'b, T: ${toBound}+Reflectable>(base: &'a mut JSRef<'b, T>) -> Option<&'a mut JSRef<'b, Self>> { + match base.deref().${checkFn}() { + true => unsafe { Some(base.transmute_mut()) }, false => None } } #[inline(always)] - unsafe fn to_unchecked<T: ${toBound}>(base: &JS<T>) -> JS<Self> { - assert!(base.get().${checkFn}()); - base.clone().transmute() + fn from_ref<'a, 'b, T: ${fromBound}>(derived: &'a JSRef<'b, T>) -> &'a JSRef<'b, Self> { + unsafe { derived.transmute() } + } + + #[inline(always)] + fn from_mut_ref<'a, 'b, T: ${fromBound}>(derived: &'a mut JSRef<'b, T>) -> &'a mut JSRef<'b, Self> { + unsafe { derived.transmute_mut() } + } + + #[inline(always)] + fn from_unrooted<T: ${fromBound}+Reflectable>(derived: Temporary<T>) -> Temporary<Self> { + unsafe { derived.transmute() } } } ''').substitute({'checkFn': 'is_' + name.lower(), @@ -5313,7 +5358,7 @@ class GlobalGenRoots(): curr = UnionTypes(config.getDescriptors()) - curr = CGImports(curr, [ + curr = CGImports(curr, [], [ 'dom::bindings::utils::unwrap_jsmanaged', 'dom::bindings::codegen::PrototypeList', 'dom::bindings::conversions::{FromJSValConvertible, ToJSValConvertible}', diff --git a/src/components/script/dom/bindings/codegen/Configuration.py b/src/components/script/dom/bindings/codegen/Configuration.py index a1ccb460ee2..1272867f542 100644 --- a/src/components/script/dom/bindings/codegen/Configuration.py +++ b/src/components/script/dom/bindings/codegen/Configuration.py @@ -128,14 +128,19 @@ class Descriptor(DescriptorProvider): # Read the desc, and fill in the relevant defaults. ifaceName = self.interface.identifier.name + + # Callback types do not use JS smart pointers, so we should not use the + # built-in rooting mechanisms for them. if self.interface.isCallback(): - nativeTypeDefault = "nsIDOM" + ifaceName + self.needsRooting = False else: - nativeTypeDefault = 'JS<%s>' % ifaceName + self.needsRooting = True - self.nativeType = desc.get('nativeType', nativeTypeDefault) + self.returnType = "Temporary<%s>" % ifaceName + self.argumentType = "JSRef<%s>" % ifaceName + self.memberType = "Root<'a, 'b, %s>" % ifaceName + self.nativeType = desc.get('nativeType', 'JS<%s>' % ifaceName) self.concreteType = desc.get('concreteType', ifaceName) - self.needsAbstract = desc.get('needsAbstract', []) self.createGlobal = desc.get('createGlobal', False) self.register = desc.get('register', True) self.outerObjectHook = desc.get('outerObjectHook', 'None') diff --git a/src/components/script/dom/bindings/conversions.rs b/src/components/script/dom/bindings/conversions.rs index 6721f3f5dde..02d6983cf3b 100644 --- a/src/components/script/dom/bindings/conversions.rs +++ b/src/components/script/dom/bindings/conversions.rs @@ -2,9 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use dom::bindings::js::JS; +use dom::bindings::js::{JS, JSRef, Root}; use dom::bindings::str::ByteString; -use dom::bindings::utils::Reflectable; +use dom::bindings::utils::{Reflectable, Reflector}; use dom::bindings::utils::jsstring_to_str; use dom::bindings::utils::unwrap_jsmanaged; use servo_util::str::DOMString; @@ -293,9 +293,9 @@ impl FromJSValConvertible<()> for ByteString { } } -impl<T: Reflectable> ToJSValConvertible for JS<T> { +impl ToJSValConvertible for Reflector { fn to_jsval(&self, cx: *JSContext) -> JSVal { - let obj = self.reflector().get_jsobject(); + let obj = self.get_jsobject(); assert!(obj.is_not_null()); let mut value = ObjectValue(unsafe { &*obj }); if unsafe { JS_WrapValue(cx, &mut value as *mut JSVal as *JSVal) } == 0 { @@ -316,6 +316,18 @@ impl<T: Reflectable+IDLInterface> FromJSValConvertible<()> for JS<T> { } } +impl<'a, 'b, T: Reflectable> ToJSValConvertible for Root<'a, 'b, T> { + fn to_jsval(&self, cx: *JSContext) -> JSVal { + self.reflector().to_jsval(cx) + } +} + +impl<'a, T: Reflectable> ToJSValConvertible for JSRef<'a, T> { + fn to_jsval(&self, cx: *JSContext) -> JSVal { + self.reflector().to_jsval(cx) + } +} + impl<T: ToJSValConvertible> ToJSValConvertible for Option<T> { fn to_jsval(&self, cx: *JSContext) -> JSVal { match self { diff --git a/src/components/script/dom/bindings/js.rs b/src/components/script/dom/bindings/js.rs index 10aec5424bc..da84c85eeb7 100644 --- a/src/components/script/dom/bindings/js.rs +++ b/src/components/script/dom/bindings/js.rs @@ -2,14 +2,115 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use dom::bindings::utils::{Reflector, Reflectable}; -use dom::window::Window; -use js::jsapi::JSContext; +/// The DOM is made up of Rust types whose lifetime is entirely controlled by the whims of +/// the SpiderMonkey garbage collector. The types in this module are designed to ensure +/// that any interactions with said Rust types only occur on values that will remain alive +/// the entire time. +/// +/// Here is a brief overview of the important types: +/// - JSRef<T>: a freely-copyable reference to a rooted value. +/// - JS<T>: a pointer to JS-owned memory that can automatically be traced by the GC when +/// encountered as a field of a Rust structure. +/// - Temporary<T>: a value that will remain rooted for the duration of its lifetime. +/// +/// The rule of thumb is as follows: +/// - All methods return Temporary<T>, to ensure the value remains alive until it is stored +/// somewhere that is reachable by the GC. +/// - All functions take &JSRef<T> arguments, to ensure that they will remain uncollected for +/// the duration of their usage. +/// - All types contain JS<T> fields and derive the Encodable trait, to ensure that they are +/// transitively marked as reachable by the GC if the enclosing value is reachable. +/// - All methods for type T are implemented for JSRef<T>, to ensure that the self value +/// will not be collected for the duration of the method call. +/// +/// Both Temporary<T> and JS<T> do not allow access to their inner value without explicitly +/// creating a stack-based root via the `root` method. This returns a Root<T>, which causes +/// the JS-owned value to be uncollectable for the duration of the Root type's lifetime. +/// A JSRef<T> can be obtained from a Root<T> either by dereferencing the Root<T> (`*rooted`) +/// or explicitly calling the `root_ref` method. These JSRef<T> values are not allowed to +/// outlive their originating Root<T>, to ensure that all interactions with the enclosed value +/// only occur when said value is uncollectable, and will cause static lifetime errors if +/// misused. +/// +/// Other miscellaneous helper traits: +/// - OptionalRootable and OptionalRootedRootable: make rooting Option values easy via a `root` method +/// - ResultRootable: make rooting successful Result values easy +/// - TemporaryPushable: allows mutating vectors of JS<T> with new elements of JSRef/Temporary +/// - OptionalSettable: allows assigning Option values of JSRef/Temporary to fields of Option<JS<T>> +/// - RootedReference: makes obtaining an Option<JSRef<T>> from an Option<Root<T>> easy + +use dom::bindings::utils::{Reflector, Reflectable, cx_for_dom_object}; +use dom::node::Node; +use js::jsapi::{JSObject, JS_AddObjectRoot, JS_RemoveObjectRoot}; use layout_interface::TrustedNodeAddress; +use script_task::StackRoots; use std::cast; use std::cell::RefCell; +use std::local_data; + +/// A type that represents a JS-owned value that is rooted for the lifetime of this value. +/// Importantly, it requires explicit rooting in order to interact with the inner value. +/// Can be assigned into JS-owned member fields (ie. JS<T> types) safely via the +/// `JS<T>::assign` method or `OptionalSettable::assign` (for Option<JS<T>> fields). +pub struct Temporary<T> { + inner: JS<T>, +} + +impl<T> Eq for Temporary<T> { + fn eq(&self, other: &Temporary<T>) -> bool { + self.inner == other.inner + } +} + +#[unsafe_destructor] +impl<T: Reflectable> Drop for Temporary<T> { + fn drop(&mut self) { + let cx = cx_for_dom_object(&self.inner); + unsafe { + JS_RemoveObjectRoot(cx, self.inner.reflector().rootable()); + } + } +} + +impl<T: Reflectable> Temporary<T> { + /// Create a new Temporary value from a JS-owned value. + pub fn new(inner: JS<T>) -> Temporary<T> { + let cx = cx_for_dom_object(&inner); + unsafe { + JS_AddObjectRoot(cx, inner.reflector().rootable()); + } + Temporary { + inner: inner, + } + } + + /// Create a new Temporary value from a rooted value. + pub fn from_rooted<'a>(root: &JSRef<'a, T>) -> Temporary<T> { + Temporary::new(root.unrooted()) + } + + /// Create a stack-bounded root for this value. + pub fn root<'a, 'b>(self) -> Root<'a, 'b, T> { + local_data::get(StackRoots, |opt| { + let collection = opt.unwrap(); + unsafe { + (**collection).new_root(&self.inner) + } + }) + } + + unsafe fn inner(&self) -> JS<T> { + self.inner.clone() + } + + //XXXjdm It would be lovely if this could be private. + pub unsafe fn transmute<To>(self) -> Temporary<To> { + cast::transmute(self) + } +} +/// A rooted, JS-owned value. Must only be used as a field in other JS-owned types. pub struct JS<T> { ptr: RefCell<*mut T> } @@ -29,13 +130,18 @@ impl <T> Clone for JS<T> { } } -impl<T: Reflectable> JS<T> { - pub fn new(obj: ~T, - window: &JS<Window>, - wrap_fn: extern "Rust" fn(*JSContext, &JS<Window>, ~T) -> JS<T>) -> JS<T> { - wrap_fn(window.get().get_cx(), window, obj) +impl JS<Node> { + /// Create a new JS-owned value wrapped from an address known to be a Node pointer. + pub unsafe fn from_trusted_node_address(inner: TrustedNodeAddress) -> JS<Node> { + let TrustedNodeAddress(addr) = inner; + JS { + ptr: RefCell::new(addr as *mut Node) + } } +} +impl<T: Reflectable> JS<T> { + /// Create a new JS-owned value wrapped from a raw Rust pointer. pub unsafe fn from_raw(raw: *mut T) -> JS<T> { JS { ptr: RefCell::new(raw) @@ -43,45 +149,47 @@ impl<T: Reflectable> JS<T> { } - pub unsafe fn from_trusted_node_address(inner: TrustedNodeAddress) -> JS<T> { - let TrustedNodeAddress(addr) = inner; - JS { - ptr: RefCell::new(addr as *mut T) - } + /// Root this JS-owned value to prevent its collection as garbage. + pub fn root<'a, 'b>(&self) -> Root<'a, 'b, T> { + local_data::get(StackRoots, |opt| { + let collection = opt.unwrap(); + unsafe { + (**collection).new_root(self) + } + }) } } +//XXXjdm This is disappointing. This only gets called from trace hooks, in theory, +// so it's safe to assume that self is rooted and thereby safe to access. impl<T: Reflectable> Reflectable for JS<T> { fn reflector<'a>(&'a self) -> &'a Reflector { - self.get().reflector() - } - - fn mut_reflector<'a>(&'a mut self) -> &'a mut Reflector { - self.get_mut().mut_reflector() - } -} - -impl<T> JS<T> { - pub fn get<'a>(&'a self) -> &'a T { - let borrowed = self.ptr.borrow(); unsafe { - &**borrowed + (*self.unsafe_get()).reflector() } } - pub fn get_mut<'a>(&'a mut self) -> &'a mut T { - let mut borrowed = self.ptr.borrow_mut(); + fn mut_reflector<'a>(&'a mut self) -> &'a mut Reflector { unsafe { - &mut **borrowed + (*self.unsafe_get()).mut_reflector() } } +} +impl<T: Reflectable> JS<T> { /// Returns an unsafe pointer to the interior of this JS object without touching the borrow /// flags. This is the only method that be safely accessed from layout. (The fact that this /// is unsafe is what necessitates the layout wrappers.) pub unsafe fn unsafe_get(&self) -> *mut T { cast::transmute_copy(&self.ptr) } + + /// Store an unrooted value in this field. This is safe under the assumption that JS<T> + /// values are only used as fields in DOM types that are reachable in the GC graph, + /// so this unrooted value becomes transitively rooted for the lifetime of its new owner. + pub fn assign(&mut self, val: Temporary<T>) { + *self = unsafe { val.inner() }; + } } impl<From, To> JS<From> { @@ -94,3 +202,304 @@ impl<From, To> JS<From> { cast::transmute_copy(self) } } + + +/// Get an Option<JSRef<T>> out of an Option<Root<T>> +pub trait RootedReference<T> { + fn root_ref<'a>(&'a self) -> Option<JSRef<'a, T>>; +} + +impl<'a, 'b, T: Reflectable> RootedReference<T> for Option<Root<'a, 'b, T>> { + fn root_ref<'a>(&'a self) -> Option<JSRef<'a, T>> { + self.as_ref().map(|root| root.root_ref()) + } +} + +/// Get an Option<Option<JSRef<T>>> out of an Option<Option<Root<T>>> +pub trait OptionalRootedReference<T> { + fn root_ref<'a>(&'a self) -> Option<Option<JSRef<'a, T>>>; +} + +impl<'a, 'b, T: Reflectable> OptionalRootedReference<T> for Option<Option<Root<'a, 'b, T>>> { + fn root_ref<'a>(&'a self) -> Option<Option<JSRef<'a, T>>> { + self.as_ref().map(|inner| inner.root_ref()) + } +} + +/// Trait that allows extracting a JS<T> value from a variety of rooting-related containers, +/// which in general is an unsafe operation since they can outlive the rooted lifetime of the +/// original value. +/*definitely not public*/ trait Assignable<T> { + unsafe fn get_js(&self) -> JS<T>; +} + +impl<T> Assignable<T> for JS<T> { + unsafe fn get_js(&self) -> JS<T> { + self.clone() + } +} + +impl<'a, T> Assignable<T> for JSRef<'a, T> { + unsafe fn get_js(&self) -> JS<T> { + self.unrooted() + } +} + +impl<T: Reflectable> Assignable<T> for Temporary<T> { + unsafe fn get_js(&self) -> JS<T> { + self.inner() + } +} + +/// Assign an optional rootable value (either of JS<T> or Temporary<T>) to an optional +/// field of a DOM type (ie. Option<JS<T>>) +pub trait OptionalSettable<T> { + fn assign(&mut self, val: Option<T>); +} + +impl<T: Assignable<U>, U: Reflectable> OptionalSettable<T> for Option<JS<U>> { + fn assign(&mut self, val: Option<T>) { + *self = val.map(|val| unsafe { val.get_js() }); + } +} + +/// Root a rootable Option type (used for Option<Temporary<T>>) +pub trait OptionalRootable<T> { + fn root<'a, 'b>(self) -> Option<Root<'a, 'b, T>>; +} + +impl<T: Reflectable> OptionalRootable<T> for Option<Temporary<T>> { + fn root<'a, 'b>(self) -> Option<Root<'a, 'b, T>> { + self.map(|inner| inner.root()) + } +} + +/// Return an unrooted type for storing in optional DOM fields +pub trait OptionalUnrootable<T> { + fn unrooted(&self) -> Option<JS<T>>; +} + +impl<'a, T: Reflectable> OptionalUnrootable<T> for Option<JSRef<'a, T>> { + fn unrooted(&self) -> Option<JS<T>> { + self.as_ref().map(|inner| inner.unrooted()) + } +} + +/// Root a rootable Option type (used for Option<JS<T>>) +pub trait OptionalRootedRootable<T> { + fn root<'a, 'b>(&self) -> Option<Root<'a, 'b, T>>; +} + +impl<T: Reflectable> OptionalRootedRootable<T> for Option<JS<T>> { + fn root<'a, 'b>(&self) -> Option<Root<'a, 'b, T>> { + self.as_ref().map(|inner| inner.root()) + } +} + +/// Root a rootable Option<Option> type (used for Option<Option<JS<T>>>) +pub trait OptionalOptionalRootedRootable<T> { + fn root<'a, 'b>(&self) -> Option<Option<Root<'a, 'b, T>>>; +} + +impl<T: Reflectable> OptionalOptionalRootedRootable<T> for Option<Option<JS<T>>> { + fn root<'a, 'b>(&self) -> Option<Option<Root<'a, 'b, T>>> { + self.as_ref().map(|inner| inner.root()) + } +} + + +/// Root a rootable Result type (any of Temporary<T> or JS<T>) +pub trait ResultRootable<T,U> { + fn root<'a, 'b>(self) -> Result<Root<'a, 'b, T>, U>; +} + +impl<T: Reflectable, U> ResultRootable<T, U> for Result<Temporary<T>, U> { + fn root<'a, 'b>(self) -> Result<Root<'a, 'b, T>, U> { + self.map(|inner| inner.root()) + } +} + +impl<T: Reflectable, U> ResultRootable<T, U> for Result<JS<T>, U> { + fn root<'a, 'b>(self) -> Result<Root<'a, 'b, T>, U> { + self.map(|inner| inner.root()) + } +} + +/// Provides a facility to push unrooted values onto lists of rooted values. This is safe +/// under the assumption that said lists are reachable via the GC graph, and therefore the +/// new values are transitively rooted for the lifetime of their new owner. +pub trait TemporaryPushable<T> { + fn push_unrooted(&mut self, val: &T); + fn insert_unrooted(&mut self, index: uint, val: &T); +} + +impl<T: Assignable<U>, U: Reflectable> TemporaryPushable<T> for Vec<JS<U>> { + fn push_unrooted(&mut self, val: &T) { + self.push(unsafe { val.get_js() }); + } + + fn insert_unrooted(&mut self, index: uint, val: &T) { + self.insert(index, unsafe { val.get_js() }); + } +} + +/// An opaque, LIFO rooting mechanism. +pub struct RootCollection { + roots: RefCell<Vec<*JSObject>>, +} + +impl RootCollection { + /// Create an empty collection of roots + pub fn new() -> RootCollection { + RootCollection { + roots: RefCell::new(vec!()), + } + } + + /// Create a new stack-bounded root that will not outlive this collection + fn new_root<'a, 'b, T: Reflectable>(&'a self, unrooted: &JS<T>) -> Root<'a, 'b, T> { + Root::new(self, unrooted) + } + + /// Track a stack-based root to ensure LIFO root ordering + fn root<'a, 'b, T: Reflectable>(&self, untracked: &Root<'a, 'b, T>) { + let mut roots = self.roots.borrow_mut(); + roots.push(untracked.js_ptr); + debug!(" rooting {:?}", untracked.js_ptr); + } + + /// Stop tracking a stack-based root, asserting if LIFO root ordering has been violated + fn unroot<'a, 'b, T: Reflectable>(&self, rooted: &Root<'a, 'b, T>) { + let mut roots = self.roots.borrow_mut(); + debug!("unrooting {:?} (expecting {:?}", roots.last().unwrap(), rooted.js_ptr); + assert!(*roots.last().unwrap() == rooted.js_ptr); + roots.pop().unwrap(); + } +} + +/// A rooted JS value. The JS value is pinned for the duration of this object's lifetime; +/// roots are additive, so this object's destruction will not invalidate other roots +/// for the same JS value. Roots cannot outlive the associated RootCollection object. +/// Attempts to transfer ownership of a Root via moving will trigger dynamic unrooting +/// failures due to incorrect ordering. +pub struct Root<'a, 'b, T> { + /// List that ensures correct dynamic root ordering + root_list: &'a RootCollection, + /// Reference to rooted value that must not outlive this container + jsref: JSRef<'b, T>, + /// Pointer to underlying Rust data + ptr: RefCell<*mut T>, + /// On-stack JS pointer to assuage conservative stack scanner + js_ptr: *JSObject, +} + +impl<'a, 'b, T: Reflectable> Root<'a, 'b, T> { + /// Create a new stack-bounded root for the provided JS-owned value. + /// It cannot not outlive its associated RootCollection, and it contains a JSRef + /// which cannot outlive this new Root. + fn new(roots: &'a RootCollection, unrooted: &JS<T>) -> Root<'a, 'b, T> { + let root = Root { + root_list: roots, + jsref: JSRef { + ptr: unrooted.ptr.clone(), + chain: unsafe { cast::transmute_region(&()) }, + }, + ptr: unrooted.ptr.clone(), + js_ptr: unrooted.reflector().get_jsobject(), + }; + roots.root(&root); + root + } + + /// Obtain a safe reference to the wrapped JS owned-value that cannot outlive + /// the lifetime of this root. + pub fn root_ref<'b>(&'b self) -> JSRef<'b,T> { + self.jsref.clone() + } +} + +#[unsafe_destructor] +impl<'a, 'b, T: Reflectable> Drop for Root<'a, 'b, T> { + fn drop(&mut self) { + self.root_list.unroot(self); + } +} + +impl<'a, 'b, T: Reflectable> Deref<JSRef<'b, T>> for Root<'a, 'b, T> { + fn deref<'c>(&'c self) -> &'c JSRef<'b, T> { + &self.jsref + } +} + +impl<'a, 'b, T: Reflectable> DerefMut<JSRef<'b, T>> for Root<'a, 'b, T> { + fn deref_mut<'c>(&'c mut self) -> &'c mut JSRef<'b, T> { + &mut self.jsref + } +} + +impl<'a, T: Reflectable> Deref<T> for JSRef<'a, T> { + fn deref<'b>(&'b self) -> &'b T { + let borrow = self.ptr.borrow(); + unsafe { + &**borrow + } + } +} + +impl<'a, T: Reflectable> DerefMut<T> for JSRef<'a, T> { + fn deref_mut<'b>(&'b mut self) -> &'b mut T { + let mut borrowed = self.ptr.borrow_mut(); + unsafe { + &mut **borrowed + } + } +} + +/// Encapsulates a reference to something that is guaranteed to be alive. This is freely copyable. +pub struct JSRef<'a, T> { + ptr: RefCell<*mut T>, + chain: &'a (), +} + +impl<'a, T> Clone for JSRef<'a, T> { + fn clone(&self) -> JSRef<'a, T> { + JSRef { + ptr: self.ptr.clone(), + chain: self.chain + } + } +} + +impl<'a, T> Eq for JSRef<'a, T> { + fn eq(&self, other: &JSRef<T>) -> bool { + self.ptr == other.ptr + } +} + +impl<'a,T> JSRef<'a,T> { + //XXXjdm It would be lovely if this could be private. + pub unsafe fn transmute<'b, To>(&'b self) -> &'b JSRef<'a, To> { + cast::transmute(self) + } + + //XXXjdm It would be lovely if this could be private. + pub unsafe fn transmute_mut<'b, To>(&'b mut self) -> &'b mut JSRef<'a, To> { + cast::transmute(self) + } + + pub fn unrooted(&self) -> JS<T> { + JS { + ptr: self.ptr.clone() + } + } +} + +impl<'a, T: Reflectable> Reflectable for JSRef<'a, T> { + fn reflector<'a>(&'a self) -> &'a Reflector { + self.deref().reflector() + } + + fn mut_reflector<'a>(&'a mut self) -> &'a mut Reflector { + self.deref_mut().mut_reflector() + } +} diff --git a/src/components/script/dom/bindings/trace.rs b/src/components/script/dom/bindings/trace.rs index 554c67e8dc6..cbbeddb8594 100644 --- a/src/components/script/dom/bindings/trace.rs +++ b/src/components/script/dom/bindings/trace.rs @@ -6,6 +6,7 @@ use dom::bindings::js::JS; use dom::bindings::utils::{Reflectable, Reflector}; use js::jsapi::{JSObject, JSTracer, JS_CallTracer, JSTRACE_OBJECT}; +use js::jsval::JSVal; use libc; use std::cast; @@ -42,6 +43,22 @@ pub trait JSTraceable { fn trace(&self, trc: *mut JSTracer); } +pub fn trace_jsval(tracer: *mut JSTracer, description: &str, val: JSVal) { + if !val.is_gcthing() { + return; + } + + unsafe { + description.to_c_str().with_ref(|name| { + (*tracer).debugPrinter = ptr::null(); + (*tracer).debugPrintIndex = -1; + (*tracer).debugPrintArg = name as *libc::c_void; + debug!("tracing value {:s}", description); + JS_CallTracer(tracer as *JSTracer, val.to_gcthing(), val.trace_kind()); + }); + } +} + pub fn trace_reflector(tracer: *mut JSTracer, description: &str, reflector: &Reflector) { trace_object(tracer, description, reflector.get_jsobject()) } @@ -132,3 +149,10 @@ impl<S: Encoder<E>, E> Encodable<S, E> for Traceable<*JSObject> { Ok(()) } } + +impl<S: Encoder<E>, E> Encodable<S, E> for Traceable<JSVal> { + fn encode(&self, s: &mut S) -> Result<(), E> { + trace_jsval(get_jstracer(s), "val", **self); + Ok(()) + } +} diff --git a/src/components/script/dom/bindings/utils.rs b/src/components/script/dom/bindings/utils.rs index 417d4c434c2..a99189ae161 100644 --- a/src/components/script/dom/bindings/utils.rs +++ b/src/components/script/dom/bindings/utils.rs @@ -5,7 +5,7 @@ use dom::bindings::codegen::PrototypeList; use dom::bindings::codegen::PrototypeList::MAX_PROTO_CHAIN_LENGTH; use dom::bindings::conversions::{FromJSValConvertible, IDLInterface}; -use dom::bindings::js::JS; +use dom::bindings::js::{JS, JSRef, Temporary, Root}; use dom::bindings::trace::Untraceable; use dom::browsercontext; use dom::window; @@ -37,7 +37,6 @@ use js::jsapi::{JSContext, JSObject, JSBool, jsid, JSClass, JSNative}; use js::jsapi::{JSFunctionSpec, JSPropertySpec}; use js::jsapi::{JS_NewGlobalObject, JS_InitStandardClasses}; use js::jsapi::{JSString}; -use js::jsapi::{JS_AllowGC, JS_InhibitGC}; use js::jsfriendapi::bindgen::JS_NewObjectWithUniqueType; use js::jsval::JSVal; use js::jsval::{PrivateValue, ObjectValue, NullValue, ObjectOrNullValue}; @@ -390,10 +389,10 @@ pub trait Reflectable { pub fn reflect_dom_object<T: Reflectable> (obj: ~T, - window: &JS<window::Window>, - wrap_fn: extern "Rust" fn(*JSContext, &JS<window::Window>, ~T) -> JS<T>) - -> JS<T> { - JS::new(obj, window, wrap_fn) + window: &JSRef<window::Window>, + wrap_fn: extern "Rust" fn(*JSContext, &JSRef<window::Window>, ~T) -> JS<T>) + -> Temporary<T> { + Temporary::new(wrap_fn(window.deref().get_cx(), window, obj)) } #[deriving(Eq)] @@ -413,6 +412,13 @@ impl Reflector { self.object = object; } + /// Return a pointer to the memory location at which the JS reflector object is stored. + /// Used by Temporary values to root the reflector, as required by the JSAPI rooting + /// APIs. + pub fn rootable<'a>(&'a self) -> &'a *JSObject { + &self.object + } + pub fn new() -> Reflector { Reflector { object: ptr::null(), @@ -605,11 +611,13 @@ pub extern fn outerize_global(_cx: *JSContext, obj: JSHandleObject) -> *JSObject unsafe { debug!("outerizing"); let obj = *obj.unnamed; - let win: JS<window::Window> = + let win: Root<window::Window> = unwrap_jsmanaged(obj, IDLInterface::get_prototype_id(None::<window::Window>), - IDLInterface::get_prototype_depth(None::<window::Window>)).unwrap(); - win.get().browser_context.get_ref().window_proxy() + IDLInterface::get_prototype_depth(None::<window::Window>)) + .unwrap() + .root(); + win.deref().browser_context.get_ref().window_proxy() } } @@ -625,8 +633,8 @@ pub fn global_object_for_js_object(obj: *JSObject) -> JS<window::Window> { } fn cx_for_dom_reflector(obj: *JSObject) -> *JSContext { - let win = global_object_for_js_object(obj); - let js_info = win.get().page().js_info(); + let win = global_object_for_js_object(obj).root(); + let js_info = win.deref().page().js_info(); match *js_info { Some(ref info) => info.js_context.deref().deref().ptr, None => fail!("no JS context for DOM global") @@ -637,26 +645,6 @@ pub fn cx_for_dom_object<T: Reflectable>(obj: &T) -> *JSContext { cx_for_dom_reflector(obj.reflector().get_jsobject()) } -/// Execute arbitrary code with the JS GC enabled, then disable it afterwards. -pub fn with_gc_enabled<R>(cx: *JSContext, f: || -> R) -> R { - unsafe { - JS_AllowGC(cx); - let rv = f(); - JS_InhibitGC(cx); - rv - } -} - -/// Execute arbitrary code with the JS GC disabled, then enable it afterwards. -pub fn with_gc_disabled<R>(cx: *JSContext, f: || -> R) -> R { - unsafe { - JS_InhibitGC(cx); - let rv = f(); - JS_AllowGC(cx); - rv - } -} - /// Check if an element name is valid. See http://www.w3.org/TR/xml/#NT-Name /// for details. #[deriving(Eq)] |