diff --git a/src/components/script/dom/bindings/callback.rs b/src/components/script/dom/bindings/callback.rs new file mode 100644 index 00000000000..6cf88b353ed --- /dev/null +++ b/src/components/script/dom/bindings/callback.rs @@ -0,0 +1,105 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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::{WrapNativeParent, Reflectable}; +use js::jsapi::{JSContext, JSObject, JS_WrapObject, JSVal, JS_ObjectIsCallable}; +use js::jsapi::JS_GetProperty; +use js::{JSVAL_IS_OBJECT, JSVAL_TO_OBJECT}; + +use std::libc; +use std::ptr; + +pub enum ExceptionHandling { + // Report any exception and don't throw it to the caller code. + eReportExceptions, + // Throw an exception to the caller code if the thrown exception is a + // binding object for a DOMError from the caller's scope, otherwise report + // it. + eRethrowContentExceptions, + // Throw any exception to the caller code. + eRethrowExceptions +} + +#[deriving(Clone,Eq)] +pub struct CallbackInterface { + callback: *JSObject +} + +pub trait CallbackContainer { + fn callback(&self) -> *JSObject; +} + +impl CallbackContainer for CallbackInterface { + fn callback(&self) -> *JSObject { + self.callback + } +} + +impl CallbackInterface { + pub fn new(callback: *JSObject) -> CallbackInterface { + CallbackInterface { + callback: callback + } + } + + #[fixed_stack_segment] + pub fn GetCallableProperty(&self, cx: *JSContext, name: *libc::c_char, callable: &mut JSVal) -> bool { + unsafe { + if JS_GetProperty(cx, self.callback, name, &*callable) == 0 { + return false; + } + + if !JSVAL_IS_OBJECT(*callable) || + JS_ObjectIsCallable(cx, JSVAL_TO_OBJECT(*callable)) == 0 { + //ThrowErrorMessage(cx, MSG_NOT_CALLABLE, description.get()); + return false; + } + + return true; + } + } +} + +pub fn GetJSObjectFromCallback(callback: &T) -> *JSObject { + callback.callback() +} + +#[fixed_stack_segment] +pub fn WrapCallThisObject(cx: *JSContext, + scope: *JSObject, + p: @mut T) -> *JSObject { + let mut obj = GetJSObjectFromCallback(p); + if obj.is_null() { + obj = WrapNativeParent(cx, scope, Some(p as @mut Reflectable)); + if obj.is_null() { + return ptr::null(); + } + } + + unsafe { + if JS_WrapObject(cx, &obj) == 0 { + return ptr::null(); + } + } + + return obj; +} + +pub struct CallSetup { + cx: *JSContext, + handling: ExceptionHandling +} + +impl CallSetup { + pub fn new(cx: *JSContext, handling: ExceptionHandling) -> CallSetup { + CallSetup { + cx: cx, + handling: handling + } + } + + pub fn GetContext(&self) -> *JSContext { + self.cx + } +} diff --git a/src/components/script/dom/bindings/codegen/Bindings.conf b/src/components/script/dom/bindings/codegen/Bindings.conf index 770bea35c33..6de264edc35 100644 --- a/src/components/script/dom/bindings/codegen/Bindings.conf +++ b/src/components/script/dom/bindings/codegen/Bindings.conf @@ -182,28 +182,21 @@ DOMInterfaces = { }, 'Event': { + 'nativeType': 'AbstractEvent', + 'concreteType': 'Event', + 'pointerType': '', }, -'EventListener': [ -{ +'EventListener': { + 'nativeType': 'EventListenerBinding::EventListener', }, -{ - 'workers': True, -}], -'EventTarget': [ -{ -# 'nativeType': 'nsDOMEventTargetHelper', -# 'hasInstanceInterface': 'nsIDOMEventTarget', -# 'concrete': False, -# 'prefable': True, +'EventTarget': { + 'nativeType': 'AbstractEventTarget', + 'concreteType': 'EventTarget', + 'pointerType': '', + 'needsAbstract': ['dispatchEvent'] }, -#{ -# 'workers': True, -# 'headerFile': 'mozilla/dom/workers/bindings/EventTarget.h', -# 'concrete': False -#} -], 'FileList': [ { @@ -291,6 +284,9 @@ DOMInterfaces = { }], 'MouseEvent': { + 'nativeType': 'AbstractEvent', + 'concreteType': 'MouseEvent', + 'pointerType': '', }, 'Navigator': { @@ -388,6 +384,9 @@ DOMInterfaces = { }], 'UIEvent': { + 'nativeType': 'AbstractEvent', + 'concreteType': 'UIEvent', + 'pointerType': '', }, 'ValidityState': { diff --git a/src/components/script/dom/bindings/codegen/CodegenRust.py b/src/components/script/dom/bindings/codegen/CodegenRust.py index 5734df036c6..b36f0ebd860 100644 --- a/src/components/script/dom/bindings/codegen/CodegenRust.py +++ b/src/components/script/dom/bindings/codegen/CodegenRust.py @@ -9,7 +9,7 @@ import string import operator from WebIDL import * -from Configuration import NoSuchDescriptorError +from Configuration import NoSuchDescriptorError, Descriptor AUTOGENERATED_WARNING_COMMENT = \ "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n" @@ -445,7 +445,10 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, treatNullAs="Default", treatUndefinedAs="Default", isEnforceRange=False, - isClamp=False): + isClamp=False, + exceptionCode=None, + isCallbackReturnValue=False, + sourceDescription="value"): """ Get a template for converting a JS value to a native object based on the given type and descriptor. If failureCode is given, then we're actually @@ -517,16 +520,40 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, # Also, we should not have a defaultValue if we know we're an object assert(not isDefinitelyObject or defaultValue is None) + # If exceptionCode is not set, we'll just rethrow the exception we got. + # Note that we can't just set failureCode to exceptionCode, because setting + # failureCode will prevent pending exceptions from being set in cases when + # they really should be! + if exceptionCode is None: + exceptionCode = "return 0;" + # We often want exceptionCode to be indented, since it often appears in an + # if body. + exceptionCodeIndented = CGIndenter(CGGeneric(exceptionCode)) + + # Unfortunately, .capitalize() on a string will lowercase things inside the + # string, which we do not want. + def firstCap(string): + return string[0].upper() + string[1:] + + # Helper functions for dealing with failures due to the JS value being the + # wrong type of value # Helper functions for dealing with failures due to the JS value being the # wrong type of value def onFailureNotAnObject(failureCode): - return CGWrapper(CGGeneric( + return CGWrapper( + CGGeneric( failureCode or - 'return 0; //XXXjdm return ThrowErrorMessage(cx, MSG_NOT_OBJECT);'), post="\n") + ('//XXXjdm ThrowErrorMessage(cx, MSG_NOT_OBJECT, "%s");\n' + '%s' % (firstCap(sourceDescription), exceptionCode))), + post="\n") def onFailureBadType(failureCode, typeName): - return CGWrapper(CGGeneric( + return CGWrapper( + CGGeneric( failureCode or - 'return 0; //XXXjdm return ThrowErrorMessage(cx, MSG_DOES_NOT_IMPLEMENT_INTERFACE, "%s");' % typeName), post="\n") + ('//XXXjdm ThrowErrorMessage(cx, MSG_DOES_NOT_IMPLEMENT_INTERFACE, "%s", "%s")\n;' + '%s' % (firstCap(sourceDescription), typeName, + exceptionCode))), + post="\n") # A helper function for handling default values. Takes a template # body and the C++ code to set the default value and wraps the @@ -886,6 +913,17 @@ for (uint32_t i = 0; i < length; ++i) { descriptor = descriptorProvider.getDescriptor( type.unroll().inner.identifier.name) + + if descriptor.interface.isCallback(): + name = descriptor.nativeType + declType = CGGeneric("Option<%s>" % name); + conversion = (" ${declName} = Some(%s::new(JSVAL_TO_OBJECT(${val})));\n" % name) + + template = wrapObjectTemplate(conversion, type, + "${declName} = None", + failureCode) + return (template, declType, None, isOptional, None) + # This is an interface that we implement as a concrete class # or an XPCOM interface. @@ -937,13 +975,6 @@ for (uint32_t i = 0; i < length; ++i) { "JSVAL_TO_OBJECT(${val})", "${declName}", isOptional or argIsPointer or type.nullable())) - elif descriptor.interface.isCallback() and False: - #XXXjdm unfinished - templateBody += str(CallbackObjectUnwrapper( - descriptor, - "&${val}.toObject()", - "${declName}", - codeOnFailure=failureCode)) elif descriptor.workers: templateBody += "${declName} = &${val}.toObject();" else: @@ -1218,6 +1249,12 @@ for (uint32_t i = 0; i < length; ++i) { return (template, declType, None, False, None) + if type.isVoid(): + assert not isOptional + # This one only happens for return values, and its easy: Just + # ignore the jsval. + return ("", None, None, False, None) + if not type.isPrimitive(): raise TypeError("Need conversion for argument type '%s'" % str(type)) @@ -1231,16 +1268,20 @@ for (uint32_t i = 0; i < length; ++i) { if type.nullable(): dataLoc = "${declName}.SetValue()" - nullCondition = "${val}.isNullOrUndefined()" + nullCondition = "(RUST_JSVAL_IS_NULL(${val}) != 0 || RUST_JSVAL_IS_VOID(${val}) != 0)" if defaultValue is not None and isinstance(defaultValue, IDLNullValue): nullCondition = "!(${haveValue}) || " + nullCondition + #XXXjdm support conversionBehavior here template = ( "if (%s) {\n" - " ${declName}.SetNull();\n" - "} else if (!ValueToPrimitive<%s, %s>(cx, ${val}, &%s)) {\n" - " return false;\n" - "}" % (nullCondition, typeName, conversionBehavior, dataLoc)) - declType = CGGeneric("Nullable<" + typeName + ">") + " ${declName} = None;\n" + "} else {\n" + " match JSValConvertible::from_jsval(${val}) {\n" + " Some(val_) => ${declName} = Some(val_),\n" + " None => return 0\n" + " }\n" + "}" % nullCondition) + declType = CGGeneric("Option<" + typeName + ">") else: assert(defaultValue is None or not isinstance(defaultValue, IDLNullValue)) @@ -1268,10 +1309,10 @@ for (uint32_t i = 0; i < length; ++i) { " %s = %s;\n" "}" % (dataLoc, defaultStr))).define() - if typeName != "bool": - return (template, declType, None, isOptional, "0 as %s" % typeName) - else: - return (template, declType, None, isOptional, "false") + initialVal = "false" if typeName == "bool" else ("0 as %s" % typeName) + if type.nullable(): + initialVal = "Some(%s)" % initialVal + return (template, declType, None, isOptional, initialVal) def instantiateJSToNativeConversionTemplate(templateTuple, replacements, argcAndIndex=None): @@ -1422,7 +1463,7 @@ class CGArgumentConverter(CGThing): self.argcAndIndex).define() def getWrapTemplateForType(type, descriptorProvider, result, successCode, - isCreator): + isCreator, exceptionCode): """ Reflect a C++ value stored in "result", of IDL type "type" into JS. The "successCode" is the code to run once we have successfully done the @@ -1435,6 +1476,10 @@ def getWrapTemplateForType(type, descriptorProvider, result, successCode, if not haveSuccessCode: successCode = "return 1;" + # We often want exceptionCode to be indented, since it often appears in an + # if body. + exceptionCodeIndented = CGIndenter(CGGeneric(exceptionCode)) + def setValue(value, callWrapValue=False): """ Returns the code to set the jsval to value. If "callWrapValue" is true @@ -1478,7 +1523,7 @@ def getWrapTemplateForType(type, descriptorProvider, result, successCode, # Nullable sequences are Nullable< nsTArray > (recTemplate, recInfall) = getWrapTemplateForType(type.inner, descriptorProvider, "%s.Value()" % result, successCode, - isCreator) + isCreator, exceptionCode) return (""" if (%s.IsNull()) { %s @@ -1540,8 +1585,8 @@ for (uint32_t i = 0; i < length; ++i) { # Non-prefable bindings can only fail to wrap as a new-binding object # if they already threw an exception. Same thing for # non-prefable bindings. - failed = ("//MOZ_ASSERT(JS_IsExceptionPending(cx));\n" + - "return 0;") + failed = ("assert!(unsafe { JS_IsExceptionPending(cx) != 0 });\n" + + "%s" % exceptionCode) else: if descriptor.notflattened: raise TypeError("%s is prefable but not flattened; " @@ -1611,7 +1656,7 @@ if %(resultStr)s.is_null() { if type.nullable(): (recTemplate, recInfal) = getWrapTemplateForType(type.inner, descriptorProvider, "%s.Value()" % result, successCode, - isCreator) + isCreator, exceptionCode) return ("if (%s.IsNull()) {\n" % result + CGIndenter(CGGeneric(setValue("JSVAL_NULL"))).define() + "\n" + "}\n" + recTemplate, recInfal) @@ -1661,7 +1706,9 @@ def wrapForType(type, descriptorProvider, templateValues): wrap = getWrapTemplateForType(type, descriptorProvider, templateValues.get('result', 'result'), templateValues.get('successCode', None), - templateValues.get('isCreator', False))[0] + templateValues.get('isCreator', False), + templateValues.get('exceptionCode', + "return 0;"),)[0] defaultValues = {'obj': 'obj'} return string.Template(wrap).substitute(defaultValues, **templateValues) @@ -2363,11 +2410,19 @@ class Argument(): """ A class for outputting the type and name of an argument """ - def __init__(self, argType, name): + def __init__(self, argType, name, default=None, mutable=False): self.argType = argType self.name = name - def __str__(self): - return self.name + ': ' + self.argType + self.default = default + self.mutable = mutable + def declare(self): + string = ('mut ' if self.mutable else '') + self.name + ((': ' + self.argType) if self.argType else '') + #XXXjdm Support default arguments somehow :/ + #if self.default is not None: + # string += " = " + self.default + return string + def define(self): + return self.argType + ' ' + self.name class CGAbstractMethod(CGThing): """ @@ -2408,8 +2463,8 @@ class CGAbstractMethod(CGThing): self.templateArgs = templateArgs self.pub = pub; self.unsafe = unsafe - def _argstring(self): - return ', '.join([str(a) for a in self.args]) + def _argstring(self, declare): + return ', '.join([a.declare() for a in self.args]) def _template(self): if self.templateArgs is None: return '' @@ -2450,13 +2505,13 @@ class CGAbstractMethod(CGThing): # self._argstring(), self._returnType()) return "" - def _define(self): - return self.definition_prologue() + "\n" + self.definition_body() + self.definition_epilogue() + def _define(self, fromDeclare=False): + return self.definition_prologue(fromDeclare) + "\n" + self.definition_body() + self.definition_epilogue() def define(self): return "" if self.inline else self._define() - def definition_prologue(self): + def definition_prologue(self, fromDeclare): return "%sfn %s%s(%s)%s {%s" % (self._decorators(), self.name, self._template(), - self._argstring(), self._returnType(), self._unsafe_open()) + self._argstring(fromDeclare), self._returnType(), self._unsafe_open()) def definition_epilogue(self): return "%s}\n" % self._unsafe_close() def definition_body(self): @@ -2885,6 +2940,21 @@ class CGDefineDOMInterfaceMethod(CGAbstractMethod): *aEnabled = true; return %s(aCx, global, aReceiver).is_not_null();""" % (getter)) +def isResultAlreadyAddRefed(extendedAttributes): + return not 'resultNotAddRefed' in extendedAttributes + +def needCx(returnType, arguments, extendedAttributes, considerTypes): + return (considerTypes and + (typeNeedsCx(returnType, True) or + any(typeNeedsCx(a.type) for a in arguments)) or + 'implicitJSContext' in extendedAttributes) + +def needScopeObject(returnType, arguments, extendedAttributes, + isWrapperCached, considerTypes): + return (considerTypes and not isWrapperCached and + (typeNeedsScopeObject(returnType, True) or + any(typeNeedsScopeObject(a.type) for a in arguments))) + class CGCallGenerator(CGThing): """ A class to generate an actual call to a C++ object. Assumes that the C++ @@ -3386,7 +3456,7 @@ def infallibleForMember(member, type, descriptorProvider): failure conditions. """ return getWrapTemplateForType(type, descriptorProvider, 'result', None,\ - memberIsCreator(member))[1] + memberIsCreator(member), "return false;",)[1] class CGMemberJITInfo(CGThing): """ @@ -3488,6 +3558,466 @@ class CGEnum(CGThing): """ % (",\n ".join(map(getEnumValueName, self.enum.values())), ",\n ".join(['EnumEntry {value: &"' + val + '", length: ' + str(len(val)) + '}' for val in self.enum.values()])) +class ClassItem: + """ Use with CGClass """ + def __init__(self, name, visibility): + self.name = name + self.visibility = visibility + def declare(self, cgClass): + assert False + def define(self, cgClass): + assert False + +class ClassBase(ClassItem): + def __init__(self, name, visibility='pub'): + ClassItem.__init__(self, name, visibility) + def declare(self, cgClass): + return '%s %s' % (self.visibility, self.name) + def define(self, cgClass): + # Only in the header + return '' + +class ClassMethod(ClassItem): + def __init__(self, name, returnType, args, inline=False, static=False, + virtual=False, const=False, bodyInHeader=False, + templateArgs=None, visibility='public', body=None, + breakAfterReturnDecl="\n", + breakAfterSelf="\n", override=False): + """ + override indicates whether to flag the method as MOZ_OVERRIDE + """ + assert not override or virtual + self.returnType = returnType + self.args = args + self.inline = False + self.static = static + self.virtual = virtual + self.const = const + self.bodyInHeader = True + self.templateArgs = templateArgs + self.body = body + self.breakAfterReturnDecl = breakAfterReturnDecl + self.breakAfterSelf = breakAfterSelf + self.override = override + ClassItem.__init__(self, name, visibility) + + def getDecorators(self, declaring): + decorators = ['#[fixed_stack_segment]'] + if self.inline: + decorators.append('inline') + if declaring: + if self.static: + decorators.append('static') + if self.virtual: + decorators.append('virtual') + if decorators: + return ' '.join(decorators) + ' ' + return '' + + def getBody(self): + # Override me or pass a string to constructor + assert self.body is not None + return self.body + + def declare(self, cgClass): + templateClause = '<%s>' % ', '.join(self.templateArgs) \ + if self.bodyInHeader and self.templateArgs else '' + args = ', '.join([a.declare() for a in self.args]) + if self.bodyInHeader: + body = CGIndenter(CGGeneric(self.getBody())).define() + body = ' {\n' + body + '\n}' + else: + body = ';' + + return string.Template("${decorators}%s" + "${visibility}fn ${name}${templateClause}(${args})${returnType}${const}${override}${body}%s" % + (self.breakAfterReturnDecl, self.breakAfterSelf) + ).substitute({ + 'templateClause': templateClause, + 'decorators': self.getDecorators(True), + 'returnType': (" -> %s" % self.returnType) if self.returnType else "", + 'name': self.name, + 'const': ' const' if self.const else '', + 'override': ' MOZ_OVERRIDE' if self.override else '', + 'args': args, + 'body': body, + 'visibility': self.visibility + ' ' if self.visibility is not 'priv' else '' + }) + + def define(self, cgClass): + pass + +class ClassUsingDeclaration(ClassItem): + """" + Used for importing a name from a base class into a CGClass + + baseClass is the name of the base class to import the name from + + name is the name to import + + visibility determines the visibility of the name (public, + protected, private), defaults to public. + """ + def __init__(self, baseClass, name, visibility='public'): + self.baseClass = baseClass + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return string.Template("""using ${baseClass}::${name}; +""").substitute({ 'baseClass': self.baseClass, + 'name': self.name }) + + def define(self, cgClass): + return '' + +class ClassConstructor(ClassItem): + """ + Used for adding a constructor to a CGClass. + + args is a list of Argument objects that are the arguments taken by the + constructor. + + inline should be True if the constructor should be marked inline. + + bodyInHeader should be True if the body should be placed in the class + declaration in the header. + + visibility determines the visibility of the constructor (public, + protected, private), defaults to private. + + explicit should be True if the constructor should be marked explicit. + + baseConstructors is a list of strings containing calls to base constructors, + defaults to None. + + body contains a string with the code for the constructor, defaults to empty. + """ + def __init__(self, args, inline=False, bodyInHeader=False, + visibility="priv", explicit=False, baseConstructors=None, + body=""): + self.args = args + self.inline = False + self.bodyInHeader = bodyInHeader + self.explicit = explicit + self.baseConstructors = baseConstructors or [] + self.body = body + ClassItem.__init__(self, None, visibility) + + def getDecorators(self, declaring): + decorators = [] + if self.explicit: + decorators.append('explicit') + if self.inline and declaring: + decorators.append('inline') + if decorators: + return ' '.join(decorators) + ' ' + return '' + + def getInitializationList(self, cgClass): + items = [str(c) for c in self.baseConstructors] + for m in cgClass.members: + if not m.static: + initialize = m.body + if initialize: + items.append(m.name + "(" + initialize + ")") + + if len(items) > 0: + return '\n : ' + ',\n '.join(items) + return '' + + def getBody(self, cgClass): + initializers = [" parent: %s" % str(self.baseConstructors[0])] + return (self.body + ( + "%s {\n" + "%s\n" + "}") % (cgClass.name, '\n'.join(initializers))) + + def declare(self, cgClass): + args = ', '.join([a.declare() for a in self.args]) + body = ' ' + self.getBody(cgClass); + body = stripTrailingWhitespace(body.replace('\n', '\n ')) + if len(body) > 0: + body += '\n' + body = ' {\n' + body + '}' + + return string.Template("""pub fn ${decorators}new(${args}) -> ${className}${body} +""").substitute({ 'decorators': self.getDecorators(True), + 'className': cgClass.getNameString(), + 'args': args, + 'body': body }) + + def define(self, cgClass): + if self.bodyInHeader: + return '' + + args = ', '.join([a.define() for a in self.args]) + + body = ' ' + self.getBody() + body = '\n' + stripTrailingWhitespace(body.replace('\n', '\n ')) + if len(body) > 0: + body += '\n' + + return string.Template("""${decorators} +${className}::${className}(${args})${initializationList} +{${body}} +""").substitute({ 'decorators': self.getDecorators(False), + 'className': cgClass.getNameString(), + 'args': args, + 'initializationList': self.getInitializationList(cgClass), + 'body': body }) + +class ClassDestructor(ClassItem): + """ + Used for adding a destructor to a CGClass. + + inline should be True if the destructor should be marked inline. + + bodyInHeader should be True if the body should be placed in the class + declaration in the header. + + visibility determines the visibility of the destructor (public, + protected, private), defaults to private. + + body contains a string with the code for the destructor, defaults to empty. + + virtual determines whether the destructor is virtual, defaults to False. + """ + def __init__(self, inline=False, bodyInHeader=False, + visibility="private", body='', virtual=False): + self.inline = inline or bodyInHeader + self.bodyInHeader = bodyInHeader + self.body = body + self.virtual = virtual + ClassItem.__init__(self, None, visibility) + + def getDecorators(self, declaring): + decorators = [] + if self.virtual and declaring: + decorators.append('virtual') + if self.inline and declaring: + decorators.append('inline') + if decorators: + return ' '.join(decorators) + ' ' + return '' + + def getBody(self): + return self.body + + def declare(self, cgClass): + if self.bodyInHeader: + body = ' ' + self.getBody(); + body = stripTrailingWhitespace(body.replace('\n', '\n ')) + if len(body) > 0: + body += '\n' + body = '\n{\n' + body + '}' + else: + body = ';' + + return string.Template("""${decorators}~${className}()${body} +""").substitute({ 'decorators': self.getDecorators(True), + 'className': cgClass.getNameString(), + 'body': body }) + + def define(self, cgClass): + if self.bodyInHeader: + return '' + + body = ' ' + self.getBody() + body = '\n' + stripTrailingWhitespace(body.replace('\n', '\n ')) + if len(body) > 0: + body += '\n' + + return string.Template("""${decorators} +${className}::~${className}() +{${body}} +""").substitute({ 'decorators': self.getDecorators(False), + 'className': cgClass.getNameString(), + 'body': body }) + +class ClassMember(ClassItem): + def __init__(self, name, type, visibility="priv", static=False, + body=None): + self.type = type; + self.static = static + self.body = body + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return '%s: %s,\n' % (self.name, self.type) + + def define(self, cgClass): + if not self.static: + return '' + if self.body: + body = " = " + self.body + else: + body = "" + return '%s %s::%s%s;\n' % (self.type, cgClass.getNameString(), + self.name, body) + +class ClassTypedef(ClassItem): + def __init__(self, name, type, visibility="public"): + self.type = type + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return 'typedef %s %s;\n' % (self.type, self.name) + + def define(self, cgClass): + # Only goes in the header + return '' + +class ClassEnum(ClassItem): + def __init__(self, name, entries, values=None, visibility="public"): + self.entries = entries + self.values = values + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + entries = [] + for i in range(0, len(self.entries)): + if not self.values or i >= len(self.values): + entry = '%s' % self.entries[i] + else: + entry = '%s = %s' % (self.entries[i], self.values[i]) + entries.append(entry) + name = '' if not self.name else ' ' + self.name + return 'enum%s\n{\n %s\n};\n' % (name, ',\n '.join(entries)) + + def define(self, cgClass): + # Only goes in the header + return '' + +class ClassUnion(ClassItem): + def __init__(self, name, entries, visibility="public"): + self.entries = [entry + ";" for entry in entries] + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return 'union %s\n{\n %s\n};\n' % (self.name, '\n '.join(self.entries)) + + def define(self, cgClass): + # Only goes in the header + return '' + +class CGClass(CGThing): + def __init__(self, name, bases=[], members=[], constructors=[], + destructor=None, methods=[], + typedefs = [], enums=[], unions=[], templateArgs=[], + templateSpecialization=[], isStruct=False, + disallowCopyConstruction=False, indent='', + decorators='', + extradeclarations='', + extradefinitions=''): + CGThing.__init__(self) + self.name = name + self.bases = bases + self.members = members + self.constructors = constructors + # We store our single destructor in a list, since all of our + # code wants lists of members. + self.destructors = [destructor] if destructor else [] + self.methods = methods + self.typedefs = typedefs + self.enums = enums + self.unions = unions + self.templateArgs = templateArgs + self.templateSpecialization = templateSpecialization + self.isStruct = isStruct + self.disallowCopyConstruction = disallowCopyConstruction + self.indent = indent + self.defaultVisibility ='pub' if isStruct else 'priv' + self.decorators = decorators + self.extradeclarations = extradeclarations + self.extradefinitions = extradefinitions + + def getNameString(self): + className = self.name + if self.templateSpecialization: + className = className + \ + '<%s>' % ', '.join([str(a) for a + in self.templateSpecialization]) + return className + + def declare(self): + result = '' + if self.templateArgs: + templateArgs = [a.declare() for a in self.templateArgs] + templateArgs = templateArgs[len(self.templateSpecialization):] + result = result + self.indent + 'template <%s>\n' \ + % ','.join([str(a) for a in templateArgs]) + + if self.templateSpecialization: + specialization = \ + '<%s>' % ', '.join([str(a) for a in self.templateSpecialization]) + else: + specialization = '' + + myself = '' + if self.decorators != '': + myself += self.decorators + '\n' + myself += '%spub struct %s%s' % (self.indent, self.name, specialization) + result += myself + + assert len(self.bases) == 1 #XXjdm Can we support multiple inheritance? + + result += '{\n%s\n' % self.indent + + if self.bases: + self.members = [ClassMember("parent", self.bases[0].name, "pub")] + self.members + + result += CGIndenter(CGGeneric(self.extradeclarations), + len(self.indent)).define() + + def declareMembers(cgClass, memberList): + result = '' + + for member in memberList: + declaration = member.declare(cgClass) + declaration = CGIndenter(CGGeneric(declaration)).define() + result = result + declaration + return result + + if self.disallowCopyConstruction: + class DisallowedCopyConstructor(object): + def __init__(self): + self.visibility = "private" + def declare(self, cgClass): + name = cgClass.getNameString() + return ("%s(const %s&) MOZ_DELETE;\n" + "void operator=(const %s) MOZ_DELETE;\n" % (name, name, name)) + disallowedCopyConstructors = [DisallowedCopyConstructor()] + else: + disallowedCopyConstructors = [] + + order = [(self.enums, ''), (self.unions, ''), + (self.typedefs, ''), (self.members, '')] + + for (memberList, separator) in order: + memberString = declareMembers(self, memberList) + if self.indent: + memberString = CGIndenter(CGGeneric(memberString), + len(self.indent)).define() + result = result + memberString + + result += self.indent + '}\n\n' + result += 'impl %s {\n' % self.name + + order = [(self.constructors + disallowedCopyConstructors, '\n'), + (self.destructors, '\n'), (self.methods, '\n)')] + for (memberList, separator) in order: + memberString = declareMembers(self, memberList) + if self.indent: + memberString = CGIndenter(CGGeneric(memberString), + len(self.indent)).define() + result = result + memberString + + result += "}" + return result + + def define(self): + return '' + class CGXrayHelper(CGAbstractExternMethod): def __init__(self, descriptor, name, args, properties): CGAbstractExternMethod.__init__(self, descriptor, name, "bool", args) @@ -4610,10 +5140,15 @@ class CGBindingRoot(CGThing): def __init__(self, config, prefix, webIDLFile): descriptors = config.getDescriptors(webIDLFile=webIDLFile, hasInterfaceOrInterfacePrototypeObject=True) - dictionaries = config.getDictionaries(webIDLFile) + dictionaries = config.getDictionaries(webIDLFile=webIDLFile) cgthings = [] + mainCallbacks = config.getCallbacks(webIDLFile=webIDLFile, + workers=False) + callbackDescriptors = config.getDescriptors(webIDLFile=webIDLFile, + isCallback=True) + # Do codegen for all the enums def makeEnum(e): return CGNamespace.build([e.identifier.name + "Values"], @@ -4654,9 +5189,17 @@ class CGBindingRoot(CGThing): cgthings.extend([CGDictionary(d, config.getDescriptorProvider(False)) for d in dictionaries]) + # Do codegen for all the callbacks. + cgthings.extend(CGCallbackFunction(c, config.getDescriptorProvider(False)) + for c in mainCallbacks) + # Do codegen for all the descriptors cgthings.extend([CGDescriptor(x) for x in descriptors]) + # Do codegen for all the callback interfaces. Skip worker callbacks. + cgthings.extend([CGCallbackInterface(x) for x in callbackDescriptors if + not x.workers]) + # And make sure we have the right number of newlines at the end curr = CGWrapper(CGList(cgthings, "\n\n"), post="\n\n") @@ -4675,14 +5218,16 @@ class CGBindingRoot(CGThing): 'js::glue::*', 'dom::types::*', 'dom::bindings::utils::*', + 'dom::bindings::callback::*', 'dom::bindings::conversions::*', 'dom::bindings::codegen::*', #XXXjdm 'script_task::{JSPageInfo, page_from_context}', - 'dom::bindings::utils::EnumEntry', 'dom::bindings::proxyhandler', 'dom::bindings::proxyhandler::*', 'dom::document::AbstractDocument', 'dom::node::{AbstractNode, ScriptView}', + 'dom::eventtarget::AbstractEventTarget', + 'dom::event::AbstractEvent', 'servo_util::vec::zip_copies', 'std::cast', 'std::libc', @@ -4705,6 +5250,897 @@ class CGBindingRoot(CGThing): def define(self): return stripTrailingWhitespace(self.root.define()) +class CGNativeMember(ClassMethod): + def __init__(self, descriptorProvider, member, name, signature, extendedAttrs, + breakAfter=True, passJSBitsAsNeeded=True, visibility="public", + jsObjectsArePtr=False, variadicIsSequence=False): + """ + If jsObjectsArePtr is true, typed arrays and "object" will be + passed as JSObject*. + + If passJSBitsAsNeeded is false, we don't automatically pass in a + JSContext* or a JSObject* based on the return and argument types. We + can still pass it based on 'implicitJSContext' annotations. + """ + self.descriptorProvider = descriptorProvider + self.member = member + self.extendedAttrs = extendedAttrs + self.resultAlreadyAddRefed = isResultAlreadyAddRefed(self.extendedAttrs) + self.passJSBitsAsNeeded = passJSBitsAsNeeded + self.jsObjectsArePtr = jsObjectsArePtr + self.variadicIsSequence = variadicIsSequence + breakAfterSelf = "\n" if breakAfter else "" + ClassMethod.__init__(self, name, + self.getReturnType(signature[0], False), + self.getArgs(signature[0], signature[1]), + static=member.isStatic(), + # Mark our getters, which are attrs that + # have a non-void return type, as const. + const=(not member.isStatic() and member.isAttr() and + not signature[0].isVoid()), + breakAfterReturnDecl=" ", + breakAfterSelf=breakAfterSelf, + visibility=visibility) + + def getReturnType(self, type, isMember): + return self.getRetvalInfo(type, isMember)[0] + + def getRetvalInfo(self, type, isMember): + """ + Returns a tuple: + + The first element is the type declaration for the retval + + The second element is a default value that can be used on error returns. + For cases whose behavior depends on isMember, the second element will be + None if isMember is true. + + The third element is a template for actually returning a value stored in + "${declName}" and "${holderName}". This means actually returning it if + we're not outparam, else assigning to the "retval" outparam. If + isMember is true, this can be None, since in that case the caller will + never examine this value. + """ + if type.isVoid(): + typeDecl, errorDefault, template = "", "", "" + elif type.isPrimitive() and type.tag() in builtinNames: + result = CGGeneric(builtinNames[type.tag()]) + defaultReturnArg = "0" + if type.nullable(): + result = CGTemplatedType("Nullable", result) + defaultReturnArg = "" + typeDecl, errorDefault, template = \ + (result.define(), + "%s(%s)" % (result.define(), defaultReturnArg), + "return ${declName};") + elif type.isDOMString(): + if isMember: + # No need for a third element in the isMember case + typeDecl, errorDefault, template = "nsString", None, None + # Outparam + else: + typeDecl, errorDefault, template = "void", "", "retval = ${declName};" + elif type.isByteString(): + if isMember: + # No need for a third element in the isMember case + typeDecl, errorDefault, template = "nsCString", None, None + # Outparam + typeDecl, errorDefault, template = "void", "", "retval = ${declName};" + elif type.isEnum(): + enumName = type.unroll().inner.identifier.name + if type.nullable(): + enumName = CGTemplatedType("Nullable", + CGGeneric(enumName)).define() + defaultValue = "%s()" % enumName + else: + defaultValue = "%s(0)" % enumName + typeDecl, errorDefault, template = enumName, defaultValue, "return ${declName};" + elif type.isGeckoInterface(): + iface = type.unroll().inner; + nativeType = self.descriptorProvider.getDescriptor( + iface.identifier.name).nativeType + # Now trim off unnecessary namespaces + nativeType = nativeType.split("::") + if nativeType[0] == "mozilla": + nativeType.pop(0) + if nativeType[0] == "dom": + nativeType.pop(0) + result = CGGeneric("::".join(nativeType)) + if self.resultAlreadyAddRefed: + if isMember: + holder = "nsRefPtr" + else: + holder = "already_AddRefed" + if memberReturnsNewObject(self.member): + warning = "" + else: + warning = "// Mark this as resultNotAddRefed to return raw pointers\n" + result = CGWrapper(result, + pre=("%s%s<" % (warning, holder)), + post=">") + else: + result = CGWrapper(result, post="*") + # Since we always force an owning type for callback return values, + # our ${declName} is an OwningNonNull or nsRefPtr. So we can just + # .forget() to get our already_AddRefed. + typeDecl, errorDefault, template = \ + result.define(), "nullptr", "return ${declName}.forget();" + elif type.isCallback(): + typeDecl, errorDefault, template = \ + ("already_AddRefed<%s>" % type.unroll().identifier.name, + "nullptr", "return ${declName}.forget();") + elif type.isAny(): + typeDecl, errorDefault, template = \ + "JS::Value", "JS::UndefinedValue()", "return ${declName};" + elif type.isObject(): + typeDecl, errorDefault, template = \ + "JSObject*", "nullptr", "return ${declName};" + elif type.isSpiderMonkeyInterface(): + if type.nullable(): + returnCode = "return ${declName}.IsNull() ? nullptr : ${declName}.Value().Obj();" + else: + returnCode = "return ${declName}.Obj();" + typeDecl, errorDefault, template = "JSObject*", "nullptr", returnCode + elif type.isSequence(): + # If we want to handle sequence-of-sequences return values, we're + # going to need to fix example codegen to not produce nsTArray + # for the relevant argument... + assert not isMember + # Outparam. + if type.nullable(): + returnCode = ("if (${declName}.IsNull()) {\n" + " retval.SetNull();\n" + "} else {\n" + " retval.SetValue().SwapElements(${declName}.Value());\n" + "}") + else: + returnCode = "retval.SwapElements(${declName});" + typeDecl, errorDefault, template = "void", "", returnCode + elif type.isDate(): + result = CGGeneric("Date") + if type.nullable(): + result = CGTemplatedType("Nullable", result) + typeDecl, errorDefault, template = \ + (result.define(), "%s()" % result.define(), "return ${declName};") + else: + raise TypeError("Don't know how to declare return value for %s" % type) + + if not 'infallible' in self.extendedAttrs: + if typeDecl: + typeDecl = "Fallible<%s>" % typeDecl + else: + typeDecl = "ErrorResult" + if not errorDefault: + errorDefault = "Err(FailureUnknown)" + if not template: + template = "return Ok(());" + return typeDecl, errorDefault, template + + def getArgs(self, returnType, argList): + args = [self.getArg(arg) for arg in argList] + # Now the outparams + if returnType.isDOMString(): + args.append(Argument("nsString&", "retval")) + if returnType.isByteString(): + args.append(Argument("nsCString&", "retval")) + elif returnType.isSequence(): + nullable = returnType.nullable() + if nullable: + returnType = returnType.inner + # And now the actual underlying type + elementDecl = self.getReturnType(returnType.inner, True) + type = CGTemplatedType("nsTArray", CGGeneric(elementDecl)) + if nullable: + type = CGTemplatedType("Nullable", type) + args.append(Argument("%s&" % type.define(), "retval")) + # The legacycaller thisval + if self.member.isMethod() and self.member.isLegacycaller(): + # If it has an identifier, we can't deal with it yet + assert self.member.isIdentifierLess() + args.insert(0, Argument("JS::Value", "aThisVal")) + # And jscontext bits. + if needCx(returnType, argList, self.extendedAttrs, + self.passJSBitsAsNeeded): + args.insert(0, Argument("JSContext*", "cx")) + if needScopeObject(returnType, argList, self.extendedAttrs, + self.descriptorProvider, + self.passJSBitsAsNeeded): + args.insert(1, Argument("JS::Handle", "obj")) + # And if we're static, a global + if self.member.isStatic(): + args.insert(0, Argument("const GlobalObject&", "global")) + return args + + def doGetArgType(self, type, optional, isMember): + """ + The main work of getArgType. Returns a string type decl, whether this + is a const ref, as well as whether the type should be wrapped in + Nullable as needed. + + isMember can be false or one of the strings "Sequence" or "Variadic" + """ + if type.isArray(): + raise TypeError("Can't handle array arguments yet") + + if type.isSequence(): + nullable = type.nullable() + if nullable: + type = type.inner + elementType = type.inner + argType = self.getArgType(elementType, False, "Sequence")[0] + decl = CGTemplatedType("Sequence", argType) + return decl.define(), True, True + + if type.isUnion(): + if type.nullable(): + type = type.inner + return str(type), True, True + + if type.isGeckoInterface() and not type.isCallbackInterface(): + iface = type.unroll().inner + argIsPointer = type.nullable() or iface.isExternal() + forceOwningType = iface.isCallback() or isMember + if argIsPointer: + if (optional or isMember) and forceOwningType: + typeDecl = "nsRefPtr<%s>" + else: + typeDecl = "*%s" + else: + if optional or isMember: + if forceOwningType: + typeDecl = "OwningNonNull<%s>" + else: + typeDecl = "NonNull<%s>" + else: + typeDecl = "%s%s" + descriptor = self.descriptorProvider.getDescriptor(iface.identifier.name) + return (typeDecl % (descriptor.pointerType, descriptor.nativeType), + False, False) + + if type.isSpiderMonkeyInterface(): + if self.jsObjectsArePtr: + return "JSObject*", False, False + + return type.name, True, True + + if type.isDOMString(): + if isMember: + declType = "nsString" + else: + declType = "nsAString" + return declType, True, False + + if type.isByteString(): + declType = "nsCString" + return declType, True, False + + if type.isEnum(): + return type.unroll().inner.identifier.name, False, True + + if type.isCallback() or type.isCallbackInterface(): + forceOwningType = optional or isMember + if type.nullable(): + if forceOwningType: + declType = "nsRefPtr<%s>" + else: + declType = "%s*" + else: + if forceOwningType: + declType = "OwningNonNull<%s>" + else: + declType = "%s&" + if type.isCallback(): + name = type.unroll().identifier.name + else: + name = type.unroll().inner.identifier.name + return declType % name, False, False + + if type.isAny(): + # Don't do the rooting stuff for variadics for now + if isMember: + declType = "JS::Value" + else: + declType = "JS::Handle" + return declType, False, False + + if type.isObject(): + if isMember: + declType = "JSObject*" + else: + declType = "JS::Handle" + return declType, False, False + + if type.isDictionary(): + typeName = CGDictionary.makeDictionaryName(type.inner) + return typeName, True, True + + if type.isDate(): + return "Date", False, True + + assert type.isPrimitive() + + return builtinNames[type.tag()], False, True + + def getArgType(self, type, optional, isMember): + """ + Get the type of an argument declaration. Returns the type CGThing, and + whether this should be a const ref. + + isMember can be False, "Sequence", or "Variadic" + """ + (decl, ref, handleNullable) = self.doGetArgType(type, optional, + isMember) + decl = CGGeneric(decl) + if handleNullable and type.nullable(): + decl = CGTemplatedType("Nullable", decl) + ref = True + if isMember == "Variadic": + arrayType = "Sequence" if self.variadicIsSequence else "nsTArray" + decl = CGTemplatedType(arrayType, decl) + ref = True + elif optional: + # Note: All variadic args claim to be optional, but we can just use + # empty arrays to represent them not being present. + decl = CGTemplatedType("Optional", decl) + ref = True + return (decl, ref) + + def getArg(self, arg): + """ + Get the full argument declaration for an argument + """ + (decl, ref) = self.getArgType(arg.type, + arg.optional and not arg.defaultValue, + "Variadic" if arg.variadic else False) + if ref: + decl = CGWrapper(decl, pre="const ", post="&") + + return Argument(decl.define(), arg.identifier.name) + +def isJSImplementedDescriptor(descriptorProvider): + return (isinstance(descriptorProvider, Descriptor) and + descriptorProvider.interface.isJSImplemented()) + +class CGCallback(CGClass): + def __init__(self, idlObject, descriptorProvider, baseName, methods, + getters=[], setters=[]): + self.baseName = baseName + self._deps = idlObject.getDeps() + name = idlObject.identifier.name + if isJSImplementedDescriptor(descriptorProvider): + name = jsImplName(name) + # For our public methods that needThisHandling we want most of the + # same args and the same return type as what CallbackMember + # generates. So we want to take advantage of all its + # CGNativeMember infrastructure, but that infrastructure can't deal + # with templates and most especially template arguments. So just + # cheat and have CallbackMember compute all those things for us. + realMethods = [] + for method in methods: + if not method.needThisHandling: + realMethods.append(method) + else: + realMethods.extend(self.getMethodImpls(method)) + CGClass.__init__(self, name, + bases=[ClassBase(baseName)], + constructors=self.getConstructors(), + methods=realMethods+getters+setters, + decorators="#[deriving(Eq,Clone)]") + + def getConstructors(self): + return [ClassConstructor( + [Argument("*JSObject", "aCallback")], + bodyInHeader=True, + visibility="pub", + explicit=False, + baseConstructors=[ + "%s::new(aCallback)" % self.baseName + ])] + + def getMethodImpls(self, method): + assert method.needThisHandling + args = list(method.args) + # Strip out the JSContext*/JSObject* args + # that got added. + assert args[0].name == "cx" and args[0].argType == "*JSContext" + assert args[1].name == "aThisObj" and args[1].argType == "*JSObject" + args = args[2:] + # Record the names of all the arguments, so we can use them when we call + # the private method. + argnames = [arg.name for arg in args] + argnamesWithThis = ["s.GetContext()", "thisObjJS"] + argnames + argnamesWithoutThis = ["s.GetContext()", "JSVAL_TO_OBJECT(JSVAL_NULL)"] + argnames + # Now that we've recorded the argnames for our call to our private + # method, insert our optional argument for deciding whether the + # CallSetup should re-throw exceptions on aRv. + args.append(Argument("ExceptionHandling", "aExceptionHandling", + "eReportExceptions")) + + # Ensure the first argument is mutable + args[0] = Argument(args[0].argType, args[0].name, args[0].default, mutable=True) + method.args[2] = args[0] + + # And now insert our template argument. + argsWithoutThis = list(args) + args.insert(0, Argument("@mut T", "thisObj")) + + # And the self argument + method.args.insert(0, Argument(None, "&self")) + args.insert(0, Argument(None, "&self")) + argsWithoutThis.insert(0, Argument(None, "&self")) + + setupCall = ("let s = CallSetup::new(cx_for_dom_object(&mut ${cxProvider}), aExceptionHandling);\n" + "if s.GetContext().is_null() {\n" + " return${errorReturn};\n" + "}\n") + + bodyWithThis = string.Template( + setupCall+ + "let thisObjJS = WrapCallThisObject(s.GetContext(), ptr::null() /*XXXjdm proper scope*/, thisObj);\n" + "if thisObjJS.is_null() {\n" + " return${errorReturn};\n" + "}\n" + "return ${methodName}(${callArgs});").substitute({ + "errorReturn" : method.getDefaultRetval(), + "callArgs" : ", ".join(argnamesWithThis), + "methodName": 'self.' + method.name, + "cxProvider": '*thisObj' + }) + bodyWithoutThis = string.Template( + setupCall + + "return ${methodName}(${callArgs});").substitute({ + "errorReturn" : method.getDefaultRetval(), + "callArgs" : ", ".join(argnamesWithoutThis), + "methodName": 'self.' + method.name, + "cxProvider": args[2].name #XXXjdm There's no guarantee that this is a DOM object + }) + return [ClassMethod(method.name+'_', method.returnType, args, + bodyInHeader=True, + templateArgs=["T: 'static+CallbackContainer+Reflectable"], + body=bodyWithThis, + visibility='pub'), + ClassMethod(method.name+'__', method.returnType, argsWithoutThis, + bodyInHeader=True, + body=bodyWithoutThis, + visibility='pub'), + method] + + def deps(self): + return self._deps + +# We're always fallible +def callbackGetterName(attr): + return "Get" + MakeNativeName(attr.identifier.name) + +def callbackSetterName(attr): + return "Set" + MakeNativeName(attr.identifier.name) + +class CGCallbackFunction(CGCallback): + def __init__(self, callback, descriptorProvider): + CGCallback.__init__(self, callback, descriptorProvider, + "CallbackFunction", + methods=[CallCallback(callback, descriptorProvider)]) + + def getConstructors(self): + return CGCallback.getConstructors(self) + [ + ClassConstructor( + [Argument("CallbackFunction*", "aOther")], + bodyInHeader=True, + visibility="pub", + explicit=True, + baseConstructors=[ + "CallbackFunction(aOther)" + ])] + +class CGCallbackInterface(CGCallback): + def __init__(self, descriptor): + iface = descriptor.interface + attrs = [m for m in iface.members if m.isAttr() and not m.isStatic()] + getters = [CallbackGetter(a, descriptor) for a in attrs] + setters = [CallbackSetter(a, descriptor) for a in attrs + if not a.readonly] + methods = [m for m in iface.members + if m.isMethod() and not m.isStatic() and not m.isIdentifierLess()] + methods = [CallbackOperation(m, sig, descriptor) for m in methods + for sig in m.signatures()] + if iface.isJSImplemented() and iface.ctor(): + sigs = descriptor.interface.ctor().signatures() + if len(sigs) != 1: + raise TypeError("We only handle one constructor. See bug 869268.") + methods.append(CGJSImplInitOperation(sigs[0], descriptor)) + CGCallback.__init__(self, iface, descriptor, "CallbackInterface", + methods, getters=getters, setters=setters) + +class FakeMember(): + def __init__(self): + self.treatUndefinedAs = self.treatNullAs = "Default" + def isStatic(self): + return False + def isAttr(self): + return False + def isMethod(self): + return False + def getExtendedAttribute(self, name): + # Claim to be a [NewObject] so we can avoid the "mark this + # resultNotAddRefed" comments CGNativeMember codegen would + # otherwise stick in. + if name == "NewObject": + return True + return None + +class CallbackMember(CGNativeMember): + def __init__(self, sig, name, descriptorProvider, needThisHandling, rethrowContentException=False): + """ + needThisHandling is True if we need to be able to accept a specified + thisObj, False otherwise. + """ + assert not rethrowContentException or not needThisHandling + + self.retvalType = sig[0] + self.originalSig = sig + args = sig[1] + self.argCount = len(args) + if self.argCount > 0: + # Check for variadic arguments + lastArg = args[self.argCount-1] + if lastArg.variadic: + self.argCountStr = ( + "(%d - 1) + %s.Length()" % (self.argCount, + lastArg.identifier.name)) + else: + self.argCountStr = "%d" % self.argCount + self.needThisHandling = needThisHandling + # If needThisHandling, we generate ourselves as private and the caller + # will handle generating public versions that handle the "this" stuff. + visibility = "priv" if needThisHandling else "pub" + self.rethrowContentException = rethrowContentException + # We don't care, for callback codegen, whether our original member was + # a method or attribute or whatnot. Just always pass FakeMember() + # here. + CGNativeMember.__init__(self, descriptorProvider, FakeMember(), + name, (self.retvalType, args), + extendedAttrs={}, + passJSBitsAsNeeded=False, + visibility=visibility, + jsObjectsArePtr=True) + # We have to do all the generation of our body now, because + # the caller relies on us throwing if we can't manage it. + self.exceptionCode= "return Err(FailureUnknown);\n" + self.body = self.getImpl() + + def getImpl(self): + replacements = { + "declRval": self.getRvalDecl(), + "errorReturn" : self.getDefaultRetval(), + "returnResult": self.getResultConversion(), + "convertArgs": self.getArgConversions(), + "doCall": self.getCall(), + "setupCall": self.getCallSetup(), + } + if self.argCount > 0: + replacements["argCount"] = self.argCountStr + replacements["argvDecl"] = string.Template( + "let mut argv = vec::from_elem(${argCount}, JSVAL_VOID);\n" + ).substitute(replacements) + else: + # Avoid weird 0-sized arrays + replacements["argvDecl"] = "" + + return string.Template( + # Newlines and semicolons are in the values + "${setupCall}" + "${declRval}" + "${argvDecl}" + "${convertArgs}" + "${doCall}" + "${returnResult}").substitute(replacements) + + def getResultConversion(self): + replacements = { + "val": "rval", + "mutableVal": "&rval", + "holderName" : "rvalHolder", + "declName" : "rvalDecl", + # We actually want to pass in a null scope object here, because + # wrapping things into our current compartment (that of mCallback) + # is what we want. + "obj": "nullptr" + } + + if isJSImplementedDescriptor(self.descriptorProvider): + isCallbackReturnValue = "JSImpl" + else: + isCallbackReturnValue = "Callback" + convertType = instantiateJSToNativeConversionTemplate( + getJSToNativeConversionTemplate(self.retvalType, + self.descriptorProvider, + exceptionCode=self.exceptionCode, + isCallbackReturnValue=isCallbackReturnValue, + # XXXbz we should try to do better here + sourceDescription="return value"), + replacements) + assignRetval = string.Template( + self.getRetvalInfo(self.retvalType, + False)[2]).substitute(replacements) + return convertType.define() + "\n" + assignRetval + + def getArgConversions(self): + # Just reget the arglist from self.originalSig, because our superclasses + # just have way to many members they like to clobber, so I can't find a + # safe member name to store it in. + argConversions = [self.getArgConversion(i, arg) for (i, arg) + in enumerate(self.originalSig[1])] + # Do them back to front, so our argc modifications will work + # correctly, because we examine trailing arguments first. + argConversions.reverse(); + # Wrap each one in a scope so that any locals it has don't leak out, and + # also so that we can just "break;" for our successCode. + argConversions = [CGWrapper(CGIndenter(CGGeneric(c)), + pre="loop {\n", + post="\nbreak;}\n") + for c in argConversions] + if self.argCount > 0: + argConversions.insert(0, self.getArgcDecl()) + # And slap them together. + return CGList(argConversions, "\n\n").define() + "\n\n" + + def getArgConversion(self, i, arg): + argval = arg.identifier.name + + if arg.variadic: + argval = argval + "[idx]" + jsvalIndex = "%d + idx" % i + else: + jsvalIndex = "%d" % i + if arg.optional and not arg.defaultValue: + argval += ".Value()" + if arg.type.isDOMString(): + # XPConnect string-to-JS conversion wants to mutate the string. So + # let's give it a string it can mutate + # XXXbz if we try to do a sequence of strings, this will kinda fail. + result = "mutableStr" + prepend = "nsString mutableStr(%s);\n" % argval + else: + result = argval + prepend = "" + + conversion = prepend + wrapForType( + arg.type, self.descriptorProvider, + { + 'result' : result, + 'successCode' : "continue;" if arg.variadic else "break;", + 'jsvalRef' : "argv.handleAt(%s)" % jsvalIndex, + 'jsvalHandle' : "argv.handleAt(%s)" % jsvalIndex, + 'jsvalPtr': "&mut argv[%s]" % jsvalIndex, + # XXXbz we don't have anything better to use for 'obj', + # really... It's OK to use CallbackPreserveColor because + # CallSetup already handled the unmark-gray bits for us. + 'obj' : 'ptr::null() /*XXXjdm proper scope*/', #XXXjdm 'CallbackPreserveColor()', + 'returnsNewObject': False, + 'exceptionCode' : self.exceptionCode + }) + if arg.variadic: + conversion = string.Template( + "for (uint32_t idx = 0; idx < ${arg}.Length(); ++idx) {\n" + + CGIndenter(CGGeneric(conversion)).define() + "\n" + "}\n" + "break;").substitute({ "arg": arg.identifier.name }) + elif arg.optional and not arg.defaultValue: + conversion = ( + CGIfWrapper(CGGeneric(conversion), + "%s.WasPassed()" % arg.identifier.name).define() + + " else if (argc == %d) {\n" + " // This is our current trailing argument; reduce argc\n" + " --argc;\n" + "} else {\n" + " argv[%d] = JS::UndefinedValue();\n" + "}" % (i+1, i)) + return conversion + + def getDefaultRetval(self): + default = self.getRetvalInfo(self.retvalType, False)[1] + if len(default) != 0: + default = " " + default + return default + + def getArgs(self, returnType, argList): + args = CGNativeMember.getArgs(self, returnType, argList) + if not self.needThisHandling: + # Since we don't need this handling, we're the actual method that + # will be called, so we need an aRethrowExceptions argument. + if self.rethrowContentException: + args.append(Argument("JSCompartment*", "aCompartment", "nullptr")) + else: + args.append(Argument("ExceptionHandling", "aExceptionHandling", + "eReportExceptions")) + return args + # We want to allow the caller to pass in a "this" object, as + # well as a JSContext. + return [Argument("*JSContext", "cx"), + Argument("*JSObject", "aThisObj")] + args + + def getCallSetup(self): + if self.needThisHandling: + # It's been done for us already + return "" + callSetup = "CallSetup s(CallbackPreserveColor(), aRv" + if self.rethrowContentException: + # getArgs doesn't add the aExceptionHandling argument but does add + # aCompartment for us. + callSetup += ", eRethrowContentExceptions, aCompartment" + else: + callSetup += ", aExceptionHandling" + callSetup += ");" + return string.Template( + "${callSetup}\n" + "JSContext* cx = s.GetContext();\n" + "if (!cx) {\n" + " return${errorReturn};\n" + "}\n").substitute({ + "callSetup": callSetup, + "errorReturn" : self.getDefaultRetval(), + }) + + def getArgcDecl(self): + return CGGeneric("let argc = %su32;" % self.argCountStr); + + @staticmethod + def ensureASCIIName(idlObject): + type = "attribute" if idlObject.isAttr() else "operation" + if re.match("[^\x20-\x7E]", idlObject.identifier.name): + raise SyntaxError('Callback %s name "%s" contains non-ASCII ' + "characters. We can't handle that. %s" % + (type, idlObject.identifier.name, + idlObject.location)) + if re.match('"', idlObject.identifier.name): + raise SyntaxError("Callback %s name '%s' contains " + "double-quote character. We can't handle " + "that. %s" % + (type, idlObject.identifier.name, + idlObject.location)) + +class CallbackMethod(CallbackMember): + def __init__(self, sig, name, descriptorProvider, needThisHandling, rethrowContentException=False): + CallbackMember.__init__(self, sig, name, descriptorProvider, + needThisHandling, rethrowContentException) + def getRvalDecl(self): + return "let mut rval = JSVAL_VOID;\n" + + def getCall(self): + replacements = { + "errorReturn" : self.getDefaultRetval(), + "thisObj": self.getThisObj(), + "getCallable": self.getCallableDecl() + } + if self.argCount > 0: + replacements["argv"] = "&argv[0]" + replacements["argc"] = "argc" + else: + replacements["argv"] = "nullptr" + replacements["argc"] = "0" + return string.Template("${getCallable}" + "if unsafe { JS_CallFunctionValue(cx, ${thisObj}, callable,\n" + " ${argc}, ${argv}, &rval) == 0 } {\n" + " return${errorReturn};\n" + "}\n").substitute(replacements) + +class CallCallback(CallbackMethod): + def __init__(self, callback, descriptorProvider): + CallbackMethod.__init__(self, callback.signatures()[0], "Call", + descriptorProvider, needThisHandling=True) + + def getThisObj(self): + return "aThisObj" + + def getCallableDecl(self): + return "JS::Rooted callable(cx, JS::ObjectValue(*mCallback));\n" + +class CallbackOperationBase(CallbackMethod): + """ + Common class for implementing various callback operations. + """ + def __init__(self, signature, jsName, nativeName, descriptor, singleOperation, rethrowContentException=False): + self.singleOperation = singleOperation + self.methodName = jsName + CallbackMethod.__init__(self, signature, nativeName, descriptor, singleOperation, rethrowContentException) + + def getThisObj(self): + if not self.singleOperation: + return "self.parent.callback" + # This relies on getCallableDecl declaring a boolean + # isCallable in the case when we're a single-operation + # interface. + return "if isCallable { aThisObj } else { self.parent.callback }" + + def getCallableDecl(self): + replacements = { + "errorReturn" : self.getDefaultRetval(), + "methodName": self.methodName + } + getCallableFromProp = string.Template( + 'if "${methodName}".to_c_str().with_ref(|name| !self.parent.GetCallableProperty(cx, name, &mut callable)) {\n' + ' return${errorReturn};\n' + '}\n').substitute(replacements) + if not self.singleOperation: + return 'JS::Rooted callable(cx);\n' + getCallableFromProp + return ( + 'let isCallable = unsafe { JS_ObjectIsCallable(cx, self.parent.callback) != 0 };\n' + 'let mut callable = JSVAL_VOID;\n' + 'if isCallable {\n' + ' callable = unsafe { RUST_OBJECT_TO_JSVAL(self.parent.callback) };\n' + '} else {\n' + '%s' + '}\n' % CGIndenter(CGGeneric(getCallableFromProp)).define()) + +class CallbackOperation(CallbackOperationBase): + """ + Codegen actual WebIDL operations on callback interfaces. + """ + def __init__(self, method, signature, descriptor): + self.ensureASCIIName(method) + jsName = method.identifier.name + CallbackOperationBase.__init__(self, signature, + jsName, MakeNativeName(jsName), + descriptor, descriptor.interface.isSingleOperationInterface(), + rethrowContentException=descriptor.interface.isJSImplemented()) + +class CallbackGetter(CallbackMember): + def __init__(self, attr, descriptor): + self.ensureASCIIName(attr) + self.attrName = attr.identifier.name + CallbackMember.__init__(self, + (attr.type, []), + callbackGetterName(attr), + descriptor, + needThisHandling=False, + rethrowContentException=descriptor.interface.isJSImplemented()) + + def getRvalDecl(self): + return "JS::Rooted rval(cx, JS::UndefinedValue());\n" + + def getCall(self): + replacements = { + "errorReturn" : self.getDefaultRetval(), + "attrName": self.attrName + } + return string.Template( + 'if (!JS_GetProperty(cx, mCallback, "${attrName}", &rval)) {\n' + ' aRv.Throw(NS_ERROR_UNEXPECTED);\n' + ' return${errorReturn};\n' + '}\n').substitute(replacements); + +class CallbackSetter(CallbackMember): + def __init__(self, attr, descriptor): + self.ensureASCIIName(attr) + self.attrName = attr.identifier.name + CallbackMember.__init__(self, + (BuiltinTypes[IDLBuiltinType.Types.void], + [FakeArgument(attr.type, attr)]), + callbackSetterName(attr), + descriptor, + needThisHandling=False, + rethrowContentException=descriptor.interface.isJSImplemented()) + + def getRvalDecl(self): + # We don't need an rval + return "" + + def getCall(self): + replacements = { + "errorReturn" : self.getDefaultRetval(), + "attrName": self.attrName, + "argv": "argv.handleAt(0)", + } + return string.Template( + 'MOZ_ASSERT(argv.length() == 1);\n' + 'if (!JS_SetProperty(cx, mCallback, "${attrName}", ${argv})) {\n' + ' aRv.Throw(NS_ERROR_UNEXPECTED);\n' + ' return${errorReturn};\n' + '}\n').substitute(replacements) + + def getArgcDecl(self): + return None + class GlobalGenRoots(): """ Roots for global codegen. @@ -4788,7 +6224,12 @@ class GlobalGenRoots(): @staticmethod def InterfaceTypes(config): - descriptors = [d.name for d in config.getDescriptors(register=True)] + def pathToType(descriptor): + if descriptor.interface.isCallback(): + return "dom::bindings::codegen::%sBinding" % descriptor.name + return "dom::%s" % descriptor.name.lower() + + descriptors = [d.name for d in config.getDescriptors(register=True, hasInterfaceObject=True)] curr = CGList([CGGeneric(declare="pub use dom::%s::%s;\n" % (name.lower(), name)) for name in descriptors]) curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) return curr diff --git a/src/components/script/dom/bindings/codegen/Configuration.py b/src/components/script/dom/bindings/codegen/Configuration.py index 32918d272b3..0c87ead0259 100644 --- a/src/components/script/dom/bindings/codegen/Configuration.py +++ b/src/components/script/dom/bindings/codegen/Configuration.py @@ -42,6 +42,8 @@ class Configuration: self.enums = [e for e in parseData if e.isEnum()] self.dictionaries = [d for d in parseData if d.isDictionary()] + self.callbacks = [c for c in parseData if + c.isCallback() and not c.isInterface()] # Keep the descriptor list sorted for determinism. self.descriptors.sort(lambda x,y: cmp(x.name, y.name)) @@ -66,14 +68,34 @@ class Configuration: getter = lambda x: x.interface.isCallback() elif key == 'isExternal': getter = lambda x: x.interface.isExternal() + elif key == 'isJSImplemented': + getter = lambda x: x.interface.isJSImplemented() else: getter = lambda x: getattr(x, key) curr = filter(lambda x: getter(x) == val, curr) return curr def getEnums(self, webIDLFile): return filter(lambda e: e.filename() == webIDLFile, self.enums) - def getDictionaries(self, webIDLFile): - return filter(lambda d: d.filename() == webIDLFile, self.dictionaries) + + @staticmethod + def _filterForFileAndWorkers(items, filters): + """Gets the items that match the given filters.""" + for key, val in filters.iteritems(): + if key == 'webIDLFile': + items = filter(lambda x: x.filename() == val, items) + elif key == 'workers': + if val: + items = filter(lambda x: x.getUserData("workers", False), items) + else: + items = filter(lambda x: x.getUserData("mainThread", False), items) + else: + assert(0) # Unknown key + return items + def getDictionaries(self, **filters): + return self._filterForFileAndWorkers(self.dictionaries, filters) + def getCallbacks(self, **filters): + return self._filterForFileAndWorkers(self.callbacks, filters) + def getDescriptor(self, interfaceName, workers): """ Gets the appropriate descriptor for the given interface name diff --git a/src/components/script/dom/bindings/codegen/Document.webidl b/src/components/script/dom/bindings/codegen/Document.webidl index ef479f01a97..10d212910fc 100644 --- a/src/components/script/dom/bindings/codegen/Document.webidl +++ b/src/components/script/dom/bindings/codegen/Document.webidl @@ -58,8 +58,8 @@ interface Document : Node { [Throws] Node adoptNode(Node node);*/ - // [Creator, Throws] - // Event createEvent(DOMString interface_); + [Creator, Throws] + Event createEvent(DOMString interface_); /*[Creator, Throws] Range createRange();*/ diff --git a/src/components/script/dom/bindings/codegen/EventListener.webidl b/src/components/script/dom/bindings/codegen/EventListener.webidl new file mode 100644 index 00000000000..05e1684d31e --- /dev/null +++ b/src/components/script/dom/bindings/codegen/EventListener.webidl @@ -0,0 +1,16 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. + * + * The origin of this IDL file is + * http://www.w3.org/TR/2012/WD-dom-20120105/ + * + * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C + * liability, trademark and document use rules apply. + */ + +callback interface EventListener { + void handleEvent(Event event); +}; + diff --git a/src/components/script/dom/bindings/codegen/EventTarget.webidl b/src/components/script/dom/bindings/codegen/EventTarget.webidl index f4e1ba00f70..897756fa273 100644 --- a/src/components/script/dom/bindings/codegen/EventTarget.webidl +++ b/src/components/script/dom/bindings/codegen/EventTarget.webidl @@ -11,4 +11,12 @@ */ interface EventTarget { + void addEventListener(DOMString type, + EventListener? listener, + optional boolean capture = false); + void removeEventListener(DOMString type, + EventListener? listener, + optional boolean capture = false); + [Throws] + boolean dispatchEvent(Event event); }; diff --git a/src/components/script/dom/bindings/codegen/Node.webidl b/src/components/script/dom/bindings/codegen/Node.webidl index 5f32ff93f67..1a9d9d53bb3 100644 --- a/src/components/script/dom/bindings/codegen/Node.webidl +++ b/src/components/script/dom/bindings/codegen/Node.webidl @@ -14,7 +14,7 @@ interface URI; interface UserDataHandler;*/ -interface Node /*: EventTarget*/ { +interface Node : EventTarget { const unsigned short ELEMENT_NODE = 1; const unsigned short ATTRIBUTE_NODE = 2; // historical const unsigned short TEXT_NODE = 3; diff --git a/src/components/script/dom/bindings/codegen/Window.webidl b/src/components/script/dom/bindings/codegen/Window.webidl index b85890676eb..59ea26308e3 100644 --- a/src/components/script/dom/bindings/codegen/Window.webidl +++ b/src/components/script/dom/bindings/codegen/Window.webidl @@ -8,7 +8,7 @@ */ [NamedPropertiesObject] -/*sealed*/ interface Window /*: EventTarget*/ { +/*sealed*/ interface Window : EventTarget { // the current browsing context /*[Unforgeable] readonly attribute WindowProxy window; [Replaceable] readonly attribute WindowProxy self;*/ diff --git a/src/components/script/dom/bindings/codegen/parser/WebIDL.py b/src/components/script/dom/bindings/codegen/parser/WebIDL.py index 1aab3debc6a..046b8130dff 100644 --- a/src/components/script/dom/bindings/codegen/parser/WebIDL.py +++ b/src/components/script/dom/bindings/codegen/parser/WebIDL.py @@ -146,6 +146,23 @@ class IDLObject(object): def isCallback(self): return False + def isSingleOperationInterface(self): + assert self.isCallback() or self.isJSImplemented() + return ( + # JS-implemented things should never need the + # this-handling weirdness of single-operation interfaces. + not self.isJSImplemented() and + # Not inheriting from another interface + not self.parent and + # No consequential interfaces + len(self.getConsequentialInterfaces()) == 0 and + # No attributes of any kinds + not any(m.isAttr() for m in self.members) and + # There is at least one regular operation, and all regular + # operations have the same identifier + len(set(m.identifier.name for m in self.members if + m.isMethod() and not m.isStatic())) == 1) + def isType(self): return False @@ -167,6 +184,38 @@ class IDLObject(object): def handleExtendedAttribute(self, attr): assert False # Override me! + def _getDependentObjects(self): + assert False # Override me! + + def getDeps(self, visited=None): + """ Return a set of files that this object depends on. If any of + these files are changed the parser needs to be rerun to regenerate + a new IDLObject. + + The visited argument is a set of all the objects already visited. + We must test to see if we are in it, and if so, do nothing. This + prevents infinite recursion.""" + + # NB: We can't use visited=set() above because the default value is + # evaluated when the def statement is evaluated, not when the function + # is executed, so there would be one set for all invocations. + if visited == None: + visited = set() + + if self in visited: + return set() + + visited.add(self) + + deps = set() + if self.filename() != "": + deps.add(self.filename()) + + for d in self._getDependentObjects(): + deps = deps.union(d.getDeps(visited)) + + return deps + class IDLScope(IDLObject): def __init__(self, location, parentScope, identifier): IDLObject.__init__(self, location) @@ -428,6 +477,15 @@ class IDLExternalInterface(IDLObjectWithIdentifier): def resolve(self, parentScope): pass + def getJSImplementation(self): + return None + + def isJSImplemented(self): + return False + + def _getDependentObjects(self): + return set() + class IDLInterface(IDLObjectWithScope): def __init__(self, location, parentScope, name, parent, members, isPartial): @@ -777,6 +835,24 @@ class IDLInterface(IDLObjectWithScope): # Put the new members at the beginning self.members = members + self.members + def getJSImplementation(self): + classId = self.getExtendedAttribute("JSImplementation") + if not classId: + return classId + assert isinstance(classId, list) + assert len(classId) == 1 + return classId[0] + + def isJSImplemented(self): + return bool(self.getJSImplementation()) + + def _getDependentObjects(self): + deps = set(self.members) + deps.union(self.implementedInterfaces) + if self.parent: + deps.add(self.parent) + return deps + class IDLDictionary(IDLObjectWithScope): def __init__(self, location, parentScope, name, parent, members): assert isinstance(parentScope, IDLScope) @@ -847,6 +923,11 @@ class IDLDictionary(IDLObjectWithScope): def addExtendedAttributes(self, attrs): assert len(attrs) == 0 + def _getDependentObjects(self): + deps = set(self.members) + if (self.parent): + deps.add(self.parent) + return deps class IDLEnum(IDLObjectWithIdentifier): def __init__(self, location, parentScope, name, values): @@ -875,6 +956,9 @@ class IDLEnum(IDLObjectWithIdentifier): def addExtendedAttributes(self, attrs): assert len(attrs) == 0 + def _getDependentObjects(self): + return set() + class IDLType(IDLObject): Tags = enum( # The integer types @@ -893,6 +977,7 @@ class IDLType(IDLObject): # Other types 'any', 'domstring', + 'bytestring', 'object', 'date', 'void', @@ -930,6 +1015,12 @@ class IDLType(IDLObject): def isString(self): return False + def isByteString(self): + return False + + def isDOMString(self): + return False + def isVoid(self): return self.name == "Void" @@ -1075,6 +1166,12 @@ class IDLNullableType(IDLType): def isString(self): return self.inner.isString() + def isByteString(self): + return self.inner.isByteString() + + def isDOMString(self): + return self.inner.isDOMString() + def isFloat(self): return self.inner.isFloat() @@ -1163,6 +1260,9 @@ class IDLNullableType(IDLType): return False return self.inner.isDistinguishableFrom(other) + def _getDependentObjects(self): + return self.inner._getDependentObjects() + class IDLSequenceType(IDLType): def __init__(self, location, parameterType): assert not parameterType.isVoid() @@ -1231,6 +1331,9 @@ class IDLSequenceType(IDLType): other.isDictionary() or other.isDate() or other.isNonCallbackInterface()) + def _getDependentObjects(self): + return self.inner._getDependentObjects() + class IDLUnionType(IDLType): def __init__(self, location, memberTypes): IDLType.__init__(self, location, "") @@ -1317,6 +1420,9 @@ class IDLUnionType(IDLType): return False return True + def _getDependentObjects(self): + return set(self.memberTypes) + class IDLArrayType(IDLType): def __init__(self, location, parameterType): assert not parameterType.isVoid() @@ -1393,6 +1499,9 @@ class IDLArrayType(IDLType): other.isDictionary() or other.isDate() or other.isNonCallbackInterface()) + def _getDependentObjects(self): + return self.inner._getDependentObjects() + class IDLTypedefType(IDLType, IDLObjectWithIdentifier): def __init__(self, location, innerType, name): IDLType.__init__(self, location, innerType.name) @@ -1478,6 +1587,9 @@ class IDLTypedefType(IDLType, IDLObjectWithIdentifier): def isDistinguishableFrom(self, other): return self.inner.isDistinguishableFrom(other) + def _getDependentObjects(self): + return self.inner._getDependentObjects() + class IDLWrapperType(IDLType): def __init__(self, location, inner): IDLType.__init__(self, location, inner.identifier.name) @@ -1583,6 +1695,23 @@ class IDLWrapperType(IDLType): assert other.isObject() return False + def _getDependentObjects(self): + # NB: The codegen for an interface type depends on + # a) That the identifier is in fact an interface (as opposed to + # a dictionary or something else). + # b) The native type of the interface. + # If we depend on the interface object we will also depend on + # anything the interface depends on which is undesirable. We + # considered implementing a dependency just on the interface type + # file, but then every modification to an interface would cause this + # to be regenerated which is still undesirable. We decided not to + # depend on anything, reasoning that: + # 1) Changing the concrete type of the interface requires modifying + # Bindings.conf, which is still a global dependency. + # 2) Changing an interface to a dictionary (or vice versa) with the + # same identifier should be incredibly rare. + return set() + class IDLBuiltinType(IDLType): Types = enum( @@ -1602,6 +1731,7 @@ class IDLBuiltinType(IDLType): # Other types 'any', 'domstring', + 'bytestring', 'object', 'date', 'void', @@ -1633,6 +1763,7 @@ class IDLBuiltinType(IDLType): Types.double: IDLType.Tags.double, Types.any: IDLType.Tags.any, Types.domstring: IDLType.Tags.domstring, + Types.bytestring: IDLType.Tags.bytestring, Types.object: IDLType.Tags.object, Types.date: IDLType.Tags.date, Types.void: IDLType.Tags.void, @@ -1658,6 +1789,13 @@ class IDLBuiltinType(IDLType): return self._typeTag <= IDLBuiltinType.Types.double def isString(self): + return self._typeTag == IDLBuiltinType.Types.domstring or \ + self._typeTag == IDLBuiltinType.Types.bytestring + + def isByteString(self): + return self._typeTag == IDLBuiltinType.Types.bytestring + + def isDOMString(self): return self._typeTag == IDLBuiltinType.Types.domstring def isInteger(self): @@ -1733,6 +1871,9 @@ class IDLBuiltinType(IDLType): (self.isTypedArray() and not other.isArrayBufferView() and not (other.isTypedArray() and other.name == self.name))))) + def _getDependentObjects(self): + return set() + BuiltinTypes = { IDLBuiltinType.Types.byte: IDLBuiltinType(BuiltinLocation(""), "Byte", @@ -1877,6 +2018,9 @@ class IDLValue(IDLObject): raise WebIDLError("Cannot coerce type %s to type %s." % (self.type, type), [location]) + def _getDependentObjects(self): + return set() + class IDLNullValue(IDLObject): def __init__(self, location): IDLObject.__init__(self, location) @@ -1895,6 +2039,9 @@ class IDLNullValue(IDLObject): nullValue.type = type return nullValue + def _getDependentObjects(self): + return set() + class IDLInterfaceMember(IDLObjectWithIdentifier): @@ -1966,6 +2113,9 @@ class IDLConst(IDLInterfaceMember): def validate(self): pass + def _getDependentObjects(self): + return set([self.type, self.value]) + class IDLAttribute(IDLInterfaceMember): def __init__(self, location, identifier, type, readonly, inherit, static=False): @@ -2052,6 +2202,9 @@ class IDLAttribute(IDLInterfaceMember): def hasLenientThis(self): return self.lenientThis + def _getDependentObjects(self): + return set([self.type]) + class IDLArgument(IDLObjectWithIdentifier): def __init__(self, location, identifier, type, optional=False, defaultValue=None, variadic=False, dictionaryMember=False): IDLObjectWithIdentifier.__init__(self, location, None, identifier) @@ -2124,6 +2277,12 @@ class IDLArgument(IDLObjectWithIdentifier): self.location) assert self.defaultValue + def _getDependentObjects(self): + deps = set([self.type]) + if self.defaultValue: + deps.add(self.defaultValue) + return deps + class IDLCallbackType(IDLType, IDLObjectWithScope): def __init__(self, location, parentScope, identifier, returnType, arguments): assert isinstance(returnType, IDLType) @@ -2179,6 +2338,9 @@ class IDLCallbackType(IDLType, IDLObjectWithScope): return (other.isPrimitive() or other.isString() or other.isEnum() or other.isNonCallbackInterface() or other.isDate()) + def _getDependentObjects(self): + return set([self._returnType] + self._arguments) + class IDLMethodOverload: """ A class that represents a single overload of a WebIDL method. This is not @@ -2194,6 +2356,11 @@ class IDLMethodOverload: self.arguments = list(arguments) self.location = location + def _getDependentObjects(self): + deps = set(self.arguments) + deps.add(self.returnType) + return deps + class IDLMethod(IDLInterfaceMember, IDLScope): Special = enum( @@ -2494,6 +2661,12 @@ class IDLMethod(IDLInterfaceMember, IDLScope): [attr.location, self.location]) IDLInterfaceMember.handleExtendedAttribute(self, attr) + def _getDependentObjects(self): + deps = set() + for overload in self._overloads: + deps.union(overload._getDependentObjects()) + return deps + class IDLImplementsStatement(IDLObject): def __init__(self, location, implementor, implementee): IDLObject.__init__(self, location) diff --git a/src/components/script/dom/bindings/node.rs b/src/components/script/dom/bindings/node.rs index 0ec5b13e80b..8574c084062 100644 --- a/src/components/script/dom/bindings/node.rs +++ b/src/components/script/dom/bindings/node.rs @@ -50,7 +50,7 @@ impl Traceable for Node { } } } - debug!("tracing {:p}?:", self.reflector_.get_jsobject()); + debug!("tracing {:p}?:", self.reflector().get_jsobject()); trace_node(tracer, self.parent_node, "parent"); trace_node(tracer, self.first_child, "first child"); trace_node(tracer, self.last_child, "last child"); diff --git a/src/components/script/dom/bindings/utils.rs b/src/components/script/dom/bindings/utils.rs index 42f1eb7f4fc..7eb70ff145f 100644 --- a/src/components/script/dom/bindings/utils.rs +++ b/src/components/script/dom/bindings/utils.rs @@ -4,8 +4,8 @@ use dom::bindings::codegen::PrototypeList; use dom::bindings::codegen::PrototypeList::MAX_PROTO_CHAIN_LENGTH; -use dom::window; use dom::node::{AbstractNode, ScriptView}; +use dom::window; use std::libc::c_uint; use std::cast; @@ -22,6 +22,7 @@ use js::glue::{js_IsObjectProxyClass, js_IsFunctionProxyClass, IsProxyHandlerFam use js::jsapi::{JS_AlreadyHasOwnProperty, JS_NewObject, JS_NewFunction, JS_GetGlobalObject}; use js::jsapi::{JS_DefineProperties, JS_WrapValue, JS_ForwardGetPropertyTo}; use js::jsapi::{JS_GetClass, JS_LinkConstructorAndPrototype, JS_GetStringCharsAndLength}; +use js::jsapi::{JS_ObjectIsRegExp, JS_ObjectIsDate}; use js::jsapi::{JS_GetFunctionPrototype, JS_InternString, JS_GetFunctionObject}; use js::jsapi::{JS_HasPropertyById, JS_GetPrototype, JS_GetGlobalForObject}; use js::jsapi::{JS_NewUCStringCopyN, JS_DefineFunctions, JS_DefineProperty}; @@ -30,7 +31,7 @@ use js::jsapi::{JSContext, JSObject, JSBool, jsid, JSClass, JSNative, JSTracer}; use js::jsapi::{JSFunctionSpec, JSPropertySpec, JSVal, JSPropertyDescriptor}; use js::jsapi::{JSPropertyOp, JSStrictPropertyOp, JS_NewGlobalObject, JS_InitStandardClasses}; use js::jsfriendapi::bindgen::JS_NewObjectWithUniqueType; -use js::{JSPROP_ENUMERATE, JSVAL_NULL}; +use js::{JSPROP_ENUMERATE, JSVAL_NULL, JSCLASS_IS_GLOBAL, JSCLASS_IS_DOMJSCLASS}; use js::{JSPROP_PERMANENT, JSID_VOID, JSPROP_NATIVE_ACCESSORS, JSPROP_GETTER}; use js::{JSPROP_SETTER, JSVAL_VOID, JSVAL_TRUE, JSVAL_FALSE}; use js::{JS_THIS_OBJECT, JSFUN_CONSTRUCTOR, JS_CALLEE, JSPROP_READONLY}; @@ -767,6 +768,8 @@ pub enum Error { NotFound, HierarchyRequest, InvalidCharacter, + NotSupported, + InvalidState } pub type Fallible = Result; @@ -819,6 +822,13 @@ pub fn HasPropertyOnPrototype(cx: *JSContext, proxy: *JSObject, id: jsid) -> boo return !GetPropertyOnPrototype(cx, proxy, id, &mut found, ptr::null()) || found; } +#[fixed_stack_segment] +pub fn IsConvertibleToCallbackInterface(cx: *JSContext, obj: *JSObject) -> bool { + unsafe { + JS_ObjectIsDate(cx, obj) == 0 && JS_ObjectIsRegExp(cx, obj) == 0 + } +} + #[fixed_stack_segment] pub fn CreateDOMGlobal(cx: *JSContext, class: *JSClass) -> *JSObject { unsafe { @@ -832,6 +842,30 @@ pub fn CreateDOMGlobal(cx: *JSContext, class: *JSClass) -> *JSObject { } } +#[fixed_stack_segment] +fn cx_for_dom_reflector(obj: *JSObject) -> *JSContext { + unsafe { + let global = GetGlobalForObjectCrossCompartment(obj); + let clasp = JS_GetClass(global); + assert!(((*clasp).flags & (JSCLASS_IS_DOMJSCLASS | JSCLASS_IS_GLOBAL)) != 0); + //XXXjdm either don't hardcode or sanity assert prototype stuff + let win = unwrap_object::<*Box>(global, PrototypeList::id::Window, 1); + match win { + Ok(win) => { + match (*win).data.page.js_info { + Some(ref info) => info.js_context.ptr, + None => fail!("no JS context for DOM global") + } + } + Err(_) => fail!("found DOM global that doesn't unwrap to Window") + } + } +} + +pub fn cx_for_dom_object(obj: &mut T) -> *JSContext { + cx_for_dom_reflector(obj.reflector().get_jsobject()) +} + /// Check if an element name is valid. See http://www.w3.org/TR/xml/#NT-Name /// for details. pub fn is_valid_element_name(name: &str) -> bool { diff --git a/src/components/script/dom/document.rs b/src/components/script/dom/document.rs index 9805ea71dfa..ffea5809fda 100644 --- a/src/components/script/dom/document.rs +++ b/src/components/script/dom/document.rs @@ -5,15 +5,20 @@ use dom::comment::Comment; use dom::bindings::codegen::DocumentBinding; use dom::bindings::utils::{DOMString, ErrorResult, Fallible}; -use dom::bindings::utils::{Reflectable, Reflector, DerivedWrapper}; -use dom::bindings::utils::{is_valid_element_name, InvalidCharacter, Traceable, null_str_as_empty, null_str_as_word_null}; +use dom::bindings::utils::{Reflectable, Reflector, DerivedWrapper, NotSupported}; +use dom::bindings::utils::{is_valid_element_name, InvalidCharacter, Traceable}; +use dom::bindings::utils::{null_str_as_empty_ref, null_str_as_empty, null_str_as_word_null}; use dom::documentfragment::DocumentFragment; use dom::element::{Element}; use dom::element::{HTMLHeadElementTypeId, HTMLTitleElementTypeId}; +use dom::event::{AbstractEvent, Event, HTMLEventTypeId, UIEventTypeId}; use dom::htmlcollection::HTMLCollection; use dom::htmldocument::HTMLDocument; +use dom::htmlelement::HTMLElement; +use dom::mouseevent::MouseEvent; use dom::node::{AbstractNode, ScriptView, Node, ElementNodeTypeId, DocumentNodeTypeId}; use dom::text::Text; +use dom::uievent::UIEvent; use dom::window::Window; use dom::htmltitleelement::HTMLTitleElement; use html::hubbub_html_parser::build_element_from_tag; @@ -255,6 +260,15 @@ impl Document { Comment::new(null_str_as_word_null(data), abstract_self) } + pub fn CreateEvent(&self, interface: &DOMString) -> Fallible { + match null_str_as_empty_ref(interface) { + "UIEvents" => Ok(UIEvent::new(self.window, UIEventTypeId)), + "MouseEvents" => Ok(MouseEvent::new(self.window)), + "HTMLEvents" => Ok(Event::new(self.window, HTMLEventTypeId)), + _ => Err(NotSupported) + } + } + pub fn Title(&self, _: AbstractDocument) -> DOMString { let mut title = ~""; match self.doctype { diff --git a/src/components/script/dom/event.rs b/src/components/script/dom/event.rs index 43ee6f894c1..02acca10937 100644 --- a/src/components/script/dom/event.rs +++ b/src/components/script/dom/event.rs @@ -2,17 +2,23 @@ * 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::eventtarget::EventTarget; +use dom::eventtarget::AbstractEventTarget; use dom::window::Window; use dom::bindings::codegen::EventBinding; -use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object}; -use dom::bindings::utils::{DOMString, ErrorResult, Fallible}; +use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object, DerivedWrapper}; +use dom::bindings::utils::{DOMString, ErrorResult, Fallible, null_str_as_word_null}; +use dom::mouseevent::MouseEvent; +use dom::uievent::UIEvent; use geom::point::Point2D; -use js::jsapi::{JSObject, JSContext}; +use js::jsapi::{JSObject, JSContext, JSVal}; +use js::glue::RUST_OBJECT_TO_JSVAL; use script_task::page_from_context; +use std::cast; +use std::unstable::raw::Box; + pub enum Event_ { ResizeEvent(uint, uint), ReflowEvent, @@ -21,45 +27,194 @@ pub enum Event_ { MouseUpEvent(uint, Point2D), } -pub struct Event { - reflector_: Reflector, - type_: DOMString, - default_prevented: bool, - cancelable: bool, - bubbles: bool, - trusted: bool, +pub struct AbstractEvent { + event: *mut Box } -impl Event { - pub fn new_inherited(type_: &DOMString) -> Event { - Event { - reflector_: Reflector::new(), - type_: (*type_).clone(), - default_prevented: false, - cancelable: true, - bubbles: true, - trusted: false +pub enum EventPhase { + Phase_None = 0, + Phase_Capturing, + Phase_At_Target, + Phase_Bubbling +} + +impl AbstractEvent { + pub fn from_box(box: *mut Box) -> AbstractEvent { + AbstractEvent { + event: box } } - pub fn new(window: @mut Window, type_: &DOMString) -> @mut Event { - reflect_dom_object(@mut Event::new_inherited(type_), window, EventBinding::Wrap) + // + // Downcasting borrows + // + + fn transmute<'a, T>(&'a self) -> &'a T { + unsafe { + let box: *Box = self.event as *Box; + &(*box).data + } + } + + fn transmute_mut<'a, T>(&'a self) -> &'a mut T { + unsafe { + let box: *mut Box = self.event as *mut Box; + &mut (*box).data + } + } + + pub fn type_id(&self) -> EventTypeId { + self.event().type_id + } + + pub fn event<'a>(&'a self) -> &'a Event { + self.transmute() + } + + pub fn mut_event<'a>(&'a self) -> &'a mut Event { + self.transmute_mut() + } + + pub fn is_uievent(&self) -> bool { + self.type_id() == UIEventTypeId + } + + pub fn uievent<'a>(&'a self) -> &'a UIEvent { + assert!(self.is_uievent()); + self.transmute() + } + + pub fn mut_uievent<'a>(&'a self) -> &'a mut UIEvent { + assert!(self.is_uievent()); + self.transmute_mut() + } + + pub fn is_mouseevent(&self) -> bool { + self.type_id() == MouseEventTypeId + } + + pub fn mouseevent<'a>(&'a self) -> &'a MouseEvent { + assert!(self.is_mouseevent()); + self.transmute() + } + + pub fn mut_mouseevent<'a>(&'a self) -> &'a mut MouseEvent { + assert!(self.is_mouseevent()); + self.transmute_mut() + } + + pub fn propagation_stopped(&self) -> bool { + self.event().stop_propagation + } + + pub fn bubbles(&self) -> bool { + self.event().bubbles + } +} + +impl DerivedWrapper for AbstractEvent { + #[fixed_stack_segment] + fn wrap(&mut self, _cx: *JSContext, _scope: *JSObject, vp: *mut JSVal) -> i32 { + let wrapper = self.reflector().get_jsobject(); + if wrapper.is_not_null() { + unsafe { *vp = RUST_OBJECT_TO_JSVAL(wrapper) }; + return 1; + } + unreachable!() + } +} + +impl Reflectable for AbstractEvent { + fn reflector<'a>(&'a self) -> &'a Reflector { + self.event().reflector() + } + + fn mut_reflector<'a>(&'a mut self) -> &'a mut Reflector { + self.mut_event().mut_reflector() + } + + fn wrap_object_shared(@mut self, _cx: *JSContext, _scope: *JSObject) -> *JSObject { + fail!(~"doesn't make any sense"); + } + + fn GetParentObject(&self, cx: *JSContext) -> Option<@mut Reflectable> { + self.event().GetParentObject(cx) + } +} + +#[deriving(Eq)] +pub enum EventTypeId { + HTMLEventTypeId, + UIEventTypeId, + MouseEventTypeId, + KeyEventTypeId +} + +pub struct Event { + type_id: EventTypeId, + reflector_: Reflector, + current_target: Option, + target: Option, + type_: ~str, + phase: EventPhase, + default_prevented: bool, + stop_propagation: bool, + stop_immediate: bool, + cancelable: bool, + bubbles: bool, + trusted: bool, + dispatching: bool, + initialized: bool +} + +impl Event { + pub fn new_inherited(type_id: EventTypeId) -> Event { + Event { + type_id: type_id, + reflector_: Reflector::new(), + current_target: None, + target: None, + phase: Phase_None, + type_: ~"", + default_prevented: false, + cancelable: true, + bubbles: true, + trusted: false, + dispatching: false, + stop_propagation: false, + stop_immediate: false, + initialized: false, + } + } + + //FIXME: E should be bounded by some trait that is only implemented for Event types + pub fn as_abstract(event: @mut E) -> AbstractEvent { + // This surrenders memory management of the event! + AbstractEvent { + event: unsafe { cast::transmute(event) }, + } + } + + pub fn new(window: @mut Window, type_id: EventTypeId) -> AbstractEvent { + let ev = reflect_dom_object(@mut Event::new_inherited(type_id), window, + EventBinding::Wrap); + Event::as_abstract(ev) } pub fn EventPhase(&self) -> u16 { - 0 + self.phase as u16 } pub fn Type(&self) -> DOMString { - self.type_.clone() + Some(self.type_.clone()) } - pub fn GetTarget(&self) -> Option<@mut EventTarget> { - None + pub fn GetTarget(&self) -> Option { + self.target } - pub fn GetCurrentTarget(&self) -> Option<@mut EventTarget> { - None + pub fn GetCurrentTarget(&self) -> Option { + self.current_target } pub fn DefaultPrevented(&self) -> bool { @@ -67,13 +222,18 @@ impl Event { } pub fn PreventDefault(&mut self) { - self.default_prevented = true + if self.cancelable { + self.default_prevented = true + } } pub fn StopPropagation(&mut self) { + self.stop_propagation = true; } pub fn StopImmediatePropagation(&mut self) { + self.stop_immediate = true; + self.stop_propagation = true; } pub fn Bubbles(&self) -> bool { @@ -92,9 +252,10 @@ impl Event { type_: &DOMString, bubbles: bool, cancelable: bool) -> ErrorResult { - self.type_ = (*type_).clone(); + self.type_ = null_str_as_word_null(type_); self.cancelable = cancelable; self.bubbles = bubbles; + self.initialized = true; Ok(()) } @@ -103,9 +264,11 @@ impl Event { } pub fn Constructor(global: @mut Window, - type_: &DOMString, - _init: &EventBinding::EventInit) -> Fallible<@mut Event> { - Ok(Event::new(global, type_)) + type_: &DOMString, + init: &EventBinding::EventInit) -> Fallible { + let ev = Event::new(global, HTMLEventTypeId); + ev.mut_event().InitEvent(type_, init.bubbles, init.cancelable); + Ok(ev) } } diff --git a/src/components/script/dom/eventdispatcher.rs b/src/components/script/dom/eventdispatcher.rs new file mode 100644 index 00000000000..ba8e06795f5 --- /dev/null +++ b/src/components/script/dom/eventdispatcher.rs @@ -0,0 +1,111 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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::callback::eReportExceptions; +use dom::eventtarget::{AbstractEventTarget, Capturing, Bubbling}; +use dom::event::{AbstractEvent, Phase_At_Target, Phase_None, Phase_Bubbling, Phase_Capturing}; +use dom::node::AbstractNode; +use servo_util::tree::{TreeNodeRef}; + +// See http://dom.spec.whatwg.org/#concept-event-dispatch for the full dispatch algorithm +pub fn dispatch_event(target: AbstractEventTarget, event: AbstractEvent) -> bool { + assert!(!event.event().dispatching); + + { + let event = event.mut_event(); + event.target = Some(target); + event.dispatching = true; + } + + let type_ = event.event().type_.clone(); + let mut chain = ~[]; + + //TODO: no chain if not participating in a tree + if target.is_node() { + for ancestor in AbstractNode::from_eventtarget(target).ancestors() { + chain.push(AbstractEventTarget::from_node(ancestor)); + } + } + + event.mut_event().phase = Phase_Capturing; + + //FIXME: The "callback this value" should be currentTarget + + /* capturing */ + for &cur_target in chain.rev_iter() { + //XXX bad clone + let stopped = match cur_target.eventtarget().get_listeners_for(type_.clone(), Capturing) { + Some(listeners) => { + event.mut_event().current_target = Some(cur_target); + for listener in listeners.iter() { + listener.HandleEvent__(event, eReportExceptions); + + if event.event().stop_immediate { + break; + } + } + + event.propagation_stopped() + } + None => false + }; + + if stopped { + break; + } + } + + /* at target */ + if !event.propagation_stopped() { + { + let event = event.mut_event(); + event.phase = Phase_At_Target; + event.current_target = Some(target); + } + + let opt_listeners = target.eventtarget().get_listeners(type_.clone()); + for listeners in opt_listeners.iter() { + for listener in listeners.iter() { + listener.HandleEvent__(event, eReportExceptions); + if event.event().stop_immediate { + break; + } + } + } + } + + /* bubbling */ + if event.bubbles() && !event.propagation_stopped() { + event.mut_event().phase = Phase_Bubbling; + + for &cur_target in chain.iter() { + //XXX bad clone + let stopped = match cur_target.eventtarget().get_listeners_for(type_.clone(), Bubbling) { + Some(listeners) => { + event.mut_event().current_target = Some(cur_target); + for listener in listeners.iter() { + listener.HandleEvent__(event, eReportExceptions); + + if event.event().stop_immediate { + break; + } + } + + event.propagation_stopped() + } + None => false + }; + if stopped { + break; + } + } + } + + let event = event.mut_event(); + event.dispatching = false; + event.phase = Phase_None; + event.current_target = None; + + !event.DefaultPrevented() +} diff --git a/src/components/script/dom/eventtarget.rs b/src/components/script/dom/eventtarget.rs index 35a1fe6e6cc..d477d0f8f47 100644 --- a/src/components/script/dom/eventtarget.rs +++ b/src/components/script/dom/eventtarget.rs @@ -3,24 +3,196 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use dom::bindings::codegen::EventTargetBinding; -use dom::bindings::utils::{Reflectable, Reflector}; +use dom::bindings::utils::{Reflectable, Reflector, DOMString, Fallible, DerivedWrapper}; +use dom::bindings::utils::{null_str_as_word_null, InvalidState}; +use dom::bindings::codegen::EventListenerBinding::EventListener; +use dom::event::AbstractEvent; +use dom::eventdispatcher::dispatch_event; +use dom::node::{AbstractNode, ScriptView}; use script_task::page_from_context; -use js::jsapi::{JSObject, JSContext}; +use js::jsapi::{JSObject, JSContext, JSVal}; +use js::glue::RUST_OBJECT_TO_JSVAL; -pub struct EventTarget { - reflector_: Reflector +use std::cast; +use std::hashmap::HashMap; +use std::unstable::raw::Box; + +#[deriving(Eq)] +pub enum ListenerPhase { + Capturing, + Bubbling, } -impl EventTarget { - pub fn new() -> ~EventTarget { - ~EventTarget { - reflector_: Reflector::new() +#[deriving(Eq)] +pub enum EventTargetTypeId { + WindowTypeId, + NodeTypeId +} + +#[deriving(Eq)] +struct EventListenerEntry { + phase: ListenerPhase, + listener: EventListener +} + +pub struct EventTarget { + type_id: EventTargetTypeId, + reflector_: Reflector, + handlers: HashMap<~str, ~[EventListenerEntry]>, +} + +pub struct AbstractEventTarget { + eventtarget: *mut Box +} + +impl AbstractEventTarget { + pub fn from_box(box: *mut Box) -> AbstractEventTarget { + AbstractEventTarget { + eventtarget: box as *mut Box } } - pub fn init_wrapper(@mut self, cx: *JSContext, scope: *JSObject) { - self.wrap_object_shared(cx, scope); + pub fn from_node(node: AbstractNode) -> AbstractEventTarget { + unsafe { + cast::transmute(node) + } + } + + pub fn type_id(&self) -> EventTargetTypeId { + self.eventtarget().type_id + } + + pub fn is_window(&self) -> bool { + self.type_id() == WindowTypeId + } + + pub fn is_node(&self) -> bool { + self.type_id() == NodeTypeId + } + + // + // Downcasting borrows + // + + fn transmute<'a, T>(&'a self) -> &'a T { + unsafe { + let box: *Box = self.eventtarget as *Box; + &(*box).data + } + } + + fn transmute_mut<'a, T>(&'a mut self) -> &'a mut T { + unsafe { + let box: *mut Box = self.eventtarget as *mut Box; + &mut (*box).data + } + } + + pub fn eventtarget<'a>(&'a self) -> &'a EventTarget { + self.transmute() + } + + pub fn mut_eventtarget<'a>(&'a mut self) -> &'a mut EventTarget { + self.transmute_mut() + } +} + +impl DerivedWrapper for AbstractEventTarget { + #[fixed_stack_segment] + fn wrap(&mut self, _cx: *JSContext, _scope: *JSObject, vp: *mut JSVal) -> i32 { + let wrapper = self.reflector().get_jsobject(); + if wrapper.is_not_null() { + unsafe { *vp = RUST_OBJECT_TO_JSVAL(wrapper) }; + return 1; + } + unreachable!() + } +} + +impl Reflectable for AbstractEventTarget { + fn reflector<'a>(&'a self) -> &'a Reflector { + self.eventtarget().reflector() + } + + fn mut_reflector<'a>(&'a mut self) -> &'a mut Reflector { + self.mut_eventtarget().mut_reflector() + } + + fn wrap_object_shared(@mut self, _cx: *JSContext, _scope: *JSObject) -> *JSObject { + unreachable!() + } + + fn GetParentObject(&self, cx: *JSContext) -> Option<@mut Reflectable> { + self.eventtarget().GetParentObject(cx) + } +} + +impl EventTarget { + pub fn new_inherited(type_id: EventTargetTypeId) -> EventTarget { + EventTarget { + type_id: type_id, + reflector_: Reflector::new(), + handlers: HashMap::new(), + } + } + + pub fn get_listeners(&self, type_: ~str) -> Option<~[EventListener]> { + do self.handlers.find_equiv(&type_).map |listeners| { + listeners.iter().map(|entry| entry.listener).collect() + } + } + + pub fn get_listeners_for(&self, type_: ~str, desired_phase: ListenerPhase) + -> Option<~[EventListener]> { + do self.handlers.find_equiv(&type_).map |listeners| { + let filtered = listeners.iter().filter(|entry| entry.phase == desired_phase); + filtered.map(|entry| entry.listener).collect() + } + } + + pub fn AddEventListener(&mut self, + ty: &DOMString, + listener: Option, + capture: bool) { + for &listener in listener.iter() { + let entry = self.handlers.find_or_insert_with(null_str_as_word_null(ty), |_| ~[]); + let phase = if capture { Capturing } else { Bubbling }; + let new_entry = EventListenerEntry { + phase: phase, + listener: listener + }; + if entry.position_elem(&new_entry).is_none() { + entry.push(new_entry); + } + } + } + + pub fn RemoveEventListener(&mut self, + ty: &DOMString, + listener: Option, + capture: bool) { + for &listener in listener.iter() { + let mut entry = self.handlers.find_mut(&null_str_as_word_null(ty)); + for entry in entry.mut_iter() { + let phase = if capture { Capturing } else { Bubbling }; + let old_entry = EventListenerEntry { + phase: phase, + listener: listener + }; + let position = entry.position_elem(&old_entry); + for &position in position.iter() { + entry.remove(position); + } + } + } + } + + pub fn DispatchEvent(&self, abstract_self: AbstractEventTarget, event: AbstractEvent) -> Fallible { + if event.event().dispatching || !event.event().initialized { + return Err(InvalidState); + } + Ok(dispatch_event(abstract_self, event)) } } diff --git a/src/components/script/dom/mouseevent.rs b/src/components/script/dom/mouseevent.rs index 70862d91904..e33bd485932 100644 --- a/src/components/script/dom/mouseevent.rs +++ b/src/components/script/dom/mouseevent.rs @@ -5,7 +5,8 @@ use dom::bindings::codegen::MouseEventBinding; use dom::bindings::utils::{ErrorResult, Fallible, DOMString}; use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object}; -use dom::eventtarget::EventTarget; +use dom::event::{AbstractEvent, Event, MouseEventTypeId}; +use dom::eventtarget::AbstractEventTarget; use dom::uievent::UIEvent; use dom::window::Window; use dom::windowproxy::WindowProxy; @@ -23,38 +24,42 @@ pub struct MouseEvent { alt_key: bool, meta_key: bool, button: u16, - related_target: Option<@mut EventTarget> + related_target: Option } impl MouseEvent { - pub fn new(window: @mut Window, type_: &DOMString, can_bubble: bool, cancelable: bool, - view: Option<@mut WindowProxy>, detail: i32, screen_x: i32, - screen_y: i32, client_x: i32, client_y: i32, ctrl_key: bool, - shift_key: bool, alt_key: bool, meta_key: bool, button: u16, - _buttons: u16, related_target: Option<@mut EventTarget>) -> @mut MouseEvent { - let ev = @mut MouseEvent { - parent: UIEvent::new_inherited(type_, can_bubble, cancelable, view, detail), - screen_x: screen_x, - screen_y: screen_y, - client_x: client_x, - client_y: client_y, - ctrl_key: ctrl_key, - shift_key: shift_key, - alt_key: alt_key, - meta_key: meta_key, - button: button, - related_target: related_target - }; - reflect_dom_object(ev, window, MouseEventBinding::Wrap) + pub fn new_inherited() -> MouseEvent { + MouseEvent { + parent: UIEvent::new_inherited(MouseEventTypeId), + screen_x: 0, + screen_y: 0, + client_x: 0, + client_y: 0, + ctrl_key: false, + shift_key: false, + alt_key: false, + meta_key: false, + button: 0, + related_target: None + } + } + + pub fn new(window: @mut Window) -> AbstractEvent { + Event::as_abstract(reflect_dom_object(@mut MouseEvent::new_inherited(), + window, + MouseEventBinding::Wrap)) } pub fn Constructor(owner: @mut Window, type_: &DOMString, - init: &MouseEventBinding::MouseEventInit) -> Fallible<@mut MouseEvent> { - Ok(MouseEvent::new(owner, type_, init.bubbles, init.cancelable, init.view, init.detail, - init.screenX, init.screenY, init.clientX, init.clientY, - init.ctrlKey, init.shiftKey, init.altKey, init.metaKey, - init.button, init.buttons, init.relatedTarget)) + init: &MouseEventBinding::MouseEventInit) -> Fallible { + let ev = MouseEvent::new(owner); + ev.mut_mouseevent().InitMouseEvent(type_, init.bubbles, init.cancelable, init.view, + init.detail, init.screenX, init.screenY, + init.clientX, init.clientY, init.ctrlKey, + init.altKey, init.shiftKey, init.metaKey, + init.button, init.relatedTarget); + Ok(ev) } pub fn ScreenX(&self) -> i32 { @@ -98,7 +103,7 @@ impl MouseEvent { 0 } - pub fn GetRelatedTarget(&self) -> Option<@mut EventTarget> { + pub fn GetRelatedTarget(&self) -> Option { self.related_target } @@ -122,7 +127,7 @@ impl MouseEvent { shiftKeyArg: bool, metaKeyArg: bool, buttonArg: u16, - relatedTargetArg: Option<@mut EventTarget>) -> ErrorResult { + relatedTargetArg: Option) -> ErrorResult { self.parent.InitUIEvent(typeArg, canBubbleArg, cancelableArg, viewArg, detailArg); self.screen_x = screenXArg; self.screen_y = screenYArg; diff --git a/src/components/script/dom/node.rs b/src/components/script/dom/node.rs index 7869f103cae..a25421ddbb4 100644 --- a/src/components/script/dom/node.rs +++ b/src/components/script/dom/node.rs @@ -12,6 +12,7 @@ use dom::document::{AbstractDocument, DocumentTypeId}; use dom::documenttype::DocumentType; use dom::element::{Element, ElementTypeId, HTMLImageElementTypeId, HTMLIframeElementTypeId}; use dom::element::{HTMLStyleElementTypeId}; +use dom::eventtarget::{AbstractEventTarget, EventTarget, NodeTypeId}; use dom::nodelist::{NodeList}; use dom::htmlimageelement::HTMLImageElement; use dom::htmliframeelement::HTMLIFrameElement; @@ -63,7 +64,7 @@ pub struct AbstractNodeChildrenIterator { /// `LayoutData`. pub struct Node { /// The JavaScript reflector for this node. - reflector_: Reflector, + eventtarget: EventTarget, /// The type of node that this is. type_id: NodeTypeId, @@ -210,6 +211,13 @@ impl<'self, View> AbstractNode { } } + pub fn from_eventtarget(target: AbstractEventTarget) -> AbstractNode { + assert!(target.is_node()); + unsafe { + cast::transmute(target) + } + } + // Convenience accessors /// Returns the type ID of this node. Fails if this node is borrowed mutably. @@ -521,7 +529,7 @@ impl Node { fn new_(type_id: NodeTypeId, doc: Option) -> Node { Node { - reflector_: Reflector::new(), + eventtarget: EventTarget::new_inherited(NodeTypeId), type_id: type_id, abstract: None, @@ -1042,11 +1050,11 @@ impl Node { impl Reflectable for Node { fn reflector<'a>(&'a self) -> &'a Reflector { - &self.reflector_ + self.eventtarget.reflector() } fn mut_reflector<'a>(&'a mut self) -> &'a mut Reflector { - &mut self.reflector_ + self.eventtarget.mut_reflector() } fn wrap_object_shared(@mut self, _cx: *JSContext, _scope: *JSObject) -> *JSObject { diff --git a/src/components/script/dom/uievent.rs b/src/components/script/dom/uievent.rs index 8871ccafd92..86f84cf25b5 100644 --- a/src/components/script/dom/uievent.rs +++ b/src/components/script/dom/uievent.rs @@ -6,7 +6,7 @@ use dom::bindings::codegen::UIEventBinding; use dom::bindings::utils::{DOMString, Fallible}; use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object}; use dom::node::{AbstractNode, ScriptView}; -use dom::event::Event; +use dom::event::{AbstractEvent, Event, EventTypeId, UIEventTypeId}; use dom::window::Window; use dom::windowproxy::WindowProxy; @@ -14,36 +14,33 @@ use js::jsapi::{JSObject, JSContext}; pub struct UIEvent { parent: Event, - can_bubble: bool, - cancelable: bool, view: Option<@mut WindowProxy>, detail: i32 } impl UIEvent { - pub fn new_inherited(type_: &DOMString, can_bubble: bool, cancelable: bool, - view: Option<@mut WindowProxy>, detail: i32) -> UIEvent { + pub fn new_inherited(type_id: EventTypeId) -> UIEvent { UIEvent { - parent: Event::new_inherited(type_), - can_bubble: can_bubble, - cancelable: cancelable, - view: view, - detail: detail + parent: Event::new_inherited(type_id), + view: None, + detail: 0 } } - pub fn new(window: @mut Window, type_: &DOMString, can_bubble: bool, cancelable: bool, - view: Option<@mut WindowProxy>, detail: i32) -> @mut UIEvent { - reflect_dom_object(@mut UIEvent::new_inherited(type_, can_bubble, cancelable, view, detail), - window, - UIEventBinding::Wrap) + pub fn new(window: @mut Window, type_id: EventTypeId) -> AbstractEvent { + let ev = reflect_dom_object(@mut UIEvent::new_inherited(type_id), + window, + UIEventBinding::Wrap); + Event::as_abstract(ev) } pub fn Constructor(owner: @mut Window, type_: &DOMString, - init: &UIEventBinding::UIEventInit) -> Fallible<@mut UIEvent> { - Ok(UIEvent::new(owner, type_, init.parent.bubbles, init.parent.cancelable, - init.view, init.detail)) + init: &UIEventBinding::UIEventInit) -> Fallible { + let ev = UIEvent::new(owner, UIEventTypeId); + ev.mut_uievent().InitUIEvent(type_, init.parent.bubbles, init.parent.cancelable, + init.view, init.detail); + Ok(ev) } pub fn GetView(&self) -> Option<@mut WindowProxy> { @@ -61,8 +58,6 @@ impl UIEvent { view: Option<@mut WindowProxy>, detail: i32) { self.parent.InitEvent(type_, can_bubble, cancelable); - self.can_bubble = can_bubble; - self.cancelable = cancelable; self.view = view; self.detail = detail; } diff --git a/src/components/script/dom/window.rs b/src/components/script/dom/window.rs index 0a5bb19d3ec..3701c22554d 100644 --- a/src/components/script/dom/window.rs +++ b/src/components/script/dom/window.rs @@ -6,6 +6,7 @@ use dom::bindings::codegen::WindowBinding; use dom::bindings::utils::{Reflectable, Reflector}; use dom::bindings::utils::{DOMString, null_str_as_empty, Traceable}; use dom::document::AbstractDocument; +use dom::eventtarget::{EventTarget, WindowTypeId}; use dom::node::{AbstractNode, ScriptView}; use dom::navigator::Navigator; @@ -37,10 +38,10 @@ pub enum TimerControlMsg { } pub struct Window { + eventtarget: EventTarget, page: @mut Page, script_chan: ScriptChan, compositor: @ScriptListener, - reflector_: Reflector, timer_chan: SharedChan, navigator: Option<@mut Navigator>, image_cache_task: ImageCacheTask, @@ -140,11 +141,11 @@ impl Window { impl Reflectable for Window { fn reflector<'a>(&'a self) -> &'a Reflector { - &self.reflector_ + self.eventtarget.reflector() } fn mut_reflector<'a>(&'a mut self) -> &'a mut Reflector { - &mut self.reflector_ + self.eventtarget.mut_reflector() } fn wrap_object_shared(@mut self, cx: *JSContext, scope: *JSObject) -> *JSObject { @@ -204,10 +205,10 @@ impl Window { image_cache_task: ImageCacheTask) -> @mut Window { let win = @mut Window { + eventtarget: EventTarget::new_inherited(WindowTypeId), page: page, script_chan: script_chan.clone(), compositor: compositor, - reflector_: Reflector::new(), timer_chan: { let (timer_port, timer_chan) = comm::stream::(); let id = page.id.clone(); diff --git a/src/components/script/script.rc b/src/components/script/script.rc index bef239a5ec9..0307e70b580 100644 --- a/src/components/script/script.rc +++ b/src/components/script/script.rc @@ -28,6 +28,7 @@ pub mod dom { pub mod element; pub mod node; pub mod utils; + pub mod callback; pub mod conversions; pub mod proxyhandler; pub mod codegen { @@ -54,6 +55,7 @@ pub mod dom { pub mod domparser; pub mod element; pub mod event; + pub mod eventdispatcher; pub mod eventtarget; pub mod formdata; pub mod htmlanchorelement; diff --git a/src/support/spidermonkey/rust-mozjs b/src/support/spidermonkey/rust-mozjs index 1df26877d82..566c2af971a 160000 --- a/src/support/spidermonkey/rust-mozjs +++ b/src/support/spidermonkey/rust-mozjs @@ -1 +1 @@ -Subproject commit 1df26877d82d9722785de91ff59ab069a5acc180 +Subproject commit 566c2af971abaa5e8c51b59fa400a7e07835b257 diff --git a/src/test/html/content/test_Event.html b/src/test/html/content/test_Event.html index 7523c843206..0643df62e44 100644 --- a/src/test/html/content/test_Event.html +++ b/src/test/html/content/test_Event.html @@ -4,7 +4,7 @@ + + +Paragraph containing
event listener
.
+ + + diff --git a/src/test/html/content/test_event_dispatch_dynamic.html b/src/test/html/content/test_event_dispatch_dynamic.html new file mode 100644 index 00000000000..3b852fe497d --- /dev/null +++ b/src/test/html/content/test_event_dispatch_dynamic.html @@ -0,0 +1,21 @@ + + + + diff --git a/src/test/html/content/test_event_dispatch_order.html b/src/test/html/content/test_event_dispatch_order.html new file mode 100644 index 00000000000..e1b381d0b77 --- /dev/null +++ b/src/test/html/content/test_event_dispatch_order.html @@ -0,0 +1,42 @@ + + + + + +
+ + + diff --git a/src/test/html/content/test_event_listener.html b/src/test/html/content/test_event_listener.html new file mode 100644 index 00000000000..5096d76349a --- /dev/null +++ b/src/test/html/content/test_event_listener.html @@ -0,0 +1,39 @@ + + + + + + + +