From 221bc846935a321747fff30689218de7543c964b Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Thu, 11 Aug 2016 11:21:47 -0400 Subject: [PATCH 1/5] Support multiple WebIDL interfaces being generated in the same output binding file. Each interface gets its own module named ${Interface}Binding. Structs, enums, and callbacks continue to use the root module of the binding file. If there is only one interface in the file, we generate reexports for several public APIs and types so that existing DOM implementations don't need any modifications. When multiple interfaces exist, the reexported names get the interface name prepended (eg. FooWrap instead of Wrap). As part of this work, stop glob-importing all DOM types in every generated binding and start generating more targeted lists of relevant types based on the methods, members, etc. of WebIDL types that are in use. --- .../dom/bindings/codegen/CodegenRust.py | 333 +++++++++++------- .../dom/bindings/codegen/Configuration.py | 14 +- .../dom/bindings/codegen/parser/WebIDL.py | 4 +- .../codegen/parser/callback-location.patch | 22 ++ .../dom/bindings/codegen/parser/update.sh | 1 + 5 files changed, 239 insertions(+), 135 deletions(-) create mode 100644 components/script/dom/bindings/codegen/parser/callback-location.patch diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index 6b23e485e25..56a20677691 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 ( @@ -942,7 +943,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()) @@ -1738,7 +1739,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 +1753,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 +1777,27 @@ 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] members = d.interface.members + d.interface.namedConstructors constructor = d.interface.ctor() @@ -1796,19 +1813,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') @@ -2111,7 +2148,7 @@ 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"), [], [], [], [], imports, config, ignored_warnings=[]) class Argument(): @@ -2709,7 +2746,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 +2764,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([ @@ -5067,6 +5104,7 @@ class CGInterfaceTrait(CGThing): post="}") else: self.cgRoot = CGGeneric("") + self.empty = not methods def define(self): return self.cgRoot.define() @@ -5082,18 +5120,135 @@ 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}', + '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', + '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::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: @@ -5155,9 +5310,11 @@ class CGDescriptor(CGThing): cgThings.append(CGNamespace.build([descriptor.name + "Constants"], CGConstant(constMembers), public=True)) + reexports.append(descriptor.name + 'Constants') if descriptor.interface.hasInterfaceObject(): cgThings.append(CGDefineDOMInterfaceMethod(descriptor)) + reexports.append('DefineDOMInterface') cgThings.append(CGConstructorEnabled(descriptor)) if descriptor.proxy: @@ -5193,6 +5350,7 @@ class CGDescriptor(CGThing): pass cgThings.append(CGWrapMethod(descriptor)) + reexports.append('Wrap') haveUnscopables = False if not descriptor.interface.isCallback(): @@ -5205,7 +5363,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)) @@ -5213,11 +5376,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() @@ -5467,8 +5632,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") @@ -5541,7 +5706,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), @@ -5552,102 +5717,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) @@ -6285,12 +6356,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)>" @@ -6339,8 +6410,8 @@ class GlobalGenRoots(): CGRegisterProxyHandlers(config), ], "\n") - return CGImports(code, [], [], [ - 'dom::bindings::codegen', + return CGImports(code, [], [], [], [], [ + 'dom::bindings::codegen::Bindings', 'dom::bindings::codegen::PrototypeList::Proxies', 'libc', ], config, ignored_warnings=[]) @@ -6355,9 +6426,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) + 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..fd005bc6e05 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: @@ -183,7 +183,8 @@ class Descriptor(DescriptorProvider): # 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 +193,12 @@ class Descriptor(DescriptorProvider): self.returnType = "Root<%s>" % typeName self.argumentType = "&%s" % typeName self.nativeType = "*const %s" % typeName + 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 +380,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 +408,8 @@ def getTypesFromDictionary(dictionary): """ Get all member types for this dictionary """ + if isinstance(dictionary, IDLWrapperType): + dictionary = dictionary.inner types = [] curDict = dictionary while curDict: 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 From 34bb937aeea9a48b1b4d8dc7c49f375203be4e7e Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Thu, 28 Jul 2016 18:41:24 -0400 Subject: [PATCH 2/5] Support value iterators in WebIDL interfaces. --- .../dom/bindings/codegen/CodegenRust.py | 40 ++++++++++++++ components/script/dom/mod.rs | 1 + components/script/dom/testbindingiterable.rs | 43 +++++++++++++++ .../dom/webidls/TestBindingIterable.webidl | 14 +++++ tests/wpt/mozilla/meta/MANIFEST.json | 6 +++ .../mozilla/meta/mozilla/iterable.html.ini | 3 ++ tests/wpt/mozilla/tests/mozilla/iterable.html | 54 +++++++++++++++++++ 7 files changed, 161 insertions(+) create mode 100644 components/script/dom/testbindingiterable.rs create mode 100644 components/script/dom/webidls/TestBindingIterable.webidl create mode 100644 tests/wpt/mozilla/meta/mozilla/iterable.html.ini create mode 100644 tests/wpt/mozilla/tests/mozilla/iterable.html diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index 56a20677691..bd41a214740 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -1519,6 +1519,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'] diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 432246d814f..1cf74390257 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -384,6 +384,7 @@ pub mod storageevent; pub mod stylesheet; pub mod stylesheetlist; pub mod testbinding; +pub mod testbindingiterable; 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>, +} + +impl TestBindingIterable { + fn new(global: GlobalRef) -> Root { + reflect_dom_object(box TestBindingIterable { + reflector: Reflector::new(), + vals: DOMRefCell::new(vec![]), + }, global, TestBindingIterableBinding::Wrap) + } + + pub fn Constructor(global: GlobalRef) -> Fallible> { + 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/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; +}; 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..12ab75db54c --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/iterable.html @@ -0,0 +1,54 @@ + + +Value and pair iterable bindings + + + From 812a761abf9722985692684454e6418d737d7add Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Thu, 28 Jul 2016 18:41:30 -0400 Subject: [PATCH 3/5] Implement pair iterators in WebIDL interfaces. --- .../dom/bindings/codegen/CodegenRust.py | 89 ++++++++-- .../dom/bindings/codegen/Configuration.py | 28 ++- components/script/dom/bindings/iterable.rs | 161 ++++++++++++++++++ components/script/dom/bindings/mod.rs | 1 + components/script/dom/mod.rs | 1 + .../script/dom/testbindingpairiterable.rs | 54 ++++++ .../dom/webidls/IterableIterator.webidl | 16 ++ .../webidls/TestBindingPairIterable.webidl | 12 ++ tests/wpt/mozilla/tests/mozilla/iterable.html | 41 +++++ 9 files changed, 390 insertions(+), 13 deletions(-) create mode 100644 components/script/dom/bindings/iterable.rs create mode 100644 components/script/dom/testbindingpairiterable.rs create mode 100644 components/script/dom/webidls/IterableIterator.webidl create mode 100644 components/script/dom/webidls/TestBindingPairIterable.webidl diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index bd41a214740..eb14901c777 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -30,6 +30,7 @@ from Configuration import ( getTypesFromCallback, getTypesFromDescriptor, getTypesFromDictionary, + iteratorNativeType ) AUTOGENERATED_WARNING_COMMENT = \ @@ -720,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) @@ -1839,6 +1842,9 @@ class CGImports(CGWrapper): 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() if constructor: @@ -1945,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: @@ -2519,7 +2525,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 @@ -3043,11 +3049,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): @@ -5084,6 +5100,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) @@ -5213,6 +5230,7 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries '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}', @@ -5352,7 +5370,7 @@ class CGDescriptor(CGThing): 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)) @@ -6360,6 +6378,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)) @@ -6458,7 +6523,9 @@ class GlobalGenRoots(): @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 @@ -6469,7 +6536,7 @@ class GlobalGenRoots(): def leafModule(d): return getModuleFromObject(d).split('::')[-1] - descriptors = config.getDescriptors(register=True) + 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())) diff --git a/components/script/dom/bindings/codegen/Configuration.py b/components/script/dom/bindings/codegen/Configuration.py index fd005bc6e05..c8f92472618 100644 --- a/components/script/dom/bindings/codegen/Configuration.py +++ b/components/script/dom/bindings/codegen/Configuration.py @@ -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,7 +179,19 @@ 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. @@ -193,7 +207,10 @@ class Descriptor(DescriptorProvider): self.returnType = "Root<%s>" % typeName self.argumentType = "&%s" % typeName self.nativeType = "*const %s" % typeName - pathDefault = 'dom::types::%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) @@ -427,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/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 { + reflector: Reflector, + iterable: JS, + type_: IteratorType, + index: Cell, +} + +impl Reflectable for IterableIterator { + fn reflector<'a>(&'a self) -> &'a Reflector { + &self.reflector + } + fn init_reflector(&mut self, obj: *mut JSObject) { + self.reflector.set_jsobject(obj); + } +} + +impl ToJSValConvertible for IterableIterator { + #[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 IterableIterator { + /// Create a new iterator instance for the provided iterable DOM interface. + pub fn new(iterable: &T, + type_: IteratorType, + wrap: fn(*mut JSContext, GlobalRef, Box>) + -> Root) -> Root { + 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 1cf74390257..07e4d6227be 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -385,6 +385,7 @@ 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/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>, +} + +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 { + reflect_dom_object(box TestBindingPairIterable { + reflector: Reflector::new(), + map: DOMRefCell::new(vec![]), + }, global, TestBindingPairIterableBinding::TestBindingPairIterableWrap) + } + + pub fn Constructor(global: GlobalRef) -> Fallible> { + 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 value; + boolean done = false; +}; 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; +}; diff --git a/tests/wpt/mozilla/tests/mozilla/iterable.html b/tests/wpt/mozilla/tests/mozilla/iterable.html index 12ab75db54c..d4fe1259b01 100644 --- a/tests/wpt/mozilla/tests/mozilla/iterable.html +++ b/tests/wpt/mozilla/tests/mozilla/iterable.html @@ -51,4 +51,45 @@ 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"); + From 6492c090f144cb99b379e79d72e10a058bbbec34 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Thu, 11 Aug 2016 11:41:49 -0400 Subject: [PATCH 4/5] Set up the iterator prototype as the prototype of iterator interfaces, and alias forEach to the entries method. --- .../dom/bindings/codegen/CodegenRust.py | 59 +++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index eb14901c777..745d51726e3 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -2632,6 +2632,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: @@ -2711,6 +2713,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) @@ -5193,7 +5244,7 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries '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::{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}', @@ -5202,9 +5253,9 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries '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_NewObjectWithoutMetadata, JS_SetProperty, JS_DefinePropertyById2}', 'js::jsapi::{JS_SplicePrototype, JS_SetReservedSlot, JSAutoCompartment}', - 'js::jsapi::{JSContext, JSClass, JSFreeOp, JSFunctionSpec}', + '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}', @@ -5216,7 +5267,7 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries '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::{RUST_JSID_IS_STRING, int_to_jsid, RUST_SYMBOL_TO_JSID}', 'js::glue::AppendToAutoIdVector', 'js::rust::{GCMethods, define_methods, define_properties}', 'dom', From 86a0c45f87f28ef44f996e943aaab1e6c0206443 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Tue, 23 Aug 2016 13:09:54 -0400 Subject: [PATCH 5/5] Use named arguments for CGImports. --- .../script/dom/bindings/codegen/CodegenRust.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index 745d51726e3..80b5fe4cf0c 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -2194,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(): @@ -6566,11 +6573,11 @@ class GlobalGenRoots(): CGRegisterProxyHandlers(config), ], "\n") - return CGImports(code, [], [], [], [], [ + 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):