From 40ad677ae7d7e1e2de9c804565311eb9ac3b235d Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Wed, 17 Feb 2016 22:58:04 -0500 Subject: [PATCH] Bug 1216751 part 4. Implement forEach for iterable interfaces. r=qdot --- dom/bindings/Codegen.py | 36 ++++++++++++++++- dom/bindings/parser/WebIDL.py | 27 ++++++++----- .../test_interface_maplikesetlikeiterable.py | 6 +-- dom/bindings/test/test_iterable.html | 39 ++++++++++++++++++- 4 files changed, 93 insertions(+), 15 deletions(-) diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 896dc0a2a31..feb1ae1e285 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -2289,7 +2289,7 @@ class MethodDefiner(PropertyDefiner): maplikeOrSetlikeOrIterable and maplikeOrSetlikeOrIterable.isIterable() and maplikeOrSetlikeOrIterable.isValueIterator()): - # Add our keys/values/entries + # Add our keys/values/entries/forEach self.regular.append({ "name": "keys", "methodInfo": False, @@ -2317,6 +2317,15 @@ class MethodDefiner(PropertyDefiner): "condition": PropertyDefiner.getControllingCondition(m, descriptor) }) + self.regular.append({ + "name": "forEach", + "methodInfo": False, + "selfHostedName": "ArrayForEach", + "length": 0, + "flags": "JSPROP_ENUMERATE", + "condition": PropertyDefiner.getControllingCondition(m, + descriptor) + }) if not static: stringifier = descriptor.operations['Stringifier'] @@ -15811,6 +15820,31 @@ class CGIterableMethodGenerator(CGGeneric): using CGCallGenerator. """ def __init__(self, descriptor, iterable, methodName): + if methodName == "forEach": + CGGeneric.__init__(self, fill( + """ + if (!JS::IsCallable(arg0)) { + ThrowErrorMessage(cx, MSG_NOT_CALLABLE, "Argument 1 of ${ifaceName}.forEach"); + return false; + } + JS::AutoValueArray<3> callArgs(cx); + callArgs[2].setObject(*obj); + JS::Rooted ignoredReturnVal(cx); + for (size_t i = 0; i < self->GetIterableLength(); ++i) { + if (!ToJSValue(cx, self->GetValueAtIndex(i), callArgs[0])) { + return false; + } + if (!ToJSValue(cx, self->GetKeyAtIndex(i), callArgs[1])) { + return false; + } + if (!JS::Call(cx, arg1, arg0, JS::HandleValueArray(callArgs), + &ignoredReturnVal)) { + return false; + } + } + """, + ifaceName=descriptor.interface.identifier.name)) + return CGGeneric.__init__(self, fill( """ typedef ${iterClass} itrType; diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py index 691e3a1c85f..8d38bc5770e 100644 --- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -3617,6 +3617,17 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember): deps.add(self.valueType) return deps + def getForEachArguments(self): + return [IDLArgument(self.location, + IDLUnresolvedIdentifier(BuiltinLocation(""), + "callback"), + BuiltinTypes[IDLBuiltinType.Types.object]), + IDLArgument(self.location, + IDLUnresolvedIdentifier(BuiltinLocation(""), + "thisArg"), + BuiltinTypes[IDLBuiltinType.Types.any], + optional=True)] + # Iterable adds ES6 iterator style functions and traits # (keys/values/entries/@@iterator) to an interface. class IDLIterable(IDLMaplikeOrSetlikeOrIterableBase): @@ -3652,6 +3663,11 @@ class IDLIterable(IDLMaplikeOrSetlikeOrIterableBase): self.addMethod("values", members, False, self.iteratorType, affectsNothing=True, newObject=True) + # void forEach(callback(valueType, keyType), optional any thisArg) + self.addMethod("forEach", members, False, + BuiltinTypes[IDLBuiltinType.Types.void], + self.getForEachArguments()) + def isValueIterator(self): return not self.isPairIterator() @@ -3703,17 +3719,8 @@ class IDLMaplikeOrSetlike(IDLMaplikeOrSetlikeOrIterableBase): affectsNothing=True, isIteratorAlias=self.isSetlike()) # void forEach(callback(valueType, keyType), thisVal) - foreachArguments = [IDLArgument(self.location, - IDLUnresolvedIdentifier(BuiltinLocation(""), - "callback"), - BuiltinTypes[IDLBuiltinType.Types.object]), - IDLArgument(self.location, - IDLUnresolvedIdentifier(BuiltinLocation(""), - "thisArg"), - BuiltinTypes[IDLBuiltinType.Types.any], - optional=True)] self.addMethod("forEach", members, False, BuiltinTypes[IDLBuiltinType.Types.void], - foreachArguments) + self.getForEachArguments()) def getKeyArg(): return IDLArgument(self.location, diff --git a/dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py b/dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py index e3b38c41a92..05bb4f7ccbc 100644 --- a/dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py +++ b/dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py @@ -45,8 +45,8 @@ def WebIDLTest(parser, harness): prefix + " - Interface failed but not as a WebIDLError exception: %s" % e) iterableMembers = [(x, WebIDL.IDLMethod) for x in ["entries", "keys", - "values"]] - setROMembers = ([(x, WebIDL.IDLMethod) for x in ["has", "forEach"]] + + "values", "forEach"]] + setROMembers = ([(x, WebIDL.IDLMethod) for x in ["has"]] + [("__setlike", WebIDL.IDLMaplikeOrSetlike)] + iterableMembers) setROMembers.extend([("size", WebIDL.IDLAttribute)]) @@ -62,7 +62,7 @@ def WebIDLTest(parser, harness): "__clear", "__delete"]] + setRWMembers) - mapROMembers = ([(x, WebIDL.IDLMethod) for x in ["get", "has", "forEach"]] + + mapROMembers = ([(x, WebIDL.IDLMethod) for x in ["get", "has"]] + [("__maplike", WebIDL.IDLMaplikeOrSetlike)] + iterableMembers) mapROMembers.extend([("size", WebIDL.IDLAttribute)]) diff --git a/dom/bindings/test/test_iterable.html b/dom/bindings/test/test_iterable.html index 9cdb4ccd366..96e3f5468b0 100644 --- a/dom/bindings/test/test_iterable.html +++ b/dom/bindings/test/test_iterable.html @@ -14,7 +14,8 @@ base_properties = [["entries", "function", 0], ["keys", "function", 0], - ["values", "function", 0]] + ["values", "function", 0], + ["forEach", "function", 1]] var testExistence = function testExistence(prefix, obj, properties) { for (var [name, type, args] of properties) { // Properties are somewhere up the proto chain, hasOwnProperty won't work @@ -58,6 +59,8 @@ "IterableSingle: Should be using %ArrayIterator% for 'entries'"); is(itr.values, itr[Symbol.iterator], "IterableSingle: Should be using @@iterator for 'values'"); + is(itr.forEach, Array.prototype.forEach, + "IterableSingle: Should be using %ArrayIterator% for 'forEach'"); var keys = [...itr.keys()]; var values = [...itr.values()]; var entries = [...itr.entries()]; @@ -81,6 +84,23 @@ is(entry.value[1], entries[i][1], "IterableSingle: Entry iterator value 1 should match destructuring " + i); } + + var callsToForEachCallback = 0; + var thisArg = {}; + itr.forEach(function(value, index, obj) { + is(index, callsToForEachCallback, + `IterableSingle: Should have the right index at ${callsToForEachCallback} calls to forEach callback`); + is(value, values[index], + `IterableSingle: Should have the right value at ${callsToForEachCallback} calls to forEach callback`); + is(this, thisArg, + "IterableSingle: Should have the right this value for forEach callback"); + is(obj, itr, + "IterableSingle: Should have the right third arg for forEach callback"); + ++callsToForEachCallback; + }, thisArg); + is(callsToForEachCallback, 3, + "IterableSingle: Should have right total number of calls to forEach callback"); + var key = key_itr.next(); var value = value_itr.next(); var entry = entries_itr.next(); @@ -124,6 +144,23 @@ is(entry.value[1], entries[i][1], "IterableDouble: Entry iterator value 1 should match destructuring " + i); } + + callsToForEachCallback = 0; + thisArg = {}; + itr.forEach(function(value, key, obj) { + is(key, keys[callsToForEachCallback], + `IterableDouble: Should have the right key at ${callsToForEachCallback} calls to forEach callback`); + is(value, values[callsToForEachCallback], + `IterableDouble: Should have the right value at ${callsToForEachCallback} calls to forEach callback`); + is(this, thisArg, + "IterableDouble: Should have the right this value for forEach callback"); + is(obj, itr, + "IterableSingle: Should have the right third arg for forEach callback"); + ++callsToForEachCallback; + }, thisArg); + is(callsToForEachCallback, 3, + "IterableDouble: Should have right total number of calls to forEach callback"); + var key = key_itr.next(); var value = value_itr.next(); var entry = entries_itr.next()