diff options
-rw-r--r-- | components/script/dom/bindings/codegen/CodegenRust.py | 520 | ||||
-rw-r--r-- | components/script/dom/bindings/codegen/Configuration.py | 40 | ||||
-rw-r--r-- | components/script/dom/bindings/codegen/parser/WebIDL.py | 4 | ||||
-rw-r--r-- | components/script/dom/bindings/codegen/parser/callback-location.patch | 22 | ||||
-rwxr-xr-x | components/script/dom/bindings/codegen/parser/update.sh | 1 | ||||
-rw-r--r-- | components/script/dom/bindings/iterable.rs | 161 | ||||
-rw-r--r-- | components/script/dom/bindings/mod.rs | 1 | ||||
-rw-r--r-- | components/script/dom/mod.rs | 2 | ||||
-rw-r--r-- | components/script/dom/testbindingiterable.rs | 43 | ||||
-rw-r--r-- | components/script/dom/testbindingpairiterable.rs | 54 | ||||
-rw-r--r-- | components/script/dom/webidls/IterableIterator.webidl | 16 | ||||
-rw-r--r-- | components/script/dom/webidls/TestBindingIterable.webidl | 14 | ||||
-rw-r--r-- | components/script/dom/webidls/TestBindingPairIterable.webidl | 12 | ||||
-rw-r--r-- | tests/wpt/mozilla/meta/MANIFEST.json | 6 | ||||
-rw-r--r-- | tests/wpt/mozilla/meta/mozilla/iterable.html.ini | 3 | ||||
-rw-r--r-- | tests/wpt/mozilla/tests/mozilla/iterable.html | 95 |
16 files changed, 847 insertions, 147 deletions
diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index 7e80e6c9969..f155362415d 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -21,6 +21,7 @@ from WebIDL import ( IDLType, IDLInterfaceMember, IDLUndefinedValue, + IDLWrapperType, ) from Configuration import ( @@ -29,6 +30,7 @@ from Configuration import ( getTypesFromCallback, getTypesFromDescriptor, getTypesFromDictionary, + iteratorNativeType ) AUTOGENERATED_WARNING_COMMENT = \ @@ -719,7 +721,9 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, raise TypeError("Can't handle array arguments yet") if type.isSequence(): - innerInfo = getJSToNativeConversionInfo(innerSequenceType(type), descriptorProvider) + innerInfo = getJSToNativeConversionInfo(innerSequenceType(type), + descriptorProvider, + isMember=isMember) declType = CGWrapper(innerInfo.declType, pre="Vec<", post=">") config = getConversionConfigForType(type, isEnforceRange, isClamp, treatNullAs) @@ -942,7 +946,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, assert not type.treatNonObjectAsNull() or not type.treatNonCallableAsNull() callback = type.unroll().callback - declType = CGGeneric('%s::%s' % (getModuleFromObject(callback), callback.identifier.name)) + declType = CGGeneric(callback.identifier.name) finalDeclType = CGTemplatedType("Rc", declType) conversion = CGCallbackTempRoot(declType.define()) @@ -1518,6 +1522,46 @@ class MethodDefiner(PropertyDefiner): "length": 0, "condition": "Condition::Satisfied"}) + # Generate the keys/values/entries aliases for value iterables. + maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable + if (not static and not unforgeable and + (maplikeOrSetlikeOrIterable and + maplikeOrSetlikeOrIterable.isIterable() and + maplikeOrSetlikeOrIterable.isValueIterator())): + # Add our keys/values/entries/forEach + self.regular.append({ + "name": "keys", + "methodInfo": False, + "selfHostedName": "ArrayKeys", + "length": 0, + "condition": PropertyDefiner.getControllingCondition(m, + descriptor) + }) + self.regular.append({ + "name": "values", + "methodInfo": False, + "selfHostedName": "ArrayValues", + "length": 0, + "condition": PropertyDefiner.getControllingCondition(m, + descriptor) + }) + self.regular.append({ + "name": "entries", + "methodInfo": False, + "selfHostedName": "ArrayEntries", + "length": 0, + "condition": PropertyDefiner.getControllingCondition(m, + descriptor) + }) + self.regular.append({ + "name": "forEach", + "methodInfo": False, + "selfHostedName": "ArrayForEach", + "length": 0, + "condition": PropertyDefiner.getControllingCondition(m, + descriptor) + }) + isUnforgeableInterface = bool(descriptor.interface.getExtendedAttribute("Unforgeable")) if not static and unforgeable == isUnforgeableInterface: stringifier = descriptor.operations['Stringifier'] @@ -1738,7 +1782,7 @@ class CGImports(CGWrapper): """ Generates the appropriate import/use statements. """ - def __init__(self, child, descriptors, callbacks, imports, config, ignored_warnings=None): + def __init__(self, child, descriptors, callbacks, dictionaries, enums, imports, config, ignored_warnings=None): """ Adds a set of imports. """ @@ -1752,17 +1796,19 @@ class CGImports(CGWrapper): ] def componentTypes(type): - if type.nullable(): + if type.isType() and type.nullable(): type = type.unroll() if type.isUnion(): return type.flatMemberTypes + if type.isDictionary(): + return [type] + getTypesFromDictionary(type) return [type] def isImportable(type): if not type.isType(): - assert type.isInterface() - return not type.isCallback() - return type.isNonCallbackInterface() and not type.builtin + assert type.isInterface() or type.isDictionary() or type.isEnum() + return True + return not (type.builtin or type.isSequence() or type.isUnion()) def relatedTypesForSignatures(method): types = [] @@ -1774,13 +1820,30 @@ class CGImports(CGWrapper): def getIdentifier(t): if t.isType(): - return t.inner.identifier - assert t.isInterface() + if t.nullable(): + t = t.inner + if t.isCallback(): + return t.callback.identifier + return t.identifier + assert t.isInterface() or t.isDictionary() or t.isEnum() return t.identifier + def removeWrapperAndNullableTypes(types): + normalized = [] + for t in types: + while (t.isType() and t.nullable()) or isinstance(t, IDLWrapperType): + t = t.inner + if isImportable(t): + normalized += [t] + return normalized + types = [] for d in descriptors: - types += [d.interface] + if not d.interface.isCallback(): + types += [d.interface] + + if d.interface.isIteratorInterface(): + types += [d.interface.iterableInterface] members = d.interface.members + d.interface.namedConstructors constructor = d.interface.ctor() @@ -1796,19 +1859,39 @@ class CGImports(CGWrapper): elif m.isAttr(): types += componentTypes(m.type) + # Import the type names used in the callbacks that are being defined. for c in callbacks: types += relatedTypesForSignatures(c) + # Import the type names used in the dictionaries that are being defined. + for d in dictionaries: + types += componentTypes(d) + + # Normalize the types we've collected and remove any ones which can't be imported. + types = removeWrapperAndNullableTypes(types) + descriptorProvider = config.getDescriptorProvider() + extras = [] for t in types: - if isImportable(t): + # Importing these types in the same module that defines them is an error. + if t in dictionaries or t in enums: + continue + if t.isInterface(): descriptor = descriptorProvider.getDescriptor(getIdentifier(t).name) - imports += ['%s' % descriptor.path] + extras += [descriptor.path] + if descriptor.interface.parent: + parentName = getIdentifier(descriptor.interface.parent).name + descriptor = descriptorProvider.getDescriptor(parentName) + extras += [descriptor.path, descriptor.bindingPath] + else: + if t.isEnum(): + extras += [getModuleFromObject(t) + '::' + getIdentifier(t).name + 'Values'] + extras += [getModuleFromObject(t) + '::' + getIdentifier(t).name] statements = [] if len(ignored_warnings) > 0: statements.append('#![allow(%s)]' % ','.join(ignored_warnings)) - statements.extend('use %s;' % i for i in sorted(set(imports))) + statements.extend('use %s;' % i for i in sorted(set(imports + extras))) CGWrapper.__init__(self, child, pre='\n'.join(statements) + '\n\n') @@ -1868,7 +1951,7 @@ def DOMClass(descriptor): # padding. protoList.extend(['PrototypeList::ID::Last'] * (descriptor.config.maxProtoChainLength - len(protoList))) prototypeChainString = ', '.join(protoList) - heapSizeOf = 'heap_size_of_raw_self_and_children::<%s>' % descriptor.interface.identifier.name + heapSizeOf = 'heap_size_of_raw_self_and_children::<%s>' % descriptor.concreteType if descriptor.isGlobal(): globals_ = camel_to_upper_snake(descriptor.name) else: @@ -2111,7 +2194,14 @@ def UnionTypes(descriptors, dictionaries, callbacks, config): # Sort unionStructs by key, retrieve value unionStructs = (i[1] for i in sorted(unionStructs.items(), key=operator.itemgetter(0))) - return CGImports(CGList(unionStructs, "\n\n"), [], [], imports, config, ignored_warnings=[]) + return CGImports(CGList(unionStructs, "\n\n"), + descriptors=[], + callbacks=[], + dictionaries=[], + enums=[], + imports=imports, + config=config, + ignored_warnings=[]) class Argument(): @@ -2442,7 +2532,7 @@ class CGIDLInterface(CGThing): def define(self): interface = self.descriptor.interface - name = self.descriptor.name + name = self.descriptor.concreteType if (interface.getUserData("hasConcreteDescendant", False) or interface.getUserData("hasProxyDescendant", False)): depth = self.descriptor.prototypeDepth @@ -2549,6 +2639,8 @@ assert!((*cache)[PrototypeList::Constructor::%(id)s as usize].is_null()); if len(self.descriptor.prototypeChain) == 1: if self.descriptor.interface.getExtendedAttribute("ExceptionClass"): getPrototypeProto = "prototype_proto.set(JS_GetErrorPrototype(cx))" + elif self.descriptor.interface.isIteratorInterface(): + getPrototypeProto = "prototype_proto.set(JS_GetIteratorPrototype(cx))" else: getPrototypeProto = "prototype_proto.set(JS_GetObjectPrototype(cx, global))" else: @@ -2628,6 +2720,55 @@ assert!((*cache)[PrototypeList::Constructor::%(id)s as usize].is_null()); interface.get()); """ % properties)) + aliasedMembers = [m for m in self.descriptor.interface.members if m.isMethod() and m.aliases] + if aliasedMembers: + def defineAlias(alias): + if alias == "@@iterator": + symbolJSID = "RUST_SYMBOL_TO_JSID(GetWellKnownSymbol(cx, SymbolCode::iterator))" + getSymbolJSID = CGGeneric(fill("rooted!(in(cx) let iteratorId = ${symbolJSID});", + symbolJSID=symbolJSID)) + defineFn = "JS_DefinePropertyById2" + prop = "iteratorId.handle()" + elif alias.startswith("@@"): + raise TypeError("Can't handle any well-known Symbol other than @@iterator") + else: + getSymbolJSID = None + defineFn = "JS_DefineProperty" + prop = '"%s"' % alias + return CGList([ + getSymbolJSID, + # XXX If we ever create non-enumerable properties that can + # be aliased, we should consider making the aliases + # match the enumerability of the property being aliased. + CGGeneric(fill( + """ + assert!(${defineFn}(cx, prototype.handle(), ${prop}, aliasedVal.handle(), + JSPROP_ENUMERATE, None, None)); + """, + defineFn=defineFn, + prop=prop)) + ], "\n") + + def defineAliasesFor(m): + return CGList([ + CGGeneric(fill( + """ + assert!(JS_GetProperty(cx, prototype.handle(), + b\"${prop}\0\" as *const u8 as *const _, + aliasedVal.handle_mut())); + """, + prop=m.identifier.name)) + ] + [defineAlias(alias) for alias in sorted(m.aliases)]) + + defineAliases = CGList([ + CGGeneric(fill(""" + // Set up aliases on the interface prototype object we just created. + + """)), + CGGeneric("rooted!(in(cx) let mut aliasedVal = UndefinedValue());\n\n") + ] + [defineAliasesFor(m) for m in sorted(aliasedMembers)]) + code.append(defineAliases) + constructors = self.descriptor.interface.namedConstructors if constructors: decl = "let named_constructors: [(NonNullJSNative, &'static [u8], u32); %d]" % len(constructors) @@ -2709,7 +2850,7 @@ class CGGetProtoObjectMethod(CGGetPerInterfaceObject): """ def __init__(self, descriptor): CGGetPerInterfaceObject.__init__(self, descriptor, "GetProtoObject", - "PrototypeList::ID", pub=descriptor.hasDescendants()) + "PrototypeList::ID", pub=True) def definition_body(self): return CGList([ @@ -2727,7 +2868,7 @@ class CGGetConstructorObjectMethod(CGGetPerInterfaceObject): def __init__(self, descriptor): CGGetPerInterfaceObject.__init__(self, descriptor, "GetConstructorObject", "PrototypeList::Constructor", - pub=descriptor.hasDescendants()) + pub=True) def definition_body(self): return CGList([ @@ -2966,11 +3107,21 @@ class CGPerSignatureCall(CGThing): if self.isFallible(): errorResult = " false" - cgThings.append(CGCallGenerator( - errorResult, - self.getArguments(), self.argsPre, returnType, - self.extendedAttributes, descriptor, nativeMethodName, - static)) + if idlNode.isMethod() and idlNode.isMaplikeOrSetlikeOrIterableMethod(): + if idlNode.maplikeOrSetlikeOrIterable.isMaplike() or \ + idlNode.maplikeOrSetlikeOrIterable.isSetlike(): + raise TypeError('Maplike/Setlike methods are not supported yet') + else: + cgThings.append(CGIterableMethodGenerator(descriptor, + idlNode.maplikeOrSetlikeOrIterable, + idlNode.identifier.name)) + else: + cgThings.append(CGCallGenerator( + errorResult, + self.getArguments(), self.argsPre, returnType, + self.extendedAttributes, descriptor, nativeMethodName, + static)) + self.cgRoot = CGList(cgThings, "\n") def getArgs(self): @@ -5008,6 +5159,7 @@ class CGInterfaceTrait(CGThing): def members(): for m in descriptor.interface.members: if (m.isMethod() and not m.isStatic() and + not m.isMaplikeOrSetlikeOrIterableMethod() and (not m.isIdentifierLess() or m.isStringifier())): name = CGSpecializedMethod.makeNativeName(descriptor, m) infallible = 'infallible' in descriptor.getExtendedAttributes(m) @@ -5068,6 +5220,7 @@ class CGInterfaceTrait(CGThing): post="}") else: self.cgRoot = CGGeneric("") + self.empty = not methods def define(self): return self.cgRoot.define() @@ -5083,18 +5236,136 @@ class CGWeakReferenceableTrait(CGThing): return self.code +def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries=None, enums=None): + if not callbacks: + callbacks = [] + if not dictionaries: + dictionaries = [] + if not enums: + enums = [] + + return CGImports(cgthings, descriptors, callbacks, dictionaries, enums, [ + 'js', + 'js::{JS_CALLEE, JSCLASS_GLOBAL_SLOT_COUNT}', + 'js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL, JSCLASS_RESERVED_SLOTS_MASK}', + 'js::error::throw_type_error', + 'js::jsapi::{JSJitInfo_AliasSet, JSJitInfo_ArgType, AutoIdVector, CallArgs, FreeOp}', + 'js::jsapi::{JSITER_SYMBOLS, JSPROP_ENUMERATE, JSPROP_PERMANENT, JSPROP_READONLY, JSPROP_SHARED}', + 'js::jsapi::{JSCLASS_RESERVED_SLOTS_SHIFT, JSITER_HIDDEN, JSITER_OWNONLY}', + 'js::jsapi::{GetPropertyKeys, Handle, Call, GetWellKnownSymbol}', + 'js::jsapi::{HandleId, HandleObject, HandleValue, HandleValueArray}', + 'js::jsapi::{INTERNED_STRING_TO_JSID, IsCallable, JS_CallFunctionValue}', + 'js::jsapi::{JS_CopyPropertiesFrom, JS_ForwardGetPropertyTo}', + 'js::jsapi::{JS_GetClass, JS_GetErrorPrototype, JS_GetFunctionPrototype}', + 'js::jsapi::{JS_GetGlobalForObject, JS_GetObjectPrototype, JS_GetProperty}', + 'js::jsapi::{JS_GetPropertyById, JS_GetPropertyDescriptorById, JS_GetReservedSlot}', + 'js::jsapi::{JS_HasProperty, JS_HasPropertyById, JS_InitializePropertiesFromCompatibleNativeObject}', + 'js::jsapi::{JS_AtomizeAndPinString, JS_NewObject, JS_NewObjectWithGivenProto}', + 'js::jsapi::{JS_NewObjectWithoutMetadata, JS_SetProperty, JS_DefinePropertyById2}', + 'js::jsapi::{JS_SplicePrototype, JS_SetReservedSlot, JSAutoCompartment}', + 'js::jsapi::{JSContext, JSClass, JSFreeOp, JSFunctionSpec, JS_GetIteratorPrototype}', + 'js::jsapi::{JSJitGetterCallArgs, JSJitInfo, JSJitMethodCallArgs, JSJitSetterCallArgs}', + 'js::jsapi::{JSNative, JSObject, JSNativeWrapper, JSPropertySpec}', + 'js::jsapi::{JSString, JSTracer, JSType, JSTypedMethodJitInfo, JSValueType}', + 'js::jsapi::{ObjectOpResult, JSJitInfo_OpType, MutableHandle, MutableHandleObject}', + 'js::jsapi::{MutableHandleValue, PropertyDescriptor, RootedObject}', + 'js::jsapi::{SymbolCode, jsid}', + 'js::jsval::JSVal', + 'js::jsval::{ObjectValue, ObjectOrNullValue, PrivateValue}', + 'js::jsval::{NullValue, UndefinedValue}', + 'js::glue::{CallJitMethodOp, CallJitGetterOp, CallJitSetterOp, CreateProxyHandler}', + 'js::glue::{GetProxyPrivate, NewProxyObject, ProxyTraps}', + 'js::glue::{RUST_JSID_IS_STRING, int_to_jsid, RUST_SYMBOL_TO_JSID}', + 'js::glue::AppendToAutoIdVector', + 'js::rust::{GCMethods, define_methods, define_properties}', + 'dom', + 'dom::bindings', + 'dom::bindings::codegen::InterfaceObjectMap', + 'dom::bindings::global::{GlobalRef, global_root_from_object, global_root_from_reflector}', + 'dom::bindings::interface::{InterfaceConstructorBehavior, NonCallbackInterfaceObjectClass}', + 'dom::bindings::interface::{create_callback_interface_object, create_interface_prototype_object}', + 'dom::bindings::interface::{create_named_constructors, create_noncallback_interface_object}', + 'dom::bindings::interface::{define_guarded_methods, define_guarded_properties}', + 'dom::bindings::interface::{ConstantSpec, NonNullJSNative}', + 'dom::bindings::interface::ConstantVal::{IntVal, UintVal}', + 'dom::bindings::interface::is_exposed_in', + 'dom::bindings::iterable::{IteratorType, Iterable}', + 'dom::bindings::js::{JS, Root, RootedReference}', + 'dom::bindings::js::{OptionalRootedReference}', + 'dom::bindings::reflector::{Reflectable}', + 'dom::bindings::utils::{DOMClass, DOMJSClass}', + 'dom::bindings::utils::{DOM_PROTO_UNFORGEABLE_HOLDER_SLOT, JSCLASS_DOM_GLOBAL}', + 'dom::bindings::utils::{ProtoOrIfaceArray, create_dom_global}', + 'dom::bindings::utils::{enumerate_global, finalize_global, find_enum_string_index}', + 'dom::bindings::utils::{generic_getter, generic_lenient_getter, generic_lenient_setter}', + 'dom::bindings::utils::{generic_method, generic_setter, get_array_index_from_id}', + 'dom::bindings::utils::{get_dictionary_property, get_property_on_prototype}', + 'dom::bindings::utils::{get_proto_or_iface_array, has_property_on_prototype}', + 'dom::bindings::utils::{is_platform_object, resolve_global, set_dictionary_property, trace_global}', + 'dom::bindings::trace::{JSTraceable, RootedTraceable}', + 'dom::bindings::callback::{CallbackContainer,CallbackInterface,CallbackFunction}', + 'dom::bindings::callback::{CallSetup,ExceptionHandling}', + 'dom::bindings::callback::wrap_call_this_object', + 'dom::bindings::conversions::{ConversionBehavior, ConversionResult, DOM_OBJECT_SLOT}', + 'dom::bindings::conversions::{IDLInterface, is_array_like}', + 'dom::bindings::conversions::{FromJSValConvertible, StringificationBehavior}', + 'dom::bindings::conversions::{ToJSValConvertible, jsid_to_str, native_from_handlevalue}', + 'dom::bindings::conversions::{native_from_object, private_from_object, root_from_object}', + 'dom::bindings::conversions::{root_from_handleobject, root_from_handlevalue}', + 'dom::bindings::codegen::{PrototypeList, RegisterBindings, UnionTypes}', + 'dom::bindings::error::{Fallible, Error, ErrorResult}', + 'dom::bindings::error::Error::JSFailed', + 'dom::bindings::error::throw_dom_exception', + 'dom::bindings::guard::{Condition, Guard}', + 'dom::bindings::proxyhandler', + 'dom::bindings::proxyhandler::{ensure_expando_object, fill_property_descriptor}', + 'dom::bindings::proxyhandler::{get_expando_object, get_property_descriptor}', + 'dom::bindings::num::Finite', + 'dom::bindings::str::{ByteString, DOMString, USVString}', + 'dom::bindings::weakref::{DOM_WEAK_SLOT, WeakBox, WeakReferenceable}', + 'dom::browsingcontext::BrowsingContext', + 'mem::heap_size_of_raw_self_and_children', + 'libc', + 'util::prefs::PREFS', + 'script_runtime::{store_panic_result, maybe_take_panic_result}', + 'std::borrow::ToOwned', + 'std::cmp', + 'std::mem', + 'std::num', + 'std::os', + 'std::panic::{self, AssertUnwindSafe}', + 'std::ptr', + 'std::str', + 'std::rc', + 'std::rc::Rc', + 'std::default::Default', + 'std::ffi::CString', + ], config) + + class CGDescriptor(CGThing): - def __init__(self, descriptor): + def __init__(self, descriptor, config, soleDescriptor): CGThing.__init__(self) assert not descriptor.concrete or not descriptor.interface.isCallback() + reexports = [] + + def reexportedName(name): + if name.startswith(descriptor.name): + return name + if not soleDescriptor: + return '%s as %s%s' % (name, descriptor.name, name) + return name + cgThings = [] if not descriptor.interface.isCallback(): cgThings.append(CGGetProtoObjectMethod(descriptor)) + reexports.append('GetProtoObject') if (descriptor.interface.hasInterfaceObject() and descriptor.shouldHaveGetConstructorObjectMethod()): cgThings.append(CGGetConstructorObjectMethod(descriptor)) + reexports.append('GetConstructorObject') unscopableNames = [] for m in descriptor.interface.members: @@ -5156,9 +5427,11 @@ class CGDescriptor(CGThing): cgThings.append(CGNamespace.build([descriptor.name + "Constants"], CGConstant(constMembers), public=True)) + reexports.append(descriptor.name + 'Constants') - if descriptor.interface.hasInterfaceObject(): + if descriptor.interface.hasInterfaceObject() and descriptor.register: cgThings.append(CGDefineDOMInterfaceMethod(descriptor)) + reexports.append('DefineDOMInterface') cgThings.append(CGConstructorEnabled(descriptor)) if descriptor.proxy: @@ -5194,6 +5467,7 @@ class CGDescriptor(CGThing): pass cgThings.append(CGWrapMethod(descriptor)) + reexports.append('Wrap') haveUnscopables = False if not descriptor.interface.isCallback(): @@ -5206,7 +5480,12 @@ class CGDescriptor(CGThing): CGGeneric("];\n")], "\n")) if descriptor.concrete or descriptor.hasDescendants(): cgThings.append(CGIDLInterface(descriptor)) - cgThings.append(CGInterfaceTrait(descriptor)) + + interfaceTrait = CGInterfaceTrait(descriptor) + cgThings.append(interfaceTrait) + if not interfaceTrait.empty: + reexports.append('%sMethods' % descriptor.name) + if descriptor.weakReferenceable: cgThings.append(CGWeakReferenceableTrait(descriptor)) @@ -5214,11 +5493,13 @@ class CGDescriptor(CGThing): cgThings.append(CGGeneric(str(properties))) cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties, haveUnscopables)) - cgThings = CGList(cgThings, "\n") - # self.cgRoot = CGWrapper(CGNamespace(toBindingNamespace(descriptor.name), - # cgThings), - # post='\n') - self.cgRoot = cgThings + cgThings = generate_imports(config, CGList(cgThings, '\n'), [descriptor]) + cgThings = CGWrapper(CGNamespace(toBindingNamespace(descriptor.name), + cgThings, public=True), + post='\n') + reexports = ', '.join(map(lambda name: reexportedName(name), reexports)) + self.cgRoot = CGList([CGGeneric('pub use self::%sBinding::{%s};' % (descriptor.name, reexports)), + cgThings], '\n') def define(self): return self.cgRoot.define() @@ -5468,8 +5749,8 @@ class CGRegisterProxyHandlersMethod(CGAbstractMethod): def definition_body(self): return CGList([ - CGGeneric("proxy_handlers[Proxies::%s as usize] = codegen::Bindings::%sBinding::DefineProxyHandler();" - % (desc.name, desc.name)) + CGGeneric("proxy_handlers[Proxies::%s as usize] = Bindings::%s::DefineProxyHandler();" + % (desc.name, '::'.join([desc.name + 'Binding'] * 2))) for desc in self.descriptors ], "\n") @@ -5542,7 +5823,7 @@ class CGBindingRoot(CGThing): for c in mainCallbacks) # Do codegen for all the descriptors - cgthings.extend([CGDescriptor(x) for x in descriptors]) + cgthings.extend([CGDescriptor(x, config, len(descriptors) == 1) for x in descriptors]) # Do codegen for all the callback interfaces. cgthings.extend(CGList([CGCallbackInterface(x), @@ -5553,102 +5834,8 @@ class CGBindingRoot(CGThing): curr = CGWrapper(CGList(cgthings, "\n\n"), post="\n\n") # Add imports - curr = CGImports(curr, descriptors + callbackDescriptors, mainCallbacks, [ - 'js', - 'js::{JS_CALLEE, JSCLASS_GLOBAL_SLOT_COUNT}', - 'js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL, JSCLASS_RESERVED_SLOTS_MASK}', - 'js::error::throw_type_error', - 'js::jsapi::{JSJitInfo_AliasSet, JSJitInfo_ArgType, AutoIdVector, CallArgs, FreeOp}', - 'js::jsapi::{JSITER_SYMBOLS, JSPROP_ENUMERATE, JSPROP_PERMANENT, JSPROP_READONLY, JSPROP_SHARED}', - 'js::jsapi::{JSCLASS_RESERVED_SLOTS_SHIFT, JSITER_HIDDEN, JSITER_OWNONLY}', - 'js::jsapi::{GetPropertyKeys, Handle}', - 'js::jsapi::{HandleId, HandleObject, HandleValue, HandleValueArray}', - 'js::jsapi::{INTERNED_STRING_TO_JSID, IsCallable, JS_CallFunctionValue}', - 'js::jsapi::{JS_CopyPropertiesFrom, JS_ForwardGetPropertyTo}', - 'js::jsapi::{JS_GetClass, JS_GetErrorPrototype, JS_GetFunctionPrototype}', - 'js::jsapi::{JS_GetGlobalForObject, JS_GetObjectPrototype, JS_GetProperty}', - 'js::jsapi::{JS_GetPropertyById, JS_GetPropertyDescriptorById, JS_GetReservedSlot}', - 'js::jsapi::{JS_HasProperty, JS_HasPropertyById, JS_InitializePropertiesFromCompatibleNativeObject}', - 'js::jsapi::{JS_AtomizeAndPinString, JS_NewObject, JS_NewObjectWithGivenProto}', - 'js::jsapi::{JS_NewObjectWithoutMetadata, JS_SetProperty}', - 'js::jsapi::{JS_SplicePrototype, JS_SetReservedSlot, JSAutoCompartment}', - 'js::jsapi::{JSContext, JSClass, JSFreeOp, JSFunctionSpec}', - 'js::jsapi::{JSJitGetterCallArgs, JSJitInfo, JSJitMethodCallArgs, JSJitSetterCallArgs}', - 'js::jsapi::{JSNative, JSObject, JSNativeWrapper, JSPropertySpec}', - 'js::jsapi::{JSString, JSTracer, JSType, JSTypedMethodJitInfo, JSValueType}', - 'js::jsapi::{ObjectOpResult, JSJitInfo_OpType, MutableHandle, MutableHandleObject}', - 'js::jsapi::{MutableHandleValue, PropertyDescriptor, RootedObject}', - 'js::jsapi::{SymbolCode, jsid}', - 'js::jsval::JSVal', - 'js::jsval::{ObjectValue, ObjectOrNullValue, PrivateValue}', - 'js::jsval::{NullValue, UndefinedValue}', - 'js::glue::{CallJitMethodOp, CallJitGetterOp, CallJitSetterOp, CreateProxyHandler}', - 'js::glue::{GetProxyPrivate, NewProxyObject, ProxyTraps}', - 'js::glue::{RUST_JSID_IS_STRING, int_to_jsid}', - 'js::glue::AppendToAutoIdVector', - 'js::rust::{GCMethods, define_methods, define_properties}', - 'dom::bindings', - 'dom::bindings::codegen::InterfaceObjectMap', - 'dom::bindings::global::{GlobalRef, global_root_from_object, global_root_from_reflector}', - 'dom::bindings::interface::{InterfaceConstructorBehavior, NonCallbackInterfaceObjectClass}', - 'dom::bindings::interface::{create_callback_interface_object, create_interface_prototype_object}', - 'dom::bindings::interface::{create_named_constructors, create_noncallback_interface_object}', - 'dom::bindings::interface::{define_guarded_methods, define_guarded_properties}', - 'dom::bindings::interface::{ConstantSpec, NonNullJSNative}', - 'dom::bindings::interface::ConstantVal::{IntVal, UintVal}', - 'dom::bindings::interface::is_exposed_in', - 'dom::bindings::js::{JS, Root, RootedReference}', - 'dom::bindings::js::{OptionalRootedReference}', - 'dom::bindings::reflector::{Reflectable}', - 'dom::bindings::utils::{DOMClass, DOMJSClass}', - 'dom::bindings::utils::{DOM_PROTO_UNFORGEABLE_HOLDER_SLOT, JSCLASS_DOM_GLOBAL}', - 'dom::bindings::utils::{ProtoOrIfaceArray, create_dom_global}', - 'dom::bindings::utils::{enumerate_global, finalize_global, find_enum_string_index}', - 'dom::bindings::utils::{generic_getter, generic_lenient_getter, generic_lenient_setter}', - 'dom::bindings::utils::{generic_method, generic_setter, get_array_index_from_id}', - 'dom::bindings::utils::{get_dictionary_property, get_property_on_prototype}', - 'dom::bindings::utils::{get_proto_or_iface_array, has_property_on_prototype}', - 'dom::bindings::utils::{is_platform_object, resolve_global, set_dictionary_property, trace_global}', - 'dom::bindings::trace::{JSTraceable, RootedTraceable}', - 'dom::bindings::callback::{CallbackContainer,CallbackInterface,CallbackFunction}', - 'dom::bindings::callback::{CallSetup,ExceptionHandling}', - 'dom::bindings::callback::wrap_call_this_object', - 'dom::bindings::conversions::{ConversionBehavior, ConversionResult, DOM_OBJECT_SLOT}', - 'dom::bindings::conversions::{IDLInterface, is_array_like}', - 'dom::bindings::conversions::{FromJSValConvertible, StringificationBehavior}', - 'dom::bindings::conversions::{ToJSValConvertible, jsid_to_str, native_from_handlevalue}', - 'dom::bindings::conversions::{native_from_object, private_from_object, root_from_object}', - 'dom::bindings::conversions::{root_from_handleobject, root_from_handlevalue}', - 'dom::bindings::codegen::{PrototypeList, RegisterBindings, UnionTypes}', - 'dom::bindings::codegen::Bindings::*', - 'dom::bindings::error::{Fallible, Error, ErrorResult}', - 'dom::bindings::error::Error::JSFailed', - 'dom::bindings::error::throw_dom_exception', - 'dom::bindings::guard::{Condition, Guard}', - 'dom::bindings::proxyhandler', - 'dom::bindings::proxyhandler::{ensure_expando_object, fill_property_descriptor}', - 'dom::bindings::proxyhandler::{get_expando_object, get_property_descriptor}', - 'dom::bindings::num::Finite', - 'dom::bindings::str::{ByteString, DOMString, USVString}', - 'dom::bindings::weakref::{DOM_WEAK_SLOT, WeakBox, WeakReferenceable}', - 'dom::browsingcontext::BrowsingContext', - 'mem::heap_size_of_raw_self_and_children', - 'libc', - 'util::prefs::PREFS', - 'script_runtime::{store_panic_result, maybe_take_panic_result}', - 'std::borrow::ToOwned', - 'std::cmp', - 'std::mem', - 'std::num', - 'std::os', - 'std::panic::{self, AssertUnwindSafe}', - 'std::ptr', - 'std::str', - 'std::rc', - 'std::rc::Rc', - 'std::default::Default', - 'std::ffi::CString', - ], config) + curr = generate_imports(config, curr, callbackDescriptors, mainCallbacks, + dictionaries, enums) # Add the auto-generated comment. curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) @@ -6250,6 +6437,53 @@ class CallbackSetter(CallbackMember): return None +class CGIterableMethodGenerator(CGGeneric): + """ + Creates methods for iterable interfaces. Unwrapping/wrapping + will be taken care of by the usual method generation machinery in + CGMethodCall/CGPerSignatureCall. Functionality is filled in here instead of + using CGCallGenerator. + """ + def __init__(self, descriptor, iterable, methodName): + if methodName == "forEach": + CGGeneric.__init__(self, fill( + """ + if !IsCallable(arg0) { + throw_type_error(cx, "Argument 1 of ${ifaceName}.forEach is not callable."); + return false; + } + rooted!(in(cx) let arg0 = ObjectValue(&*arg0)); + rooted!(in(cx) let mut call_arg1 = UndefinedValue()); + rooted!(in(cx) let mut call_arg2 = UndefinedValue()); + let mut call_args = vec![UndefinedValue(), UndefinedValue(), ObjectValue(&**_obj)]; + rooted!(in(cx) let mut ignoredReturnVal = UndefinedValue()); + for i in 0..(*this).get_iterable_length() { + (*this).get_value_at_index(i).to_jsval(cx, call_arg1.handle_mut()); + (*this).get_key_at_index(i).to_jsval(cx, call_arg2.handle_mut()); + call_args[0] = call_arg1.handle().get(); + call_args[1] = call_arg2.handle().get(); + let call_args = HandleValueArray { length_: 3, elements_: call_args.as_ptr() }; + if !Call(cx, arg1, arg0.handle(), &call_args, + ignoredReturnVal.handle_mut()) { + return false; + } + } + + let result = (); + """, + ifaceName=descriptor.interface.identifier.name)) + return + CGGeneric.__init__(self, fill( + """ + let result = ${iterClass}::new(&*this, + IteratorType::${itrMethod}, + super::${ifaceName}IteratorBinding::Wrap); + """, + iterClass=iteratorNativeType(descriptor, True), + ifaceName=descriptor.interface.identifier.name, + itrMethod=methodName.title())) + + def camel_to_upper_snake(s): return "_".join(m.group(0).upper() for m in re.finditer("[A-Z][a-z]*", s)) @@ -6286,12 +6520,12 @@ class GlobalGenRoots(): pairs = [] for d in config.getDescriptors(hasInterfaceObject=True): binding = toBindingNamespace(d.name) - pairs.append((d.name, binding)) + pairs.append((d.name, binding, binding)) for ctor in d.interface.namedConstructors: - pairs.append((ctor.identifier.name, binding)) + pairs.append((ctor.identifier.name, binding, binding)) pairs.sort(key=operator.itemgetter(0)) mappings = [ - CGGeneric('b"%s" => codegen::Bindings::%s::DefineDOMInterface as unsafe fn(_, _),' % pair) + CGGeneric('b"%s" => codegen::Bindings::%s::%s::DefineDOMInterface as unsafe fn(_, _),' % pair) for pair in pairs ] mapType = "phf::Map<&'static [u8], unsafe fn(*mut JSContext, HandleObject)>" @@ -6340,15 +6574,17 @@ class GlobalGenRoots(): CGRegisterProxyHandlers(config), ], "\n") - return CGImports(code, [], [], [ - 'dom::bindings::codegen', + return CGImports(code, descriptors=[], callbacks=[], dictionaries=[], enums=[], imports=[ + 'dom::bindings::codegen::Bindings', 'dom::bindings::codegen::PrototypeList::Proxies', 'libc', - ], config, ignored_warnings=[]) + ], config=config, ignored_warnings=[]) @staticmethod def InterfaceTypes(config): - descriptors = [d.name for d in config.getDescriptors(register=True, isCallback=False)] + descriptors = [d.name for d in config.getDescriptors(register=True, + isCallback=False, + isIteratorInterface=False)] curr = CGList([CGGeneric("pub use dom::%s::%s;\n" % (name.lower(), name)) for name in descriptors]) curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) return curr @@ -6356,9 +6592,13 @@ class GlobalGenRoots(): @staticmethod def Bindings(config): - descriptors = (set(d.name + "Binding" for d in config.getDescriptors(register=True)) | - set(getModuleFromObject(d) for d in config.callbacks) | - set(getModuleFromObject(d) for d in config.getDictionaries())) + def leafModule(d): + return getModuleFromObject(d).split('::')[-1] + + descriptors = config.getDescriptors(register=True, isIteratorInterface=False) + descriptors = (set(d.name + "Binding" for d in descriptors) | + set(leafModule(d) for d in config.callbacks) | + set(leafModule(d) for d in config.getDictionaries())) curr = CGList([CGGeneric("pub mod %s;\n" % name) for name in sorted(descriptors)]) curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) return curr diff --git a/components/script/dom/bindings/codegen/Configuration.py b/components/script/dom/bindings/codegen/Configuration.py index 4074736a462..c8f92472618 100644 --- a/components/script/dom/bindings/codegen/Configuration.py +++ b/components/script/dom/bindings/codegen/Configuration.py @@ -4,7 +4,7 @@ import os -from WebIDL import IDLExternalInterface, IDLInterface, WebIDLError +from WebIDL import IDLExternalInterface, IDLInterface, IDLWrapperType, WebIDLError class Configuration: @@ -89,6 +89,8 @@ class Configuration: getter = lambda x: x.isGlobal() elif key == 'isExposedConditionally': getter = lambda x: x.interface.isExposedConditionally() + elif key == 'isIteratorInterface': + getter = lambda x: x.interface.isIteratorInterface() else: getter = lambda x: getattr(x, key) curr = filter(lambda x: getter(x) == val, curr) @@ -177,13 +179,26 @@ class Descriptor(DescriptorProvider): # Read the desc, and fill in the relevant defaults. ifaceName = self.interface.identifier.name - typeName = desc.get('nativeType', ifaceName) + nativeTypeDefault = ifaceName + + # For generated iterator interfaces for other iterable interfaces, we + # just use IterableIterator as the native type, templated on the + # nativeType of the iterable interface. That way we can have a + # templated implementation for all the duplicated iterator + # functionality. + if self.interface.isIteratorInterface(): + itrName = self.interface.iterableInterface.identifier.name + itrDesc = self.getDescriptor(itrName) + nativeTypeDefault = iteratorNativeType(itrDesc) + + typeName = desc.get('nativeType', nativeTypeDefault) # Callback types do not use JS smart pointers, so we should not use the # built-in rooting mechanisms for them. if self.interface.isCallback(): self.needsRooting = False - ty = "%sBinding::%s" % (ifaceName, ifaceName) + ty = 'dom::bindings::codegen::Bindings::%sBinding::%s' % (ifaceName, ifaceName) + pathDefault = ty self.returnType = "Rc<%s>" % ty self.argumentType = "???" self.nativeType = ty @@ -192,10 +207,15 @@ class Descriptor(DescriptorProvider): self.returnType = "Root<%s>" % typeName self.argumentType = "&%s" % typeName self.nativeType = "*const %s" % typeName + if self.interface.isIteratorInterface(): + pathDefault = 'dom::bindings::iterable::IterableIterator' + else: + pathDefault = 'dom::types::%s' % typeName self.concreteType = typeName self.register = desc.get('register', True) - self.path = desc.get('path', 'dom::types::%s' % typeName) + self.path = desc.get('path', pathDefault) + self.bindingPath = 'dom::bindings::codegen::Bindings::%s' % ('::'.join([ifaceName + 'Binding'] * 2)) self.outerObjectHook = desc.get('outerObjectHook', 'None') self.proxy = False self.weakReferenceable = desc.get('weakReferenceable', False) @@ -377,7 +397,8 @@ class Descriptor(DescriptorProvider): # Some utility methods def getModuleFromObject(object): - return os.path.basename(object.location.filename()).split('.webidl')[0] + 'Binding' + return ('dom::bindings::codegen::Bindings::' + + os.path.basename(object.location.filename()).split('.webidl')[0] + 'Binding') def getTypesFromDescriptor(descriptor): @@ -404,6 +425,8 @@ def getTypesFromDictionary(dictionary): """ Get all member types for this dictionary """ + if isinstance(dictionary, IDLWrapperType): + dictionary = dictionary.inner types = [] curDict = dictionary while curDict: @@ -421,3 +444,10 @@ def getTypesFromCallback(callback): types = [sig[0]] # Return type types.extend(arg.type for arg in sig[1]) # Arguments return types + + +def iteratorNativeType(descriptor, infer=False): + assert descriptor.interface.isIterable() + iterableDecl = descriptor.interface.maplikeOrSetlikeOrIterable + assert iterableDecl.isPairIterator() + return "IterableIterator%s" % ("" if infer else '<%s>' % descriptor.interface.identifier.name) diff --git a/components/script/dom/bindings/codegen/parser/WebIDL.py b/components/script/dom/bindings/codegen/parser/WebIDL.py index 54d510781a1..f3195050908 100644 --- a/components/script/dom/bindings/codegen/parser/WebIDL.py +++ b/components/script/dom/bindings/codegen/parser/WebIDL.py @@ -2170,7 +2170,7 @@ class IDLUnresolvedType(IDLType): return typedefType.complete(scope) elif obj.isCallback() and not obj.isInterface(): assert self.name.name == obj.identifier.name - return IDLCallbackType(self.location, obj) + return IDLCallbackType(obj.location, obj) if self._promiseInnerType and not self._promiseInnerType.isComplete(): self._promiseInnerType = self._promiseInnerType.complete(scope) @@ -6534,7 +6534,7 @@ class Parser(Tokenizer): type = IDLTypedefType(self.getLocation(p, 1), obj.innerType, obj.identifier.name) elif obj.isCallback() and not obj.isInterface(): - type = IDLCallbackType(self.getLocation(p, 1), obj) + type = IDLCallbackType(obj.location, obj) else: type = IDLWrapperType(self.getLocation(p, 1), p[1]) p[0] = self.handleModifiers(type, p[2]) diff --git a/components/script/dom/bindings/codegen/parser/callback-location.patch b/components/script/dom/bindings/codegen/parser/callback-location.patch new file mode 100644 index 00000000000..ab6b0ae9b9b --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/callback-location.patch @@ -0,0 +1,22 @@ +diff --git a/components/script/dom/bindings/codegen/parser/WebIDL.py b/components/script/dom/bindings/codegen/parser/WebIDL.py +index da32340..81c52b7 100644 +--- a/components/script/dom/bindings/codegen/parser/WebIDL.py ++++ b/components/script/dom/bindings/codegen/parser/WebIDL.py +@@ -2170,7 +2170,7 @@ class IDLUnresolvedType(IDLType): + return typedefType.complete(scope) + elif obj.isCallback() and not obj.isInterface(): + assert self.name.name == obj.identifier.name +- return IDLCallbackType(self.location, obj) ++ return IDLCallbackType(obj.location, obj) + + if self._promiseInnerType and not self._promiseInnerType.isComplete(): + self._promiseInnerType = self._promiseInnerType.complete(scope) +@@ -6521,7 +6521,7 @@ class Parser(Tokenizer): + type = IDLTypedefType(self.getLocation(p, 1), obj.innerType, + obj.identifier.name) + elif obj.isCallback() and not obj.isInterface(): +- type = IDLCallbackType(self.getLocation(p, 1), obj) ++ type = IDLCallbackType(obj.location, obj) + else: + type = IDLWrapperType(self.getLocation(p, 1), p[1]) + p[0] = self.handleModifiers(type, p[2])
\ No newline at end of file diff --git a/components/script/dom/bindings/codegen/parser/update.sh b/components/script/dom/bindings/codegen/parser/update.sh index 76a99d9cecb..25aeefc5830 100755 --- a/components/script/dom/bindings/codegen/parser/update.sh +++ b/components/script/dom/bindings/codegen/parser/update.sh @@ -2,6 +2,7 @@ wget https://hg.mozilla.org/mozilla-central/raw-file/tip/dom/bindings/parser/Web patch < abstract.patch patch < debug.patch patch < pref-main-thread.patch +patch < callback-location.patch wget https://hg.mozilla.org/mozilla-central/archive/tip.tar.gz/dom/bindings/parser/tests/ -O tests.tar.gz rm -r tests diff --git a/components/script/dom/bindings/iterable.rs b/components/script/dom/bindings/iterable.rs new file mode 100644 index 00000000000..20eb84bffde --- /dev/null +++ b/components/script/dom/bindings/iterable.rs @@ -0,0 +1,161 @@ +/* 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/. */ + +#![allow(unsafe_code)] + +//! Implementation of `iterable<...>` and `iterable<..., ...>` WebIDL declarations. + +use dom::bindings::codegen::Bindings::IterableIteratorBinding::IterableKeyAndValueResult; +use dom::bindings::codegen::Bindings::IterableIteratorBinding::IterableKeyOrValueResult; +use dom::bindings::error::Fallible; +use dom::bindings::global::GlobalRef; +use dom::bindings::js::{JS, Root}; +use dom::bindings::reflector::{Reflector, Reflectable, reflect_dom_object}; +use dom::bindings::trace::JSTraceable; +use js::conversions::ToJSValConvertible; +use js::jsapi::{JSContext, JSObject, MutableHandleValue, MutableHandleObject, HandleValue}; +use js::jsval::UndefinedValue; +use std::cell::Cell; +use std::ptr; + +/// The values that an iterator will iterate over. +#[derive(JSTraceable, HeapSizeOf)] +pub enum IteratorType { + /// The keys of the iterable object. + Keys, + /// The values of the iterable object. + Values, + /// The keys and values of the iterable object combined. + Entries, +} + +/// A DOM object that can be iterated over using a pair value iterator. +pub trait Iterable { + /// The type of the key of the iterator pair. + type Key: ToJSValConvertible; + /// The type of the value of the iterator pair. + type Value: ToJSValConvertible; + /// Return the number of entries that can be iterated over. + fn get_iterable_length(&self) -> u32; + /// Return the value at the provided index. + fn get_value_at_index(&self, index: u32) -> Self::Value; + /// Return the key at the provided index. + fn get_key_at_index(&self, index: u32) -> Self::Key; +} + +/// An iterator over the iterable entries of a given DOM interface. +//FIXME: #12811 prevents dom_struct with type parameters +//#[dom_struct] +#[must_root] +#[privatize] +#[derive(JSTraceable)] +#[derive(HeapSizeOf)] +pub struct IterableIterator<T: Reflectable + JSTraceable + Iterable> { + reflector: Reflector, + iterable: JS<T>, + type_: IteratorType, + index: Cell<u32>, +} + +impl<T: Reflectable + JSTraceable + Iterable> Reflectable for IterableIterator<T> { + fn reflector<'a>(&'a self) -> &'a Reflector { + &self.reflector + } + fn init_reflector(&mut self, obj: *mut JSObject) { + self.reflector.set_jsobject(obj); + } +} + +impl<T: Reflectable + JSTraceable + Iterable> ToJSValConvertible for IterableIterator<T> { + #[allow(unsafe_code)] + unsafe fn to_jsval(&self, + cx: *mut JSContext, + rval: MutableHandleValue) { + let object = Reflectable::reflector(self).get_jsobject(); + object.to_jsval(cx, rval) + } +} + +impl<T: Reflectable + JSTraceable + Iterable> IterableIterator<T> { + /// Create a new iterator instance for the provided iterable DOM interface. + pub fn new(iterable: &T, + type_: IteratorType, + wrap: fn(*mut JSContext, GlobalRef, Box<IterableIterator<T>>) + -> Root<Self>) -> Root<Self> { + let iterator = box IterableIterator { + reflector: Reflector::new(), + type_: type_, + iterable: JS::from_ref(iterable), + index: Cell::new(0), + }; + let global = iterable.global(); + reflect_dom_object(iterator, global.r(), wrap) + } + + /// Return the next value from the iterable object. + #[allow(non_snake_case)] + pub fn Next(&self, cx: *mut JSContext) -> Fallible<*mut JSObject> { + let index = self.index.get(); + rooted!(in(cx) let mut value = UndefinedValue()); + rooted!(in(cx) let mut rval = ptr::null_mut()); + if index >= self.iterable.get_iterable_length() { + return dict_return(cx, rval.handle_mut(), true, value.handle()) + .map(|_| rval.handle().get()); + } + let result = match self.type_ { + IteratorType::Keys => { + unsafe { + self.iterable.get_key_at_index(index).to_jsval(cx, value.handle_mut()); + } + dict_return(cx, rval.handle_mut(), false, value.handle()) + } + IteratorType::Values => { + unsafe { + self.iterable.get_value_at_index(index).to_jsval(cx, value.handle_mut()); + } + dict_return(cx, rval.handle_mut(), false, value.handle()) + } + IteratorType::Entries => { + rooted!(in(cx) let mut key = UndefinedValue()); + unsafe { + self.iterable.get_key_at_index(index).to_jsval(cx, key.handle_mut()); + self.iterable.get_value_at_index(index).to_jsval(cx, value.handle_mut()); + } + key_and_value_return(cx, rval.handle_mut(), key.handle(), value.handle()) + } + }; + self.index.set(index + 1); + result.map(|_| rval.handle().get()) + } +} + +fn dict_return(cx: *mut JSContext, + result: MutableHandleObject, + done: bool, + value: HandleValue) -> Fallible<()> { + let mut dict = unsafe { IterableKeyOrValueResult::empty(cx) }; + dict.done = done; + dict.value = value.get(); + rooted!(in(cx) let mut dict_value = UndefinedValue()); + unsafe { + dict.to_jsval(cx, dict_value.handle_mut()); + } + result.set(dict_value.to_object()); + Ok(()) +} + +fn key_and_value_return(cx: *mut JSContext, + result: MutableHandleObject, + key: HandleValue, + value: HandleValue) -> Fallible<()> { + let mut dict = unsafe { IterableKeyAndValueResult::empty(cx) }; + dict.done = false; + dict.value = Some(vec![key.get(), value.get()]); + rooted!(in(cx) let mut dict_value = UndefinedValue()); + unsafe { + dict.to_jsval(cx, dict_value.handle_mut()); + } + result.set(dict_value.to_object()); + Ok(()) +} diff --git a/components/script/dom/bindings/mod.rs b/components/script/dom/bindings/mod.rs index 19797a473d0..439016ee15b 100644 --- a/components/script/dom/bindings/mod.rs +++ b/components/script/dom/bindings/mod.rs @@ -136,6 +136,7 @@ pub mod global; pub mod guard; pub mod inheritance; pub mod interface; +pub mod iterable; pub mod js; pub mod num; pub mod proxyhandler; diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 432246d814f..07e4d6227be 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -384,6 +384,8 @@ pub mod storageevent; pub mod stylesheet; pub mod stylesheetlist; pub mod testbinding; +pub mod testbindingiterable; +pub mod testbindingpairiterable; pub mod testbindingproxy; pub mod text; pub mod textdecoder; diff --git a/components/script/dom/testbindingiterable.rs b/components/script/dom/testbindingiterable.rs new file mode 100644 index 00000000000..1e462a98531 --- /dev/null +++ b/components/script/dom/testbindingiterable.rs @@ -0,0 +1,43 @@ +/* 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/. */ + +// check-tidy: no specs after this line + +use dom::bindings::cell::DOMRefCell; +use dom::bindings::codegen::Bindings::TestBindingIterableBinding::{self, TestBindingIterableMethods}; +use dom::bindings::error::Fallible; +use dom::bindings::global::GlobalRef; +use dom::bindings::js::Root; +use dom::bindings::reflector::{Reflector, reflect_dom_object}; +use dom::bindings::str::DOMString; + +#[dom_struct] +pub struct TestBindingIterable { + reflector: Reflector, + vals: DOMRefCell<Vec<DOMString>>, +} + +impl TestBindingIterable { + fn new(global: GlobalRef) -> Root<TestBindingIterable> { + reflect_dom_object(box TestBindingIterable { + reflector: Reflector::new(), + vals: DOMRefCell::new(vec![]), + }, global, TestBindingIterableBinding::Wrap) + } + + pub fn Constructor(global: GlobalRef) -> Fallible<Root<TestBindingIterable>> { + Ok(TestBindingIterable::new(global)) + } +} + +impl TestBindingIterableMethods for TestBindingIterable { + fn Add(&self, v: DOMString) { self.vals.borrow_mut().push(v); } + fn Length(&self) -> u32 { self.vals.borrow().len() as u32 } + fn GetItem(&self, n: u32) -> DOMString { self.vals.borrow().get(n as usize).unwrap().clone() } + fn IndexedGetter(&self, n: u32, found: &mut bool) -> DOMString { + let s = self.GetItem(n); + *found = true; + s + } +} diff --git a/components/script/dom/testbindingpairiterable.rs b/components/script/dom/testbindingpairiterable.rs new file mode 100644 index 00000000000..9bceedd4980 --- /dev/null +++ b/components/script/dom/testbindingpairiterable.rs @@ -0,0 +1,54 @@ +/* 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/. */ + +// check-tidy: no specs after this line + +use dom::bindings::cell::DOMRefCell; +use dom::bindings::codegen::Bindings::TestBindingPairIterableBinding; +use dom::bindings::codegen::Bindings::TestBindingPairIterableBinding::TestBindingPairIterableMethods; +use dom::bindings::error::Fallible; +use dom::bindings::global::GlobalRef; +use dom::bindings::iterable::Iterable; +use dom::bindings::js::Root; +use dom::bindings::reflector::{Reflector, reflect_dom_object}; +use dom::bindings::str::DOMString; + +#[dom_struct] +pub struct TestBindingPairIterable { + reflector: Reflector, + map: DOMRefCell<Vec<(DOMString, u32)>>, +} + +impl Iterable for TestBindingPairIterable { + type Key = DOMString; + type Value = u32; + fn get_iterable_length(&self) -> u32 { + self.map.borrow().len() as u32 + } + fn get_value_at_index(&self, index: u32) -> u32 { + self.map.borrow().iter().nth(index as usize).map(|a| &a.1).unwrap().clone() + } + fn get_key_at_index(&self, index: u32) -> DOMString { + self.map.borrow().iter().nth(index as usize).map(|a| &a.0).unwrap().clone() + } +} + +impl TestBindingPairIterable { + fn new(global: GlobalRef) -> Root<TestBindingPairIterable> { + reflect_dom_object(box TestBindingPairIterable { + reflector: Reflector::new(), + map: DOMRefCell::new(vec![]), + }, global, TestBindingPairIterableBinding::TestBindingPairIterableWrap) + } + + pub fn Constructor(global: GlobalRef) -> Fallible<Root<TestBindingPairIterable>> { + Ok(TestBindingPairIterable::new(global)) + } +} + +impl TestBindingPairIterableMethods for TestBindingPairIterable { + fn Add(&self, key: DOMString, value: u32) { + self.map.borrow_mut().push((key, value)); + } +} diff --git a/components/script/dom/webidls/IterableIterator.webidl b/components/script/dom/webidls/IterableIterator.webidl new file mode 100644 index 00000000000..d975aa5645d --- /dev/null +++ b/components/script/dom/webidls/IterableIterator.webidl @@ -0,0 +1,16 @@ +/* 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/. */ + +// This interface is entirely internal to Servo, and should not be accessible to +// web pages. + +dictionary IterableKeyOrValueResult { + any value; + boolean done = false; +}; + +dictionary IterableKeyAndValueResult { + sequence<any> value; + boolean done = false; +}; diff --git a/components/script/dom/webidls/TestBindingIterable.webidl b/components/script/dom/webidls/TestBindingIterable.webidl new file mode 100644 index 00000000000..c9e61074eed --- /dev/null +++ b/components/script/dom/webidls/TestBindingIterable.webidl @@ -0,0 +1,14 @@ +/* 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/. */ + +// This interface is entirely internal to Servo, and should not be accessible to +// web pages. + +[Pref="dom.testbinding.enabled", Exposed=(Window,Worker), Constructor] +interface TestBindingIterable { + void add(DOMString arg); + readonly attribute unsigned long length; + getter DOMString getItem(unsigned long index); + iterable<DOMString>; +}; diff --git a/components/script/dom/webidls/TestBindingPairIterable.webidl b/components/script/dom/webidls/TestBindingPairIterable.webidl new file mode 100644 index 00000000000..a7bc66c1be3 --- /dev/null +++ b/components/script/dom/webidls/TestBindingPairIterable.webidl @@ -0,0 +1,12 @@ +/* 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/. */ + +// This interface is entirely internal to Servo, and should not be accessible to +// web pages. + +[Pref="dom.testbinding.enabled", Exposed=(Window,Worker), Constructor] +interface TestBindingPairIterable { + void add(DOMString key, unsigned long value); + iterable<DOMString, unsigned long>; +}; diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 322e742d7a1..4f9e37cfa05 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -6858,6 +6858,12 @@ "url": "/_mozilla/mozilla/interfaces.worker" } ], + "mozilla/iterable.html": [ + { + "path": "mozilla/iterable.html", + "url": "/_mozilla/mozilla/iterable.html" + } + ], "mozilla/lenient_this.html": [ { "path": "mozilla/lenient_this.html", diff --git a/tests/wpt/mozilla/meta/mozilla/iterable.html.ini b/tests/wpt/mozilla/meta/mozilla/iterable.html.ini new file mode 100644 index 00000000000..2316a8b3984 --- /dev/null +++ b/tests/wpt/mozilla/meta/mozilla/iterable.html.ini @@ -0,0 +1,3 @@ +[iterable.html] + type: testharness + prefs: [dom.testbinding.enabled:true] diff --git a/tests/wpt/mozilla/tests/mozilla/iterable.html b/tests/wpt/mozilla/tests/mozilla/iterable.html new file mode 100644 index 00000000000..d4fe1259b01 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/iterable.html @@ -0,0 +1,95 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Value and pair iterable bindings</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + function collect(iter) { + var collection = []; + for (element of iter) { + collection.push(element); + } + return collection; + } + + test(function() { + var t = new TestBindingIterable(); + var empty = true; + t.forEach(function() { empty = false; }); + assert_true(empty); + }, "Empty value iterator"); + + test(function() { + var t = new TestBindingIterable(); + function is_iterator(o) { + return o[Symbol.iterator]() === o; + } + assert_true(is_iterator(t.keys())); + assert_true(is_iterator(t.values())); + assert_true(is_iterator(t.entries())); + }, "Iterable iterators are iterators"); + + test(function() { + var t = new TestBindingIterable(); + t.add("first"); + t.add("second"); + t.add("third"); + assert_array_equals(collect(t.keys()), [0, 1, 2]); + assert_array_equals(collect(t.values()), ["first", "second", "third"]); + var expected = [[0, "first"], [1, "second"], [2, "third"]]; + var i = 0; + for (entry of t.entries()) { + assert_array_equals(entry, expected[i++]); + } + + t.add("fourth"); + assert_array_equals(collect(t.keys()), [0, 1, 2, 3]); + assert_array_equals(collect(t.values()), ["first", "second", "third", "fourth"]); + var expected = [[0, "first"], [1, "second"], [2, "third"], [3, "fourth"]]; + var i = 0; + for (entry of t.entries()) { + assert_array_equals(entry, expected[i++]); + } + }, "Iterators iterate over values"); + + test(function() { + var t = new TestBindingPairIterable(); + var empty = true; + t.forEach(function() { empty = false; }); + assert_true(empty); + }, "Empty pair iterator"); + + test(function() { + var t = new TestBindingPairIterable(); + function is_iterator(o) { + return o[Symbol.iterator]() === o; + } + assert_true(is_iterator(t.keys())); + assert_true(is_iterator(t.values())); + assert_true(is_iterator(t.entries())); + }, "Pair iterable iterators are iterators"); + + test(function() { + var t = new TestBindingPairIterable(); + t.add("first", 0); + t.add("second", 1); + t.add("third", 2); + assert_array_equals(collect(t.keys()), ["first", "second", "third"]); + assert_array_equals(collect(t.values()), [0, 1, 2]); + var expected = [["first", 0], ["second", 1], ["third", 2]]; + var i = 0; + for (entry of t.entries()) { + assert_array_equals(entry, expected[i++]); + } + + t.add("fourth", 3); + assert_array_equals(collect(t.keys()), ["first", "second", "third", "fourth"]); + assert_array_equals(collect(t.values()), [0, 1, 2, 3]); + var expected = [["first", 0], ["second", 1], ["third", 2], ["fourth", 3]]; + var i = 0; + for (entry of t.entries()) { + assert_array_equals(entry, expected[i++]); + } + }, "Pair iterators iterate over key/value pairs"); + +</script> |