Bug 1123516 - Implement maplike/setlike in WebIDL Codegen; r=bz

This commit is contained in:
Kyle Machulis 2015-05-05 23:42:27 -07:00
parent 907dea7e41
commit c035543458
27 changed files with 1690 additions and 26 deletions

View File

@ -696,6 +696,7 @@
#ifdef MOZ_DEBUG
@RESPATH@/components/TestInterfaceJS.js
@RESPATH@/components/TestInterfaceJS.manifest
@RESPATH@/components/TestInterfaceJSMaplike.js
#endif
@RESPATH@/components/PACGenerator.js

View File

@ -623,6 +623,7 @@
#ifdef MOZ_DEBUG
@RESPATH@/components/TestInterfaceJS.js
@RESPATH@/components/TestInterfaceJS.manifest
@RESPATH@/components/TestInterfaceJSMaplike.js
#endif
@RESPATH@/components/PACGenerator.js

View File

@ -2816,5 +2816,89 @@ SystemGlobalEnumerate(JSContext* cx, JS::Handle<JSObject*> obj)
ResolveSystemBinding(cx, obj, JSID_VOIDHANDLE, &ignored);
}
template<decltype(JS::NewMapObject) Method>
bool
GetMaplikeSetlikeBackingObject(JSContext* aCx, JS::Handle<JSObject*> aObj,
size_t aSlotIndex,
JS::MutableHandle<JSObject*> aBackingObj,
bool* aBackingObjCreated)
{
JS::Rooted<JSObject*> reflector(aCx);
reflector = IsDOMObject(aObj) ? aObj : js::UncheckedUnwrap(aObj,
/* stopAtOuter = */ false);
// Retrieve the backing object from the reserved slot on the maplike/setlike
// object. If it doesn't exist yet, create it.
JS::Rooted<JS::Value> slotValue(aCx);
slotValue = js::GetReservedSlot(reflector, aSlotIndex);
if (slotValue.isUndefined()) {
// Since backing object access can happen in non-originating compartments,
// make sure to create the backing object in reflector compartment.
{
JSAutoCompartment ac(aCx, reflector);
JS::Rooted<JSObject*> newBackingObj(aCx);
newBackingObj.set(Method(aCx));
if (NS_WARN_IF(!newBackingObj)) {
return false;
}
js::SetReservedSlot(reflector, aSlotIndex, JS::ObjectValue(*newBackingObj));
}
slotValue = js::GetReservedSlot(reflector, aSlotIndex);
*aBackingObjCreated = true;
} else {
*aBackingObjCreated = false;
}
if (!MaybeWrapNonDOMObjectValue(aCx, &slotValue)) {
return false;
}
aBackingObj.set(&slotValue.toObject());
return true;
}
bool
GetMaplikeBackingObject(JSContext* aCx, JS::Handle<JSObject*> aObj,
size_t aSlotIndex,
JS::MutableHandle<JSObject*> aBackingObj,
bool* aBackingObjCreated)
{
return GetMaplikeSetlikeBackingObject<JS::NewMapObject>(aCx, aObj, aSlotIndex,
aBackingObj,
aBackingObjCreated);
}
bool
GetSetlikeBackingObject(JSContext* aCx, JS::Handle<JSObject*> aObj,
size_t aSlotIndex,
JS::MutableHandle<JSObject*> aBackingObj,
bool* aBackingObjCreated)
{
return GetMaplikeSetlikeBackingObject<JS::NewSetObject>(aCx, aObj, aSlotIndex,
aBackingObj,
aBackingObjCreated);
}
bool
ForEachHandler(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
{
JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
// Unpack callback and object from slots
JS::Rooted<JS::Value>
callbackFn(aCx, js::GetFunctionNativeReserved(&args.callee(),
FOREACH_CALLBACK_SLOT));
JS::Rooted<JS::Value>
maplikeOrSetlikeObj(aCx,
js::GetFunctionNativeReserved(&args.callee(),
FOREACH_MAPLIKEORSETLIKEOBJ_SLOT));
MOZ_ASSERT(aArgc == 3);
JS::AutoValueVector newArgs(aCx);
// Arguments are passed in as value, key, object. Keep value and key, replace
// object with the maplike/setlike object.
newArgs.append(args.get(0));
newArgs.append(args.get(1));
newArgs.append(maplikeOrSetlikeObj);
JS::Rooted<JS::Value> rval(aCx, JS::UndefinedValue());
// Now actually call the user specified callback
return JS::Call(aCx, args.thisv(), callbackFn, newArgs, &rval);
}
} // namespace dom
} // namespace mozilla

View File

@ -3248,6 +3248,29 @@ bool SystemGlobalResolve(JSContext* cx, JS::Handle<JSObject*> obj,
// thrown.
bool SystemGlobalEnumerate(JSContext* cx, JS::Handle<JSObject*> obj);
// Slot indexes for maplike/setlike forEach functions
#define FOREACH_CALLBACK_SLOT 0
#define FOREACH_MAPLIKEORSETLIKEOBJ_SLOT 1
// Backing function for running .forEach() on maplike/setlike interfaces.
// Unpacks callback and maplike/setlike object from reserved slots, then runs
// callback for each key (and value, for maplikes)
bool ForEachHandler(JSContext* aCx, unsigned aArgc, JS::Value* aVp);
// Unpacks backing object (ES6 map/set) from the reserved slot of a reflector
// for a maplike/setlike interface. If backing object does not exist, creates
// backing object in the compartment of the reflector involved, making this safe
// to use across compartments/via xrays. Return values of these methods will
// always be in the context compartment.
bool GetMaplikeBackingObject(JSContext* aCx, JS::Handle<JSObject*> aObj,
size_t aSlotIndex,
JS::MutableHandle<JSObject*> aBackingObj,
bool* aBackingObjCreated);
bool GetSetlikeBackingObject(JSContext* aCx, JS::Handle<JSObject*> aObj,
size_t aSlotIndex,
JS::MutableHandle<JSObject*> aBackingObj,
bool* aBackingObjCreated);
} // namespace dom
} // namespace mozilla

View File

@ -1143,6 +1143,18 @@ class CGHeaders(CGWrapper):
if funcList is not None:
addHeaderForFunc(funcList[0])
for desc in descriptors:
if desc.interface.maplikeOrSetlike:
# We need ToJSValue.h for maplike/setlike type conversions
bindingHeaders.add("mozilla/dom/ToJSValue.h")
# Add headers for the key and value types of the maplike, since
# they'll be needed for convenience functions
addHeadersForType((desc.interface.maplikeOrSetlike.keyType,
desc, None))
if desc.interface.maplikeOrSetlike.valueType:
addHeadersForType((desc.interface.maplikeOrSetlike.valueType,
desc, None))
for d in dictionaries:
if d.parent:
declareIncludes.add(self.getDeclarationFilename(d.parent))
@ -2164,7 +2176,9 @@ class MethodDefiner(PropertyDefiner):
"name": m.identifier.name,
"methodInfo": not m.isStatic(),
"length": methodLength(m),
"flags": "JSPROP_ENUMERATE",
# Methods generated for a maplike/setlike declaration are not
# enumerable.
"flags": "JSPROP_ENUMERATE" if not m.isMaplikeOrSetlikeMethod() else "0",
"condition": PropertyDefiner.getControllingCondition(m, descriptor),
"allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable"),
"returnsPromise": m.returnsPromise(),
@ -2175,9 +2189,21 @@ class MethodDefiner(PropertyDefiner):
else:
self.regular.append(method)
# FIXME Check for an existing iterator on the interface first.
if (any(m.isGetter() and m.isIndexed() for m in methods) and
not any("@@iterator" in m.aliases for m in methods)):
# TODO: Once iterable is implemented, use tiebreak rules instead of
# failing. Also, may be more tiebreak rules to implement once spec bug
# is resolved.
# https://www.w3.org/Bugs/Public/show_bug.cgi?id=28592
def hasIterator(methods, regular):
return (any("@@iterator" in m.aliases for m in methods) or
any("@@iterator" == r["name"] for r in regular))
if (any(m.isGetter() and m.isIndexed() for m in methods)):
if hasIterator(methods, self.regular):
raise TypeError("Cannot have indexed getter/attr on "
"interface %s with other members "
"that generate @@iterator, such as "
"maplike/setlike or aliased functions." %
self.descriptor.interface.identifier.name)
self.regular.append({
"name": "@@iterator",
"methodInfo": False,
@ -2187,6 +2213,31 @@ class MethodDefiner(PropertyDefiner):
"condition": MemberCondition(None, None)
})
# Generate the maplike/setlike iterator, if one wasn't already
# generated by a method. If we already have an @@iterator symbol, fail.
if descriptor.interface.maplikeOrSetlike:
if hasIterator(methods, self.regular):
raise TypeError("Cannot have maplike/setlike interface with "
"other members that generate @@iterator "
"on interface %s, such as indexed getters "
"or aliased functions." %
self.descriptor.interface.identifier.name)
for m in methods:
if (m.isMaplikeOrSetlikeMethod() and
((m.maplikeOrSetlike.isMaplike() and
m.identifier.name == "entries") or
(m.maplikeOrSetlike.isSetlike() and
m.identifier.name == "values"))):
self.regular.append({
"name": "@@iterator",
"methodName": m.identifier.name,
"length": methodLength(m),
"flags": "0",
"condition": PropertyDefiner.getControllingCondition(m,
descriptor),
})
break
if not static:
stringifier = descriptor.operations['Stringifier']
if (stringifier and
@ -2284,7 +2335,9 @@ class MethodDefiner(PropertyDefiner):
jitinfo = "nullptr"
else:
selfHostedName = "nullptr"
accessor = m.get("nativeName", IDLToCIdentifier(m["name"]))
# When defining symbols, function name may not match symbol name
methodName = m.get("methodName", m["name"])
accessor = m.get("nativeName", IDLToCIdentifier(methodName))
if m.get("methodInfo", True):
# Cast this in case the methodInfo is a
# JSTypedMethodJitInfo.
@ -2377,8 +2430,10 @@ class AttrDefiner(PropertyDefiner):
def flags(attr):
unforgeable = " | JSPROP_PERMANENT" if self.unforgeable else ""
return ("JSPROP_SHARED | JSPROP_ENUMERATE" +
unforgeable)
# Attributes generated as part of a maplike/setlike declaration are
# not enumerable.
enumerable = " | JSPROP_ENUMERATE" if not attr.isMaplikeOrSetlikeAttr() else ""
return ("JSPROP_SHARED" + enumerable + unforgeable)
def getter(attr):
if self.static:
@ -5539,7 +5594,7 @@ class CGArgumentConverter(CGThing):
will be automatically uppercased.
"""
def __init__(self, argument, index, descriptorProvider,
argDescription,
argDescription, member,
invalidEnumValueFatal=True, lenientFloatCode=None):
CGThing.__init__(self)
self.argument = argument
@ -5556,8 +5611,16 @@ class CGArgumentConverter(CGThing):
"obj": "obj",
"passedToJSImpl": toStringBool(isJSImplementedDescriptor(descriptorProvider))
}
self.replacementVariables["val"] = string.Template(
"args[${index}]").substitute(replacer)
# If we have a method generated by the maplike/setlike portion of an
# interface, arguments can possibly be undefined, but will need to be
# converted to the key/value type of the backing object. In this case,
# use .get() instead of direct access to the argument.
if member.isMethod() and member.isMaplikeOrSetlikeMethod():
self.replacementVariables["val"] = string.Template(
"args.get(${index})").substitute(replacer)
else:
self.replacementVariables["val"] = string.Template(
"args[${index}]").substitute(replacer)
haveValueCheck = string.Template(
"args.hasDefined(${index})").substitute(replacer)
self.replacementVariables["haveValue"] = haveValueCheck
@ -6795,7 +6858,7 @@ class CGPerSignatureCall(CGThing):
cgThings.append(
CGArgumentConverter(arguments[i], i, self.descriptor,
argDescription % {"index": i + 1},
invalidEnumValueFatal=not setter,
idlNode, invalidEnumValueFatal=not setter,
lenientFloatCode=lenientFloatCode))
if needsUnwrap:
@ -6832,11 +6895,19 @@ class CGPerSignatureCall(CGThing):
CGIfWrapper(CGList(xraySteps),
"objIsXray"))
cgThings.append(CGCallGenerator(
self.getErrorReport() if self.isFallible() else None,
self.getArguments(), argsPre, returnType,
self.extendedAttributes, descriptor, nativeMethodName,
static, argsPost=argsPost, resultVar=resultVar))
# If this is a method that was generated by a maplike/setlike
# interface, use the maplike/setlike generator to fill in the body.
# Otherwise, use CGCallGenerator to call the native method.
if idlNode.isMethod() and idlNode.isMaplikeOrSetlikeMethod():
cgThings.append(CGMaplikeOrSetlikeMethodGenerator(descriptor,
idlNode.maplikeOrSetlike,
idlNode.identifier.name))
else:
cgThings.append(CGCallGenerator(
self.getErrorReport() if self.isFallible() else None,
self.getArguments(), argsPre, returnType,
self.extendedAttributes, descriptor, nativeMethodName,
static, argsPost=argsPost, resultVar=resultVar))
self.cgRoot = CGList(cgThings)
def getArguments(self):
@ -7051,7 +7122,10 @@ class CGMethodCall(CGThing):
self.cgRoot = CGList([getPerSignatureCall(signature)])
requiredArgs = requiredArgCount(signature)
if requiredArgs > 0:
# Skip required arguments check for maplike/setlike interfaces, as
# they can have arguments which are not passed, and are treated as
# if undefined had been explicitly passed.
if requiredArgs > 0 and not method.isMaplikeOrSetlikeMethod():
code = fill(
"""
if (MOZ_UNLIKELY(args.length() < ${requiredArgs})) {
@ -7146,7 +7220,7 @@ class CGMethodCall(CGThing):
# possibleSignatures[0]
caseBody = [CGArgumentConverter(possibleSignatures[0][1][i],
i, descriptor,
argDesc % (i + 1))
argDesc % (i + 1), method)
for i in range(0, distinguishingIndex)]
# Select the right overload from our set.
@ -7438,7 +7512,12 @@ class FakeArgument():
self.variadic = False
self.defaultValue = None
self._allowTreatNonCallableAsNull = allowTreatNonCallableAsNull
self.treatNullAs = interfaceMember.treatNullAs
# For FakeArguments generated by maplike/setlike convenience functions,
# we won't have an interfaceMember to pass in.
if interfaceMember:
self.treatNullAs = interfaceMember.treatNullAs
else:
self.treatNullAs = "Default"
if isinstance(interfaceMember, IDLAttribute):
self.enforceRange = interfaceMember.enforceRange
self.clamp = interfaceMember.clamp
@ -8008,6 +8087,12 @@ class CGSpecializedGetter(CGAbstractStaticMethod):
CGAbstractStaticMethod.__init__(self, descriptor, name, "bool", args)
def definition_body(self):
if self.attr.maplikeOrSetlike:
# If the interface is maplike/setlike, there will be one getter
# method for the size property of the backing object. Due to having
# to unpack the backing object from the slot, this requires its own
# generator.
return getMaplikeOrSetlikeSizeGetterBody(self.descriptor, self.attr)
nativeName = CGSpecializedGetter.makeNativeName(self.descriptor,
self.attr)
if self.attr.slotIndex is not None:
@ -11263,6 +11348,10 @@ class CGDescriptor(CGThing):
crossOriginMethods.add(m.identifier.name)
if m.getExtendedAttribute("MethodIdentityTestable"):
cgThings.append(CGMethodIdentityTest(descriptor, m))
# If we've hit the maplike/setlike member itself, go ahead and
# generate its convenience functions.
elif m.isMaplikeOrSetlike():
cgThings.append(CGMaplikeOrSetlikeHelperGenerator(descriptor, m))
elif m.isAttr():
if m.stringifier:
raise TypeError("Stringifier attributes not supported yet. "
@ -12438,6 +12527,15 @@ class CGForwardDeclarations(CGWrapper):
# Needed for at least Wrap.
for d in descriptors:
builder.add(d.nativeType)
# If we're an interface and we have a maplike/setlike declaration,
# we'll have helper functions exposed to the native side of our
# bindings, which will need to show up in the header. If either of
# our key/value types are interfaces, they'll be passed as
# arguments to helper functions, and they'll need to be forward
# declared in the header.
if d.interface.maplikeOrSetlike:
forwardDeclareForType(d.interface.maplikeOrSetlike.keyType)
forwardDeclareForType(d.interface.maplikeOrSetlike.valueType)
# We just about always need NativePropertyHooks
builder.addInMozillaDom("NativePropertyHooks", isStruct=True)
@ -14070,8 +14168,12 @@ class FakeMember():
class CallbackMember(CGNativeMember):
# XXXbz It's OK to use CallbackPreserveColor for wrapScope because
# CallSetup already handled the unmark-gray bits for us. we don't have
# anything better to use for 'obj', really...
def __init__(self, sig, name, descriptorProvider, needThisHandling,
rethrowContentException=False, typedArraysAreStructs=False):
rethrowContentException=False, typedArraysAreStructs=False,
wrapScope='CallbackPreserveColor()'):
"""
needThisHandling is True if we need to be able to accept a specified
thisObj, False otherwise.
@ -14095,6 +14197,8 @@ class CallbackMember(CGNativeMember):
# will handle generating public versions that handle the "this" stuff.
visibility = "private" if needThisHandling else "public"
self.rethrowContentException = rethrowContentException
self.wrapScope = wrapScope
# We don't care, for callback codegen, whether our original member was
# a method or attribute or whatnot. Just always pass FakeMember()
# here.
@ -14214,10 +14318,7 @@ class CallbackMember(CGNativeMember):
'successCode': "continue;\n" if arg.variadic else "break;\n",
'jsvalRef': "argv[%s]" % jsvalIndex,
'jsvalHandle': "argv[%s]" % jsvalIndex,
# XXXbz we don't have anything better to use for 'obj',
# really... It's OK to use CallbackPreserveColor because
# CallSetup already handled the unmark-gray bits for us.
'obj': 'CallbackPreserveColor()',
'obj': self.wrapScope,
'returnsNewObject': False,
'exceptionCode': self.exceptionCode,
'typedArraysAreStructs': self.typedArraysAreStructs
@ -14546,6 +14647,497 @@ class CGJSImplInitOperation(CallbackOperationBase):
return "__init"
def getMaplikeOrSetlikeErrorReturn(helperImpl):
"""
Generate return values based on whether a maplike or setlike generated
method is an interface method (which returns bool) or a helper function
(which uses ErrorResult).
"""
if helperImpl:
return dedent(
"""
aRv.Throw(NS_ERROR_UNEXPECTED);
return%s;
""" % helperImpl.getDefaultRetval())
return "return false;\n"
def getMaplikeOrSetlikeBackingObject(descriptor, maplikeOrSetlike, helperImpl=None):
"""
Generate code to get/create a JS backing object for a maplike/setlike
declaration from the declaration slot.
"""
func_prefix = maplikeOrSetlike.maplikeOrSetlikeType.title()
ret = fill(
"""
JS::Rooted<JSObject*> backingObj(cx);
bool created = false;
if (!Get${func_prefix}BackingObject(cx, obj, ${slot}, &backingObj, &created)) {
$*{errorReturn}
}
if (created) {
PreserveWrapper<${selfType}>(self);
}
""",
slot=memberReservedSlot(maplikeOrSetlike),
func_prefix=func_prefix,
errorReturn=getMaplikeOrSetlikeErrorReturn(helperImpl),
selfType=descriptor.nativeType)
return ret
def getMaplikeOrSetlikeSizeGetterBody(descriptor, attr):
"""
Creates the body for the size getter method of maplike/setlike interfaces.
"""
# We should only have one declaration attribute currently
assert attr.identifier.name == "size"
assert attr.isMaplikeOrSetlikeAttr()
return fill(
"""
$*{getBackingObj}
uint32_t result = JS::${funcPrefix}Size(cx, backingObj);
MOZ_ASSERT(!JS_IsExceptionPending(cx));
args.rval().setNumber(result);
return true;
""",
getBackingObj=getMaplikeOrSetlikeBackingObject(descriptor,
attr.maplikeOrSetlike),
funcPrefix=attr.maplikeOrSetlike.prefix)
class CGMaplikeOrSetlikeMethodGenerator(CGThing):
"""
Creates methods for maplike/setlike interfaces. It is expected that all
methods will be have a maplike/setlike object attached. 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, maplikeOrSetlike, methodName,
helperImpl=None):
CGThing.__init__(self)
# True if this will be the body of a C++ helper function.
self.helperImpl = helperImpl
self.descriptor = descriptor
self.maplikeOrSetlike = maplikeOrSetlike
self.cgRoot = CGList([])
impl_method_name = methodName
if impl_method_name[0] == "_":
# double underscore means this is a js-implemented chrome only rw
# function. Truncate the double underscore so calling the right
# underlying JSAPI function still works.
impl_method_name = impl_method_name[2:]
self.cgRoot.append(CGGeneric(
getMaplikeOrSetlikeBackingObject(self.descriptor,
self.maplikeOrSetlike,
self.helperImpl)))
self.returnStmt = getMaplikeOrSetlikeErrorReturn(self.helperImpl)
# Generates required code for the method. Method descriptions included
# in definitions below. Throw if we don't have a method to fill in what
# we're looking for.
try:
methodGenerator = getattr(self, impl_method_name)
except AttributeError:
raise TypeError("Missing %s method definition '%s'" %
(self.maplikeOrSetlike.maplikeOrSetlikeType,
methodName))
# Method generator returns tuple, containing:
#
# - a list of CGThings representing setup code for preparing to call
# the JS API function
# - a list of arguments needed for the JS API function we're calling
# - list of code CGThings needed for return value conversion.
(setupCode, arguments, setResult) = methodGenerator()
# Create the actual method call, and then wrap it with the code to
# return the value if needed.
funcName = (self.maplikeOrSetlike.prefix +
MakeNativeName(impl_method_name))
# Append the list of setup code CGThings
self.cgRoot.append(CGList(setupCode))
# Create the JS API call
self.cgRoot.append(CGWrapper(
CGGeneric(fill(
"""
if (!JS::${funcName}(${args})) {
$*{errorReturn}
}
""",
funcName=funcName,
args=", ".join(["cx", "backingObj"] + arguments),
errorReturn=self.returnStmt))))
# Append result conversion
self.cgRoot.append(CGList(setResult))
def mergeTuples(self, a, b):
"""
Expecting to take 2 tuples were all elements are lists, append the lists in
the second tuple to the lists in the first.
"""
return tuple([x + y for x, y in zip(a, b)])
def appendArgConversion(self, name):
"""
Generate code to convert arguments to JS::Values, so they can be
passed into JSAPI functions.
"""
return CGGeneric(fill(
"""
JS::Rooted<JS::Value> ${name}Val(cx);
if (!ToJSValue(cx, ${name}, &${name}Val)) {
$*{errorReturn}
}
""",
name=name,
errorReturn=self.returnStmt))
def appendKeyArgConversion(self):
"""
Generates the key argument for methods. Helper functions will use
an AutoValueVector, while interface methods have seperate JS::Values.
"""
if self.helperImpl:
return ([], ["argv[0]"], [])
return ([self.appendArgConversion("arg0")], ["arg0Val"], [])
def appendKeyAndValueArgConversion(self):
"""
Generates arguments for methods that require a key and value. Helper
functions will use an AutoValueVector, while interface methods have
seperate JS::Values.
"""
r = self.appendKeyArgConversion()
if self.helperImpl:
return self.mergeTuples(r, ([], ["argv[1]"], []))
return self.mergeTuples(r, ([self.appendArgConversion("arg1")],
["arg1Val"],
[]))
def appendIteratorResult(self):
"""
Generate code to output JSObject* return values, needed for functions that
return iterators. Iterators cannot currently be wrapped via Xrays. If
something that would return an iterator is called via Xray, fail early.
"""
# TODO: Bug 1173651 - Remove check once bug 1023984 is fixed.
code = CGGeneric(dedent(
"""
// TODO (Bug 1173651): Xrays currently cannot wrap iterators. Change
// after bug 1023984 is fixed.
if (xpc::WrapperFactory::IsXrayWrapper(obj)) {
JS_ReportError(cx, "Xray wrapping of iterators not supported.");
return false;
}
JS::Rooted<JSObject*> result(cx);
JS::Rooted<JS::Value> v(cx);
"""))
arguments = "&v"
setResult = CGGeneric(dedent(
"""
result = &v.toObject();
"""))
return ([code], [arguments], [setResult])
def appendSelfResult(self):
"""
Generate code to return the interface object itself.
"""
code = CGGeneric(dedent(
"""
JS::Rooted<JSObject*> result(cx);
"""))
setResult = CGGeneric(dedent(
"""
result = obj;
"""))
return ([code], [], [setResult])
def appendBoolResult(self):
if self.helperImpl:
return ([CGGeneric()], ["&aRetVal"], [])
return ([CGGeneric("bool result;\n")], ["&result"], [])
def forEach(self):
"""
void forEach(callback c, any thisval);
ForEach takes a callback, and a possible value to use as 'this'. The
callback needs to take value, key, and the interface object
implementing maplike/setlike. In order to make sure that the third arg
is our interface object instead of the map/set backing object, we
create a js function with the callback and original object in its
storage slots, then use a helper function in BindingUtils to make sure
the callback is called correctly.
"""
assert(not self.helperImpl)
code = [CGGeneric(dedent(
"""
// Create a wrapper function.
JSFunction* func = js::NewFunctionWithReserved(cx, ForEachHandler, 3, 0, nullptr);
if (!func) {
return false;
}
JS::Rooted<JSObject*> funcObj(cx, JS_GetFunctionObject(func));
JS::Rooted<JS::Value> funcVal(cx, JS::ObjectValue(*funcObj));
js::SetFunctionNativeReserved(funcObj, FOREACH_CALLBACK_SLOT,
JS::ObjectValue(*arg0));
js::SetFunctionNativeReserved(funcObj, FOREACH_MAPLIKEORSETLIKEOBJ_SLOT,
JS::ObjectValue(*obj));
"""))]
arguments = ["funcVal", "arg1"]
return (code, arguments, [])
def set(self):
"""
object set(key, value);
Maplike only function, takes key and sets value to it, returns
interface object unless being called from a C++ helper.
"""
assert self.maplikeOrSetlike.isMaplike()
r = self.appendKeyAndValueArgConversion()
if self.helperImpl:
return r
return self.mergeTuples(r, self.appendSelfResult())
def add(self):
"""
object add(value);
Setlike only function, adds value to set, returns interface object
unless being called from a C++ helper
"""
assert self.maplikeOrSetlike.isSetlike()
r = self.appendKeyArgConversion()
if self.helperImpl:
return r
return self.mergeTuples(r, self.appendSelfResult())
def get(self):
"""
type? get(key);
Retrieves a value from a backing object based on the key. Returns value
if key is in backing object, undefined otherwise.
"""
assert self.maplikeOrSetlike.isMaplike()
r = self.appendKeyArgConversion()
code = [CGGeneric(dedent(
"""
JS::Rooted<JS::Value> result(cx);
"""))]
arguments = ["&result"]
return self.mergeTuples(r, (code, arguments, []))
def has(self):
"""
bool has(key);
Check if an entry exists in the backing object. Returns true if value
exists in backing object, false otherwise.
"""
return self.mergeTuples(self.appendKeyArgConversion(),
self.appendBoolResult())
def keys(self):
"""
object keys();
Returns new object iterator with all keys from backing object.
"""
return self.appendIteratorResult()
def values(self):
"""
object values();
Returns new object iterator with all values from backing object.
"""
return self.appendIteratorResult()
def entries(self):
"""
object entries();
Returns new object iterator with all keys and values from backing
object. Keys will be null for set.
"""
return self.appendIteratorResult()
def clear(self):
"""
void clear();
Removes all entries from map/set.
"""
return ([], [], [])
def delete(self):
"""
bool delete(key);
Deletes an entry from the backing object. Returns true if value existed
in backing object, false otherwise.
"""
return self.mergeTuples(self.appendKeyArgConversion(),
self.appendBoolResult())
def define(self):
return self.cgRoot.define()
class CGMaplikeOrSetlikeHelperFunctionGenerator(CallbackMember):
"""
Generates code to allow C++ to perform operations on backing objects. Gets
a context from the binding wrapper, turns arguments into JS::Values (via
CallbackMember/CGNativeMember argument conversion), then uses
CGMaplikeOrSetlikeMethodGenerator to generate the body.
"""
class HelperFunction(CGAbstractMethod):
"""
Generates context retrieval code and rooted JSObject for interface for
CGMaplikeOrSetlikeMethodGenerator to use
"""
def __init__(self, descriptor, name, args, code, needsBoolReturn=False):
self.code = code
CGAbstractMethod.__init__(self, descriptor, name,
"bool" if needsBoolReturn else "void",
args)
def definition_body(self):
return self.code
def __init__(self, descriptor, maplikeOrSetlike, name, needsKeyArg=False,
needsValueArg=False, needsBoolReturn=False):
args = []
self.maplikeOrSetlike = maplikeOrSetlike
self.needsBoolReturn = needsBoolReturn
if needsKeyArg:
args.append(FakeArgument(maplikeOrSetlike.keyType, None, 'aKey'))
if needsValueArg:
assert needsKeyArg
args.append(FakeArgument(maplikeOrSetlike.valueType, None, 'aValue'))
# Run CallbackMember init function to generate argument conversion code.
# wrapScope is set to 'obj' when generating maplike or setlike helper
# functions, as we don't have access to the CallbackPreserveColor
# method.
CallbackMember.__init__(self,
[BuiltinTypes[IDLBuiltinType.Types.void], args],
name, descriptor, False,
wrapScope='obj')
# Wrap CallbackMember body code into a CGAbstractMethod to make
# generation easier.
self.implMethod = CGMaplikeOrSetlikeHelperFunctionGenerator.HelperFunction(
descriptor, name, self.args, self.body, needsBoolReturn)
def getCallSetup(self):
return dedent(
"""
MOZ_ASSERT(self);
AutoJSAPI jsapi;
jsapi.Init();
jsapi.TakeOwnershipOfErrorReporting();
JSContext* cx = jsapi.cx();
JSAutoCompartment tempCompartment(cx, xpc::UnprivilegedJunkScope());
JS::Rooted<JS::Value> v(cx);
if(!ToJSValue(cx, self, &v)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return%s;
}
// This is a reflector, but due to trying to name things
// similarly across method generators, it's called obj here.
JS::Rooted<JSObject*> obj(cx);
obj = js::UncheckedUnwrap(&v.toObject(), /* stopAtOuter = */ false);
JSAutoCompartment reflectorCompartment(cx, obj);
""" % self.getDefaultRetval())
def getArgs(self, returnType, argList):
# We don't need the context or the value. We'll generate those instead.
args = CGNativeMember.getArgs(self, returnType, argList)
# Prepend a pointer to the binding object onto the arguments
return [Argument(self.descriptorProvider.nativeType + "*", "self")] + args
def getResultConversion(self):
if self.needsBoolReturn:
return "return aRetVal;\n"
return "return;\n"
def getRvalDecl(self):
if self.needsBoolReturn:
return "bool aRetVal;\n"
return ""
def getArgcDecl(self):
# Don't need argc for anything.
return None
def getDefaultRetval(self):
if self.needsBoolReturn:
return " false"
return ""
def getCall(self):
return CGMaplikeOrSetlikeMethodGenerator(self.descriptorProvider,
self.maplikeOrSetlike,
self.name.lower(),
helperImpl=self).define()
def getPrettyName(self):
return self.name
def declare(self):
return self.implMethod.declare()
def define(self):
return self.implMethod.define()
class CGMaplikeOrSetlikeHelperGenerator(CGNamespace):
"""
Declares and defines convenience methods for accessing backing objects on
setlike/maplike interface. Generates function signatures, un/packs
backing objects from slot, etc.
"""
def __init__(self, descriptor, maplikeOrSetlike):
self.descriptor = descriptor
self.maplikeOrSetlike = maplikeOrSetlike
self.namespace = "%sHelpers" % (self.maplikeOrSetlike.maplikeOrSetlikeType.title())
self.helpers = [
CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor,
maplikeOrSetlike,
"Clear"),
CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor,
maplikeOrSetlike,
"Delete",
needsKeyArg=True,
needsBoolReturn=True),
CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor,
maplikeOrSetlike,
"Has",
needsKeyArg=True,
needsBoolReturn=True)]
if self.maplikeOrSetlike.isMaplike():
self.helpers.append(
CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor,
maplikeOrSetlike,
"Set",
needsKeyArg=True,
needsValueArg=True))
else:
assert(self.maplikeOrSetlike.isSetlike())
self.helpers.append(
CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor,
maplikeOrSetlike,
"Add",
needsKeyArg=True))
CGNamespace.__init__(self, self.namespace, CGList(self.helpers))
class GlobalGenRoots():
"""
Roots for global codegen.

View File

@ -209,6 +209,15 @@ ToJSValue(JSContext* aCx,
return ToJSValue(aCx, *aArgument.get(), aValue);
}
template <typename T>
MOZ_WARN_UNUSED_RESULT bool
ToJSValue(JSContext* aCx,
const NonNull<T>& aArgument,
JS::MutableHandle<JS::Value> aValue)
{
return ToJSValue(aCx, *aArgument.get(), aValue);
}
// Accept WebIDL dictionaries
template <class T>
MOZ_WARN_UNUSED_RESULT

View File

@ -84,6 +84,25 @@ SOURCES += [
'StructuredClone.cpp',
]
# Tests for maplike and setlike require bindings to be built, which means they
# must be included in libxul. This breaks the "no test classes are exported"
# rule stated in the test/ directory, but it's the only way this will work.
# Test classes are only built in debug mode, and all tests requiring use of
# them are only run in debug mode.
if CONFIG['MOZ_DEBUG']:
EXPORTS.mozilla.dom += [
"test/TestInterfaceMaplike.h",
"test/TestInterfaceMaplikeObject.h",
"test/TestInterfaceSetlike.h",
"test/TestInterfaceSetlikeNode.h"
]
UNIFIED_SOURCES += [
"test/TestInterfaceMaplike.cpp",
"test/TestInterfaceMaplikeObject.cpp",
"test/TestInterfaceSetlike.cpp",
"test/TestInterfaceSetlikeNode.cpp",
]
include('/ipc/chromium/chromium-config.mozbuild')
if CONFIG['MOZ_AUDIO_CHANNEL_MANAGER']:

View File

@ -3399,6 +3399,7 @@ class IDLInterfaceMember(IDLObjectWithIdentifier, IDLExposureMixins):
[self.location])
self.aliases.append(alias)
# MaplikeOrSetlike adds a trait to an interface, like map or iteration
# functions. To handle them while still getting all of the generated binding
# code taken care of, we treat them as macros that are expanded into members
@ -3425,6 +3426,12 @@ class IDLMaplikeOrSetlike(IDLInterfaceMember):
self.disallowedMemberNames = []
self.disallowedNonMethodNames = []
# When generating JSAPI access code, we need to know the backing object
# type prefix to create the correct function. Generate here for reuse.
if self.isMaplike():
self.prefix = 'Map'
elif self.isSetlike():
self.prefix = 'Set'
def __str__(self):
return "declared '%s' with key '%s'" % (self.maplikeOrSetlikeType, self.keyType)
@ -4349,7 +4356,15 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
return self._hasOverloads
def isIdentifierLess(self):
return self.identifier.name[:2] == "__"
"""
True if the method name started with __, and if the method is not a
maplike/setlike method. Interfaces with maplike/setlike will generate
methods starting with __ for chrome only backing object access in JS
implemented interfaces, so while these functions use what is considered
an non-identifier name, they actually DO have an identifier.
"""
return (self.identifier.name[:2] == "__" and
not self.isMaplikeOrSetlikeMethod())
def resolve(self, parentScope):
assert isinstance(parentScope, IDLScope)

View File

@ -1,2 +1,4 @@
component {2ac4e026-cf25-47d5-b067-78d553c3cad8} TestInterfaceJS.js
contract @mozilla.org/dom/test-interface-js;1 {2ac4e026-cf25-47d5-b067-78d553c3cad8}
component {4bc6f6f3-e005-4f0a-b42d-4d1663a9013a} TestInterfaceJSMaplike.js
contract @mozilla.org/dom/test-interface-js-maplike;1 {4bc6f6f3-e005-4f0a-b42d-4d1663a9013a}

View File

@ -0,0 +1,38 @@
/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Cu = Components.utils;
const Ci = Components.interfaces;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
function TestInterfaceJSMaplike() {}
TestInterfaceJSMaplike.prototype = {
classID: Components.ID("{4bc6f6f3-e005-4f0a-b42d-4d1663a9013a}"),
contractID: "@mozilla.org/dom/test-interface-js-maplike;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
Ci.nsIDOMGlobalPropertyInitializer]),
init: function(win) { this._win = win; },
__init: function () {},
setInternal: function(aKey, aValue) {
return this.__DOM_IMPL__.__set(aKey, aValue);
},
deleteInternal: function(aKey) {
return this.__DOM_IMPL__.__delete(aKey);
},
clearInternal: function() {
return this.__DOM_IMPL__.__clear();
}
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TestInterfaceJSMaplike])

View File

@ -0,0 +1,84 @@
/* 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/. */
#include "mozilla/dom/TestInterfaceMaplike.h"
#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeBinding.h"
#include "nsPIDOMWindow.h"
#include "mozilla/dom/BindingUtils.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceMaplike, mParent)
NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceMaplike)
NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceMaplike)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceMaplike)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
TestInterfaceMaplike::TestInterfaceMaplike(nsPIDOMWindow* aParent)
: mParent(aParent)
{
}
//static
already_AddRefed<TestInterfaceMaplike>
TestInterfaceMaplike::Constructor(const GlobalObject& aGlobal,
ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
nsRefPtr<TestInterfaceMaplike> r = new TestInterfaceMaplike(window);
return r.forget();
}
JSObject*
TestInterfaceMaplike::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return TestInterfaceMaplikeBinding::Wrap(aCx, this, aGivenProto);
}
nsPIDOMWindow*
TestInterfaceMaplike::GetParentObject() const
{
return mParent;
}
void
TestInterfaceMaplike::SetInternal(const nsAString& aKey, int32_t aValue)
{
ErrorResult rv;
TestInterfaceMaplikeBinding::MaplikeHelpers::Set(this, aKey, aValue, rv);
}
void
TestInterfaceMaplike::ClearInternal()
{
ErrorResult rv;
TestInterfaceMaplikeBinding::MaplikeHelpers::Clear(this, rv);
}
bool
TestInterfaceMaplike::DeleteInternal(const nsAString& aKey)
{
ErrorResult rv;
return TestInterfaceMaplikeBinding::MaplikeHelpers::Delete(this, aKey, rv);
}
bool
TestInterfaceMaplike::HasInternal(const nsAString& aKey)
{
ErrorResult rv;
return TestInterfaceMaplikeBinding::MaplikeHelpers::Has(this, aKey, rv);
}
}
}

View File

@ -0,0 +1,52 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#ifndef mozilla_dom_TestInterfaceMaplike_h
#define mozilla_dom_TestInterfaceMaplike_h
#include "nsWrapperCache.h"
#include "nsCOMPtr.h"
class nsPIDOMWindow;
namespace mozilla {
class ErrorResult;
namespace dom {
class GlobalObject;
// Implementation of test binding for webidl maplike interfaces, using
// primitives for key and value types.
class TestInterfaceMaplike final : public nsISupports,
public nsWrapperCache
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TestInterfaceMaplike)
explicit TestInterfaceMaplike(nsPIDOMWindow* aParent);
nsPIDOMWindow* GetParentObject() const;
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
static already_AddRefed<TestInterfaceMaplike>
Constructor(const GlobalObject& aGlobal, ErrorResult& rv);
// External access for testing internal convenience functions.
void SetInternal(const nsAString& aKey, int32_t aValue);
void ClearInternal();
bool DeleteInternal(const nsAString& aKey);
bool HasInternal(const nsAString& aKey);
private:
virtual ~TestInterfaceMaplike() {}
nsCOMPtr<nsPIDOMWindow> mParent;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_TestInterfaceMaplike_h

View File

@ -0,0 +1,88 @@
/* 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/. */
#include "mozilla/dom/TestInterfaceMaplikeObject.h"
#include "mozilla/dom/TestInterfaceMaplike.h"
#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeBinding.h"
#include "nsPIDOMWindow.h"
#include "mozilla/dom/BindingUtils.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceMaplikeObject, mParent)
NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceMaplikeObject)
NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceMaplikeObject)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceMaplikeObject)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
TestInterfaceMaplikeObject::TestInterfaceMaplikeObject(nsPIDOMWindow* aParent)
: mParent(aParent)
{
}
//static
already_AddRefed<TestInterfaceMaplikeObject>
TestInterfaceMaplikeObject::Constructor(const GlobalObject& aGlobal,
ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
nsRefPtr<TestInterfaceMaplikeObject> r =
new TestInterfaceMaplikeObject(window);
return r.forget();
}
JSObject*
TestInterfaceMaplikeObject::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto)
{
return TestInterfaceMaplikeObjectBinding::Wrap(aCx, this, aGivenProto);
}
nsPIDOMWindow*
TestInterfaceMaplikeObject::GetParentObject() const
{
return mParent;
}
void
TestInterfaceMaplikeObject::SetInternal(const nsAString& aKey)
{
nsRefPtr<TestInterfaceMaplike> p(new TestInterfaceMaplike(mParent));
ErrorResult rv;
TestInterfaceMaplikeObjectBinding::MaplikeHelpers::Set(this, aKey, *p, rv);
}
void
TestInterfaceMaplikeObject::ClearInternal()
{
ErrorResult rv;
TestInterfaceMaplikeObjectBinding::MaplikeHelpers::Clear(this, rv);
}
bool
TestInterfaceMaplikeObject::DeleteInternal(const nsAString& aKey)
{
ErrorResult rv;
return TestInterfaceMaplikeObjectBinding::MaplikeHelpers::Delete(this, aKey, rv);
}
bool
TestInterfaceMaplikeObject::HasInternal(const nsAString& aKey)
{
ErrorResult rv;
return TestInterfaceMaplikeObjectBinding::MaplikeHelpers::Has(this, aKey, rv);
}
}
}

View File

@ -0,0 +1,52 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#ifndef mozilla_dom_TestInterfaceMaplikeObject_h
#define mozilla_dom_TestInterfaceMaplikeObject_h
#include "nsWrapperCache.h"
#include "nsCOMPtr.h"
class nsPIDOMWindow;
namespace mozilla {
class ErrorResult;
namespace dom {
class GlobalObject;
// Implementation of test binding for webidl maplike interfaces, using
// primitives for key types and objects for value types.
class TestInterfaceMaplikeObject final : public nsISupports,
public nsWrapperCache
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TestInterfaceMaplikeObject)
explicit TestInterfaceMaplikeObject(nsPIDOMWindow* aParent);
nsPIDOMWindow* GetParentObject() const;
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
static already_AddRefed<TestInterfaceMaplikeObject>
Constructor(const GlobalObject& aGlobal,ErrorResult& rv);
// External access for testing internal convenience functions.
void SetInternal(const nsAString& aKey);
void ClearInternal();
bool DeleteInternal(const nsAString& aKey);
bool HasInternal(const nsAString& aKey);
private:
virtual ~TestInterfaceMaplikeObject() {}
nsCOMPtr<nsPIDOMWindow> mParent;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_TestInterfaceMaplikeObject_h

View File

@ -0,0 +1,58 @@
/* 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/. */
#include "mozilla/dom/TestInterfaceSetlike.h"
#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeBinding.h"
#include "nsPIDOMWindow.h"
#include "mozilla/dom/BindingUtils.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceSetlike, mParent)
NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceSetlike)
NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceSetlike)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceSetlike)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
TestInterfaceSetlike::TestInterfaceSetlike(JSContext* aCx,
nsPIDOMWindow* aParent)
: mParent(aParent)
{
}
//static
already_AddRefed<TestInterfaceSetlike>
TestInterfaceSetlike::Constructor(const GlobalObject& aGlobal,
ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
nsRefPtr<TestInterfaceSetlike> r = new TestInterfaceSetlike(nullptr, window);
return r.forget();
}
JSObject*
TestInterfaceSetlike::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto)
{
return TestInterfaceSetlikeBinding::Wrap(aCx, this, aGivenProto);
}
nsPIDOMWindow*
TestInterfaceSetlike::GetParentObject() const
{
return mParent;
}
}
}

View File

@ -0,0 +1,46 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#ifndef mozilla_dom_TestInterfaceSetlike_h
#define mozilla_dom_TestInterfaceSetlike_h
#include "nsWrapperCache.h"
#include "nsCOMPtr.h"
class nsPIDOMWindow;
namespace mozilla {
class ErrorResult;
namespace dom {
class GlobalObject;
// Implementation of test binding for webidl setlike interfaces, using
// primitives for key type.
class TestInterfaceSetlike final : public nsISupports,
public nsWrapperCache
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TestInterfaceSetlike)
explicit TestInterfaceSetlike(JSContext* aCx,
nsPIDOMWindow* aParent);
nsPIDOMWindow* GetParentObject() const;
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
static already_AddRefed<TestInterfaceSetlike>
Constructor(const GlobalObject& aGlobal, ErrorResult& rv);
private:
virtual ~TestInterfaceSetlike() {}
nsCOMPtr<nsPIDOMWindow> mParent;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_TestInterfaceSetlike_h

View File

@ -0,0 +1,58 @@
/* 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/. */
#include "mozilla/dom/TestInterfaceSetlikeNode.h"
#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeBinding.h"
#include "nsPIDOMWindow.h"
#include "mozilla/dom/BindingUtils.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceSetlikeNode, mParent)
NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceSetlikeNode)
NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceSetlikeNode)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceSetlikeNode)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
TestInterfaceSetlikeNode::TestInterfaceSetlikeNode(JSContext* aCx,
nsPIDOMWindow* aParent)
: mParent(aParent)
{
}
//static
already_AddRefed<TestInterfaceSetlikeNode>
TestInterfaceSetlikeNode::Constructor(const GlobalObject& aGlobal,
ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
nsRefPtr<TestInterfaceSetlikeNode> r = new TestInterfaceSetlikeNode(nullptr, window);
return r.forget();
}
JSObject*
TestInterfaceSetlikeNode::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto)
{
return TestInterfaceSetlikeNodeBinding::Wrap(aCx, this, aGivenProto);
}
nsPIDOMWindow*
TestInterfaceSetlikeNode::GetParentObject() const
{
return mParent;
}
}
}

View File

@ -0,0 +1,46 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#ifndef mozilla_dom_TestInterfaceSetlikeNode_h
#define mozilla_dom_TestInterfaceSetlikeNode_h
#include "nsWrapperCache.h"
#include "nsCOMPtr.h"
class nsPIDOMWindow;
namespace mozilla {
class ErrorResult;
namespace dom {
class GlobalObject;
// Implementation of test binding for webidl setlike interfaces, using
// primitives for key type.
class TestInterfaceSetlikeNode final : public nsISupports,
public nsWrapperCache
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TestInterfaceSetlikeNode)
explicit TestInterfaceSetlikeNode(JSContext* aCx,
nsPIDOMWindow* aParent);
nsPIDOMWindow* GetParentObject() const;
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
static already_AddRefed<TestInterfaceSetlikeNode>
Constructor(const GlobalObject& aGlobal, ErrorResult& rv);
private:
virtual ~TestInterfaceSetlikeNode() {}
nsCOMPtr<nsPIDOMWindow> mParent;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_TestInterfaceSetlikeNode_h

View File

@ -14,3 +14,5 @@ support-files =
skip-if = e10s # prerendering doesn't work in e10s yet
[test_kill_longrunning_prerendered_content.xul]
skip-if = e10s # prerendering doesn't work in e10s yet
[test_bug1123516_maplikesetlikechrome.xul]
skip-if = debug == false

View File

@ -61,3 +61,5 @@ skip-if = debug == false
[test_worker_UnwrapArg.html]
[test_unforgeablesonexpando.html]
[test_crossOriginWindowSymbolAccess.html]
[test_bug1123516_maplikesetlike.html]
skip-if = debug == false

View File

@ -17,6 +17,7 @@ Library('dombindings_test_s')
EXTRA_COMPONENTS += [
'TestInterfaceJS.js',
'TestInterfaceJS.manifest',
'TestInterfaceJSMaplike.js'
]
MOCHITEST_MANIFESTS += ['mochitest.ini']

View File

@ -0,0 +1,270 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Test Maplike Interface</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script class="testbody" type="application/javascript">
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]}, function() {
base_properties = [["has", "function", 1],
["entries", "function", 0],
["keys", "function", 0],
["values", "function", 0],
["forEach", "function", 1],
["size", "number"]];
maplike_properties = base_properties.concat([["set", "function", 2]]);
setlike_properties = base_properties;
rw_properties = [["clear", "function", 0],
["delete", "function", 1]];
setlike_rw_properties = base_properties.concat(rw_properties).concat([["add", "function", 1]]);
maplike_rw_properties = maplike_properties.concat(rw_properties).concat([["get", "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
isnot(obj[name], undefined,
`${prefix} object has property ${name}`);
is(typeof obj[name], type,
`${prefix} object property ${name} is a ${type}`);
// Check function length
if (type == "function") {
is(obj[name].length, args,
`${prefix} object property ${name} is length ${args}`);
is(obj[name].name, name,
`${prefix} object method name is ${name}`);
}
// Find where property is on proto chain, check for enumerablility there.
var owner = obj;
while (owner) {
var propDesc = Object.getOwnPropertyDescriptor(owner, name);
if (propDesc) {
ok(!propDesc.enumerable,
`${prefix} object property ${name} is not enumerable`);
break;
}
owner = Object.getPrototypeOf(owner);
}
}
}
var m;
var testSet;
var testIndex;
var iterable;
// Simple map creation and functionality test
info("SimpleMap: Testing simple map creation and functionality");
m = new TestInterfaceMaplike();
ok(m, "SimpleMap: got a TestInterfaceMaplike object");
testExistence("SimpleMap: ", m, maplike_rw_properties);
is(m.size, 0, "SimpleMap: size should be zero");
ok(!m.has("test"), "SimpleMap: maplike has should return false");
is(m.get("test"), undefined, "SimpleMap: maplike get should return undefined on bogus lookup");
m1 = m.set("test", 1);
is(m, m1, "SimpleMap: return from set should be map object");
is(m.size, 1, "SimpleMap: size should be 1");
ok(m.has("test"), "SimpleMap: maplike has should return true");
is(m.get("test"), 1, "SimpleMap: maplike get should return value entered");
m2 = m.set("test2", 2);
is(m.size, 2, "SimpleMap: size should be 2");
testSet = [["test", 1], ["test2", 2]];
testIndex = 0;
m.forEach(function(v, k, o) {
"use strict";
is(o, m, "SimpleMap: foreach obj is correct");
is(k, testSet[testIndex][0], "SimpleMap: foreach map key: " + k + " = " + testSet[testIndex][0]);
is(v, testSet[testIndex][1], "SimpleMap: foreach map value: " + v + " = " + testSet[testIndex][1]);
testIndex += 1;
});
is(testIndex, 2, "SimpleMap: foreach ran correct number of times");
ok(m.has("test2"), "SimpleMap: maplike has should return true");
is(m.get("test2"), 2, "SimpleMap: maplike get should return value entered");
is(m.delete("test2"), true, "SimpleMap: maplike deletion should return boolean");
is(m.size, 1, "SimpleMap: size should be 1");
iterable = false;
for (var e of m) {
iterable = true;
is(e[0], "test", "SimpleMap: iterable first array element should be key");
is(e[1], 1, "SimpleMap: iterable second array element should be value");
}
is(m[Symbol.iterator].length, 0, "SimpleMap: @@iterator symbol is correct length");
is(m[Symbol.iterator].name, "[Symbol.iterator]", "SimpleMap: @@iterator symbol has correct name");
ok(iterable, "SimpleMap: @@iterator symbol resolved correctly");
for (var k of m.keys()) {
is(k, "test", "SimpleMap: first keys element should be 'test'");
}
for (var v of m.values()) {
is(v, 1, "SimpleMap: first values elements should be 1");
}
for (var e of m.entries()) {
is(e[0], "test", "SimpleMap: entries first array element should be 'test'");
is(e[1], 1, "SimpleMap: entries second array element should be 1");
}
m.clear();
is(m.size, 0, "SimpleMap: size should be 0 after clear");
// Simple set creation and functionality test
info("SimpleSet: Testing simple set creation and functionality");
m = new TestInterfaceSetlike();
ok(m, "SimpleSet: got a TestInterfaceSetlike object");
testExistence("SimpleSet: ", m, setlike_rw_properties);
is(m.size, 0, "SimpleSet: size should be zero");
ok(!m.has("test"), "SimpleSet: maplike has should return false");
m1 = m.add("test");
is(m, m1, "SimpleSet: return from set should be map object");
is(m.size, 1, "SimpleSet: size should be 1");
ok(m.has("test"), "SimpleSet: maplike has should return true");
m2 = m.add("test2");
is(m.size, 2, "SimpleSet: size should be 2");
testSet = ["test", "test2"];
testIndex = 0;
m.forEach(function(v, k, o) {
"use strict";
is(o, m, "SimpleSet: foreach obj is correct");
is(k, testSet[testIndex], "SimpleSet: foreach set key: " + k + " = " + testSet[testIndex]);
testIndex += 1;
});
is(testIndex, 2, "SimpleSet: foreach ran correct number of times");
ok(m.has("test2"), "SimpleSet: maplike has should return true");
is(m.delete("test2"), true, "SimpleSet: maplike deletion should return true");
is(m.size, 1, "SimpleSet: size should be 1");
iterable = false;
for (var e of m) {
iterable = true;
is(e, "test", "SimpleSet: iterable first array element should be key");
}
is(m[Symbol.iterator].length, 0, "SimpleSet: @@iterator symbol is correct length");
is(m[Symbol.iterator].name, "[Symbol.iterator]", "SimpleSet: @@iterator symbol has correct name");
ok(iterable, "SimpleSet: @@iterator symbol resolved correctly");
for (var k of m.keys()) {
is(k, "test", "SimpleSet: first keys element should be 'test'");
}
for (var v of m.values()) {
is(v, "test", "SimpleSet: first values elements should be 'test'");
}
for (var e of m.entries()) {
is(e[0], "test", "SimpleSet: Entries first array element should be 'test'");
is(e[1], "test", "SimpleSet: Entries second array element should be 'test'");
}
m.clear();
is(m.size, 0, "SimpleSet: size should be 0 after clear");
// Map convenience function test
info("Testing map convenience functions");
m = new TestInterfaceMaplike();
ok(m, "MapConvenience: got a TestInterfaceMaplike object");
is(m.size, 0, "MapConvenience: size should be zero");
ok(!m.hasInternal("test"), "MapConvenience: maplike hasInternal should return false");
m.setInternal("test", 1);
is(m.size, 1, "MapConvenience: size should be 1");
ok(m.hasInternal("test"), "MapConvenience: maplike hasInternal should return true");
is(m.get("test"), 1, "MapConvenience: maplike get should return value entered");
m2 = m.setInternal("test2", 2);
is(m.size, 2, "size should be 2");
ok(m.hasInternal("test2"), "MapConvenience: maplike hasInternal should return true");
is(m.get("test2"), 2, "MapConvenience: maplike get should return value entered");
is(m.deleteInternal("test2"), true, "MapConvenience: maplike deleteInternal should return true");
is(m.size, 1, "MapConvenience: size should be 1");
m.clearInternal();
is(m.size, 0, "MapConvenience: size should be 0 after clearInternal");
// Map convenience function test using objects and readonly
info("Testing Map convenience function test using objects and readonly");
m = new TestInterfaceMaplikeObject();
ok(m, "ReadOnlyMapConvenience: got a TestInterfaceMaplikeObject object");
is(m.size, 0, "ReadOnlyMapConvenience: size should be zero");
is(m["set"], undefined, "ReadOnlyMapConvenience: readonly map, should be no set function");
is(m["clear"], undefined, "ReadOnlyMapConvenience: readonly map, should be no clear function");
is(m["delete"], undefined, "ReadOnlyMapConvenience: readonly map, should be no delete function");
ok(!m.hasInternal("test"), "ReadOnlyMapConvenience: maplike hasInternal should return false");
m.setInternal("test");
is(m.size, 1, "size should be 1");
ok(m.hasInternal("test"), "ReadOnlyMapConvenience: maplike hasInternal should return true");
m2 = m.setInternal("test2");
is(m.size, 2, "size should be 2");
ok(m.hasInternal("test2"), "ReadOnlyMapConvenience: maplike hasInternal should return true");
is(m.deleteInternal("test2"), true, "ReadOnlyMapConvenience: maplike deleteInternal should return true");
is(m.size, 1, "ReadOnlyMapConvenience: size should be 1");
m.clearInternal();
is(m.size, 0, "ReadOnlyMapConvenience: size should be 0 after clearInternal");
// JS implemented map creation convenience function test
info("JSMapConvenience: Testing JS implemented map creation convenience functions");
m = new TestInterfaceJSMaplike();
ok(m, "JSMapConvenience: got a TestInterfaceJSMaplike object");
is(m.size, 0, "JSMapConvenience: size should be zero");
ok(!m.has("test"), "JSMapConvenience: maplike has should return false");
m.setInternal("test", 1);
is(m.size, 1, "JSMapConvenience: size should be 1");
ok(m.has("test"), "JSMapConvenience: maplike has should return true");
is(m.get("test"), 1, "JSMapConvenience: maplike get should return value entered");
m2 = m.setInternal("test2", 2);
is(m.size, 2, "JSMapConvenience: size should be 2");
ok(m.has("test2"), "JSMapConvenience: maplike has should return true");
is(m.get("test2"), 2, "JSMapConvenience: maplike get should return value entered");
is(m.deleteInternal("test2"), true, "JSMapConvenience: maplike deleteInternal should return true");
is(m.size, 1, "JSMapConvenience: size should be 1");
for (var k of m.keys()) {
is(k, "test", "JSMapConvenience: first keys element should be 'test'");
}
for (var v of m.values()) {
is(v, 1, "JSMapConvenience: first values elements should be 1");
}
for (var e of m.entries()) {
is(e[0], "test", "JSMapConvenience: entries first array element should be 'test'");
is(e[1], 1, "JSMapConvenience: entries second array element should be 1");
}
m.clearInternal();
is(m.size, 0, "JSMapConvenience: size should be 0 after clearInternal");
// Test this override for forEach
info("ForEachThisOverride: Testing this override for forEach");
m = new TestInterfaceMaplike();
m.set("test", 1);
m.forEach(function(v, k, o) {
"use strict";
is(o, m, "ForEachThisOverride: foreach obj is correct");
is(this, 5, "ForEachThisOverride: 'this' value should be correct");
}, 5);
// Test defaulting arguments on maplike to undefined
info("MapArgsDefault: Testing maplike defaulting arguments to undefined");
m = new TestInterfaceMaplike();
m.set();
is(m.size, 1, "MapArgsDefault: should have 1 entry");
m.forEach(function(v, k) {
"use strict";
is(typeof k, "string", "MapArgsDefault: key is a string");
is(k, "undefined", "MapArgsDefault: key is the string undefined");
is(v, 0, "MapArgsDefault: value is 0");
});
is(m.get(), 0, "MapArgsDefault: no argument to get() returns correct value");
m.delete();
is(m.size, 0, "MapArgsDefault: should have 0 entries");
// Test defaulting arguments on setlike to undefined
info("SetArgsDefault: Testing setlike defaulting arguments to undefined");
m = new TestInterfaceSetlike();
m.add();
is(m.size, 1, "SetArgsDefault: should have 1 entry");
m.forEach(function(v, k) {
"use strict";
is(typeof k, "string", "SetArgsDefault: key is a string");
is(k, "undefined", "SetArgsDefault: key is the string undefined");
});
m.delete();
is(m.size, 0, "SetArgsDefault: should have 0 entries");
SimpleTest.finish();
});
</script>
</body>
</html>

View File

@ -0,0 +1,68 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1123516
-->
<window title="Mozilla Bug 1123516"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<iframe id="t"></iframe>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml">
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1123516"
target="_blank">Mozilla Bug 1123516</a>
</body>
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
/** Test for Bug 1123516 **/
const Cu = Components.utils;
function doTest() {
var win = $("t").contentWindow;
var sandbox = Components.utils.Sandbox(win, { sandboxPrototype: win });
is(sandbox._content, undefined, "_content does nothing over Xray");
// Test cross-compartment usage of maplike/setlike WebIDL structures.
SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]}, function() {
try {
var maplike = Components.utils.evalInSandbox("var m = new TestInterfaceMaplike(); m;", sandbox);
maplike.set("test2", 2);
is(maplike.get("test2"), 2, "Should be able to create and use maplike/setlike across compartments");
var test = Components.utils.evalInSandbox("m.get('test2');", sandbox);
is(test, 2, "Maplike/setlike should still work in original compartment");
is(maplike.size, 1, "Testing size retrieval across compartments");
} catch(e) {
ok(false, "Shouldn't throw when working with cross-compartment maplike/setlike interfaces " + e)
};
try {
var setlike = Components.utils.evalInSandbox("var m = new TestInterfaceSetlikeNode(); m.add(document.documentElement); m;", sandbox);
is(TestInterfaceSetlikeNode.prototype.has.call(setlike, win.document.documentElement), true,
"Cross-compartment unwrapping/comparison has works");
// TODO: Should throw until iterators are handled by Xrays, Bug 1023984
try {
var e = TestInterfaceSetlikeNode.prototype.keys.call(setlike);
ok(false, "Calling iterators via xrays should fail");
} catch(e) {
ok(true, "Calling iterators via xrays should fail");
}
setlike.forEach((v,k,t) => { is(v, win.document.documentElement, "Cross-compartment forEach works"); });
TestInterfaceSetlikeNode.prototype.forEach.call(setlike,
(v,k,t) => { is(v, win.document.documentElement, "Cross-compartment forEach works"); });
is(TestInterfaceSetlikeNode.prototype.delete.call(setlike, win.document.documentElement), true,
"Cross-compartment unwrapping/comparison delete works");
} catch(e) {
ok(false, "Shouldn't throw when working with cross-compartment maplike/setlike interfaces " + e)
};
SimpleTest.finish();
});
}
SimpleTest.waitForExplicitFinish();
addLoadEvent(doTest);
]]>
</script>
</window>

View File

@ -7,7 +7,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=741267
<window title="Mozilla Bug 741267"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script type="application/javascript">
</script>
<iframe id="t"></iframe>
<!-- test results are displayed in the html:body -->

View File

@ -0,0 +1,47 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
[Constructor(),
Pref="dom.expose_test_interfaces"]
interface TestInterfaceMaplike {
maplike<DOMString, long>;
void setInternal(DOMString aKey, long aValue);
void clearInternal();
boolean deleteInternal(DOMString aKey);
boolean hasInternal(DOMString aKey);
};
[Constructor(),
Pref="dom.expose_test_interfaces"]
interface TestInterfaceMaplikeObject {
readonly maplike<DOMString, TestInterfaceMaplike>;
void setInternal(DOMString aKey);
void clearInternal();
boolean deleteInternal(DOMString aKey);
boolean hasInternal(DOMString aKey);
};
[Pref="dom.expose_test_interfaces",
JSImplementation="@mozilla.org/dom/test-interface-js-maplike;1",
Constructor()]
interface TestInterfaceJSMaplike {
readonly maplike<DOMString, long>;
void setInternal(DOMString aKey, long aValue);
void clearInternal();
boolean deleteInternal(DOMString aKey);
};
[Constructor(),
Pref="dom.expose_test_interfaces"]
interface TestInterfaceSetlike {
setlike<DOMString>;
};
[Constructor(),
Pref="dom.expose_test_interfaces"]
interface TestInterfaceSetlikeNode {
setlike<Node>;
};

View File

@ -643,7 +643,9 @@ WEBIDL_FILES += [
# We only expose our prefable test interfaces in debug builds, just to be on
# the safe side.
if CONFIG['MOZ_DEBUG']:
WEBIDL_FILES += ['TestInterfaceJS.webidl', 'TestInterfaceJSDictionaries.webidl']
WEBIDL_FILES += ['TestInterfaceJS.webidl',
'TestInterfaceJSDictionaries.webidl',
'TestInterfaceJSMaplikeSetlike.webidl']
if CONFIG['MOZ_B2G_BT']:
if CONFIG['MOZ_B2G_BT_API_V1']:

View File

@ -449,6 +449,7 @@
#ifdef MOZ_DEBUG
@BINPATH@/components/TestInterfaceJS.js
@BINPATH@/components/TestInterfaceJS.manifest
@BINPATH@/components/TestInterfaceJSMaplike.js
#endif
@BINPATH@/components/nsAsyncShutdown.manifest