зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1683281 - Part 6-2: C++ helper function for ObservableArray; r=peterv
Generate helper function for C++ to perform operations on backing objects for each observable array attribute, - ElementAt: Get the item at that index. Throw error if fail to get the element. - ReplaceElementAt: Replace the item at the index, this will also trigger OnDelete and OnSet callback. Throw error if fail to replace the element. - AppendElement: Append one element to the end of the array, this will also trigger OnSet callback. Throw error if fail to append the element. - RemoveLastElement: Remove the element at the last index, this will also trigger OnDelete callback. Throw error if fail to remove the element. - Length: Get the number of the indexd value. Throw error if fail to get the length. Depends on D113728 Differential Revision: https://phabricator.services.mozilla.com/D113862
This commit is contained in:
Родитель
d70b01fd77
Коммит
74c8ac1951
|
@ -16291,6 +16291,7 @@ class CGDescriptor(CGThing):
|
|||
cgThings.append(
|
||||
CGObservableArrayProxyHandlerGenerator(descriptor, m)
|
||||
)
|
||||
cgThings.append(CGObservableArrayHelperGenerator(descriptor, m))
|
||||
if m.getExtendedAttribute("Unscopable"):
|
||||
assert not m.isStatic()
|
||||
unscopableNames.append(m.identifier.name)
|
||||
|
@ -18172,11 +18173,19 @@ class CGBindingRoot(CGThing):
|
|||
|
||||
return any(hasCrossOriginProperty(m) for m in desc.interface.members)
|
||||
|
||||
def descriptorHasObservableArrayTypes(desc):
|
||||
def hasObservableArrayTypes(m):
|
||||
return m.isAttr() and m.type.isObservableArray()
|
||||
|
||||
return any(hasObservableArrayTypes(m) for m in desc.interface.members)
|
||||
|
||||
bindingDeclareHeaders["mozilla/dom/RemoteObjectProxy.h"] = any(
|
||||
descriptorHasCrossOriginProperties(d) for d in descriptors
|
||||
)
|
||||
bindingDeclareHeaders["jsapi.h"] = any(
|
||||
descriptorHasCrossOriginProperties(d) for d in descriptors
|
||||
descriptorHasCrossOriginProperties(d)
|
||||
or descriptorHasObservableArrayTypes(d)
|
||||
for d in descriptors
|
||||
)
|
||||
bindingDeclareHeaders["js/TypeDecls.h"] = not bindingDeclareHeaders["jsapi.h"]
|
||||
bindingDeclareHeaders["js/RootingAPI.h"] = not bindingDeclareHeaders["jsapi.h"]
|
||||
|
@ -22000,7 +22009,7 @@ class CGIterableMethodGenerator(CGGeneric):
|
|||
)
|
||||
|
||||
|
||||
def getObservableArrayBackingObject(descriptor, attr):
|
||||
def getObservableArrayBackingObject(descriptor, attr, errorReturn="return false;\n"):
|
||||
"""
|
||||
Generate code to get/create a JS backing list for an observableArray attribute
|
||||
from the declaration slot.
|
||||
|
@ -22014,7 +22023,7 @@ def getObservableArrayBackingObject(descriptor, attr):
|
|||
bool created = false;
|
||||
if (!GetObservableArrayBackingObject(cx, obj, ${slot},
|
||||
&backingObj, &created, ${namespace}::ObservableArrayProxyHandler::getInstance())) {
|
||||
return false;
|
||||
$*{errorReturn}
|
||||
}
|
||||
if (created) {
|
||||
PreserveWrapper(self);
|
||||
|
@ -22025,6 +22034,7 @@ def getObservableArrayBackingObject(descriptor, attr):
|
|||
""",
|
||||
namespace=toBindingNamespace(MakeNativeName(attr.identifier.name)),
|
||||
slot=memberReservedSlot(attr, descriptor),
|
||||
errorReturn=errorReturn,
|
||||
selfType=descriptor.nativeType,
|
||||
)
|
||||
|
||||
|
@ -22363,6 +22373,284 @@ class CGObservableArraySetterGenerator(CGGeneric):
|
|||
)
|
||||
|
||||
|
||||
class CGObservableArrayHelperFunctionGenerator(CGHelperFunctionGenerator):
|
||||
"""
|
||||
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
|
||||
MethodBodyGenerator to generate the body.
|
||||
"""
|
||||
|
||||
class MethodBodyGenerator(CGThing):
|
||||
"""
|
||||
Creates methods body for observable array attribute. 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,
|
||||
attr,
|
||||
methodName,
|
||||
helperGenerator,
|
||||
needsIndexArg,
|
||||
):
|
||||
assert attr.isAttr()
|
||||
assert attr.type.isObservableArray()
|
||||
|
||||
CGThing.__init__(self)
|
||||
self.helperGenerator = helperGenerator
|
||||
self.cgRoot = CGList([])
|
||||
|
||||
self.cgRoot.append(
|
||||
CGGeneric(
|
||||
getObservableArrayBackingObject(
|
||||
descriptor,
|
||||
attr,
|
||||
dedent(
|
||||
"""
|
||||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||||
return%s;
|
||||
"""
|
||||
% helperGenerator.getDefaultRetval()
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# 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, methodName)
|
||||
except AttributeError:
|
||||
raise TypeError(
|
||||
"Missing observable array method definition '%s'" % methodName
|
||||
)
|
||||
# Method generator returns tuple, containing:
|
||||
#
|
||||
# - a list of CGThings representing setup code for preparing to call
|
||||
# the JS API function
|
||||
# - JS API function name
|
||||
# - a list of arguments needed for the JS API function we're calling
|
||||
# - a list of CGThings representing code needed before return.
|
||||
(setupCode, funcName, arguments, returnCode) = methodGenerator()
|
||||
|
||||
# Append the list of setup code CGThings
|
||||
self.cgRoot.append(CGList(setupCode))
|
||||
# Create the JS API call
|
||||
if needsIndexArg:
|
||||
arguments.insert(0, "aIndex")
|
||||
self.cgRoot.append(
|
||||
CGWrapper(
|
||||
CGGeneric(
|
||||
fill(
|
||||
"""
|
||||
aRv.MightThrowJSException();
|
||||
if (!${funcName}(${args})) {
|
||||
aRv.StealExceptionFromJSContext(cx);
|
||||
return${retval};
|
||||
}
|
||||
""",
|
||||
funcName=funcName,
|
||||
args=", ".join(["cx", "backingObj"] + arguments),
|
||||
retval=helperGenerator.getDefaultRetval(),
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
# Append code before return
|
||||
self.cgRoot.append(CGList(returnCode))
|
||||
|
||||
def elementat(self):
|
||||
setupCode = []
|
||||
if not self.helperGenerator.needsScopeBody():
|
||||
setupCode.append(CGGeneric("JS::Rooted<JS::Value> result(cx);\n"))
|
||||
returnCode = [
|
||||
CGGeneric(
|
||||
fill(
|
||||
"""
|
||||
if (result.isUndefined()) {
|
||||
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
|
||||
return${retval};
|
||||
}
|
||||
""",
|
||||
retval=self.helperGenerator.getDefaultRetval(),
|
||||
)
|
||||
)
|
||||
]
|
||||
return (setupCode, "JS_GetElement", ["&result"], returnCode)
|
||||
|
||||
def replaceelementat(self):
|
||||
setupCode = [
|
||||
CGGeneric(
|
||||
fill(
|
||||
"""
|
||||
uint32_t length;
|
||||
aRv.MightThrowJSException();
|
||||
if (!JS::GetArrayLength(cx, backingObj, &length)) {
|
||||
aRv.StealExceptionFromJSContext(cx);
|
||||
return${retval};
|
||||
}
|
||||
if (aIndex > length) {
|
||||
aRv.ThrowRangeError("Invalid index");
|
||||
return${retval};
|
||||
}
|
||||
""",
|
||||
retval=self.helperGenerator.getDefaultRetval(),
|
||||
)
|
||||
)
|
||||
]
|
||||
return (setupCode, "JS_SetElement", ["argv[0]"], [])
|
||||
|
||||
def appendelement(self):
|
||||
setupCode = [
|
||||
CGGeneric(
|
||||
fill(
|
||||
"""
|
||||
uint32_t length;
|
||||
aRv.MightThrowJSException();
|
||||
if (!JS::GetArrayLength(cx, backingObj, &length)) {
|
||||
aRv.StealExceptionFromJSContext(cx);
|
||||
return${retval};
|
||||
}
|
||||
""",
|
||||
retval=self.helperGenerator.getDefaultRetval(),
|
||||
)
|
||||
)
|
||||
]
|
||||
return (setupCode, "JS_SetElement", ["length", "argv[0]"], [])
|
||||
|
||||
def removelastelement(self):
|
||||
setupCode = [
|
||||
CGGeneric(
|
||||
fill(
|
||||
"""
|
||||
uint32_t length;
|
||||
aRv.MightThrowJSException();
|
||||
if (!JS::GetArrayLength(cx, backingObj, &length)) {
|
||||
aRv.StealExceptionFromJSContext(cx);
|
||||
return${retval};
|
||||
}
|
||||
if (length == 0) {
|
||||
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
|
||||
return${retval};
|
||||
}
|
||||
""",
|
||||
retval=self.helperGenerator.getDefaultRetval(),
|
||||
)
|
||||
)
|
||||
]
|
||||
return (setupCode, "JS::SetArrayLength", ["length - 1"], [])
|
||||
|
||||
def length(self):
|
||||
return (
|
||||
[CGGeneric("uint32_t retVal;\n")],
|
||||
"JS::GetArrayLength",
|
||||
["&retVal"],
|
||||
[],
|
||||
)
|
||||
|
||||
def define(self):
|
||||
return self.cgRoot.define()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
descriptor,
|
||||
attr,
|
||||
name,
|
||||
returnType=BuiltinTypes[IDLBuiltinType.Types.void],
|
||||
needsResultConversion=True,
|
||||
needsIndexArg=False,
|
||||
needsValueArg=False,
|
||||
):
|
||||
assert attr.isAttr()
|
||||
assert attr.type.isObservableArray()
|
||||
self.attr = attr
|
||||
self.needsIndexArg = needsIndexArg
|
||||
|
||||
args = []
|
||||
if needsValueArg:
|
||||
args.append(FakeArgument(attr.type.inner, "aValue"))
|
||||
|
||||
CGHelperFunctionGenerator.__init__(
|
||||
self,
|
||||
descriptor,
|
||||
name,
|
||||
args,
|
||||
returnType,
|
||||
needsResultConversion,
|
||||
)
|
||||
|
||||
def getArgs(self, returnType, argList):
|
||||
if self.needsIndexArg:
|
||||
argList = [
|
||||
FakeArgument(BuiltinTypes[IDLBuiltinType.Types.unsigned_long], "aIndex")
|
||||
] + argList
|
||||
return CGHelperFunctionGenerator.getArgs(self, returnType, argList)
|
||||
|
||||
def getCall(self):
|
||||
return CGObservableArrayHelperFunctionGenerator.MethodBodyGenerator(
|
||||
self.descriptorProvider,
|
||||
self.attr,
|
||||
self.name.lower(),
|
||||
self,
|
||||
self.needsIndexArg,
|
||||
).define()
|
||||
|
||||
|
||||
class CGObservableArrayHelperGenerator(CGNamespace):
|
||||
"""
|
||||
Declares and defines convenience methods for accessing backing object for
|
||||
observable array type. Generates function signatures, un/packs
|
||||
backing objects from slot, etc.
|
||||
"""
|
||||
|
||||
def __init__(self, descriptor, attr):
|
||||
assert attr.isAttr()
|
||||
assert attr.type.isObservableArray()
|
||||
|
||||
namespace = "%sHelpers" % MakeNativeName(attr.identifier.name)
|
||||
helpers = [
|
||||
CGObservableArrayHelperFunctionGenerator(
|
||||
descriptor,
|
||||
attr,
|
||||
"ElementAt",
|
||||
returnType=attr.type.inner,
|
||||
needsIndexArg=True,
|
||||
),
|
||||
CGObservableArrayHelperFunctionGenerator(
|
||||
descriptor,
|
||||
attr,
|
||||
"ReplaceElementAt",
|
||||
needsIndexArg=True,
|
||||
needsValueArg=True,
|
||||
),
|
||||
CGObservableArrayHelperFunctionGenerator(
|
||||
descriptor,
|
||||
attr,
|
||||
"AppendElement",
|
||||
needsValueArg=True,
|
||||
),
|
||||
CGObservableArrayHelperFunctionGenerator(
|
||||
descriptor,
|
||||
attr,
|
||||
"RemoveLastElement",
|
||||
),
|
||||
CGObservableArrayHelperFunctionGenerator(
|
||||
descriptor,
|
||||
attr,
|
||||
"Length",
|
||||
returnType=BuiltinTypes[IDLBuiltinType.Types.unsigned_long],
|
||||
needsResultConversion=False,
|
||||
),
|
||||
]
|
||||
CGNamespace.__init__(self, namespace, CGList(helpers, "\n"))
|
||||
|
||||
|
||||
class GlobalGenRoots:
|
||||
"""
|
||||
Roots for global codegen.
|
||||
|
|
|
@ -131,4 +131,95 @@ void TestInterfaceObservableArray::OnDeleteObservableArrayInterface(
|
|||
}
|
||||
}
|
||||
|
||||
bool TestInterfaceObservableArray::BooleanElementAtInternal(uint32_t aIndex,
|
||||
ErrorResult& aRv) {
|
||||
return TestInterfaceObservableArray_Binding::ObservableArrayBooleanHelpers::
|
||||
ElementAt(this, aIndex, aRv);
|
||||
}
|
||||
|
||||
void TestInterfaceObservableArray::ObjectElementAtInternal(
|
||||
JSContext* aCx, uint32_t aIndex, JS::MutableHandle<JSObject*> aValue,
|
||||
ErrorResult& aRv) {
|
||||
TestInterfaceObservableArray_Binding::ObservableArrayObjectHelpers::ElementAt(
|
||||
this, aCx, aIndex, aValue, aRv);
|
||||
}
|
||||
|
||||
already_AddRefed<TestInterfaceObservableArray>
|
||||
TestInterfaceObservableArray::InterfaceElementAtInternal(uint32_t aIndex,
|
||||
ErrorResult& aRv) {
|
||||
return TestInterfaceObservableArray_Binding::ObservableArrayInterfaceHelpers::
|
||||
ElementAt(this, aIndex, aRv);
|
||||
}
|
||||
|
||||
void TestInterfaceObservableArray::BooleanReplaceElementAtInternal(
|
||||
uint32_t aIndex, bool aValue, ErrorResult& aRv) {
|
||||
TestInterfaceObservableArray_Binding::ObservableArrayBooleanHelpers::
|
||||
ReplaceElementAt(this, aIndex, aValue, aRv);
|
||||
}
|
||||
|
||||
void TestInterfaceObservableArray::ObjectReplaceElementAtInternal(
|
||||
JSContext* aCx, uint32_t aIndex, JS::Handle<JSObject*> aValue,
|
||||
ErrorResult& aRv) {
|
||||
TestInterfaceObservableArray_Binding::ObservableArrayObjectHelpers::
|
||||
ReplaceElementAt(this, aIndex, aValue, aRv);
|
||||
}
|
||||
|
||||
void TestInterfaceObservableArray::InterfaceReplaceElementAtInternal(
|
||||
uint32_t aIndex, TestInterfaceObservableArray& aValue, ErrorResult& aRv) {
|
||||
TestInterfaceObservableArray_Binding::ObservableArrayInterfaceHelpers::
|
||||
ReplaceElementAt(this, aIndex, aValue, aRv);
|
||||
}
|
||||
|
||||
void TestInterfaceObservableArray::BooleanAppendElementInternal(
|
||||
bool aValue, ErrorResult& aRv) {
|
||||
TestInterfaceObservableArray_Binding::ObservableArrayBooleanHelpers::
|
||||
AppendElement(this, aValue, aRv);
|
||||
}
|
||||
|
||||
void TestInterfaceObservableArray::ObjectAppendElementInternal(
|
||||
JSContext* aCx, JS::Handle<JSObject*> aValue, ErrorResult& aRv) {
|
||||
TestInterfaceObservableArray_Binding::ObservableArrayObjectHelpers::
|
||||
AppendElement(this, aValue, aRv);
|
||||
}
|
||||
|
||||
void TestInterfaceObservableArray::InterfaceAppendElementInternal(
|
||||
TestInterfaceObservableArray& aValue, ErrorResult& aRv) {
|
||||
TestInterfaceObservableArray_Binding::ObservableArrayInterfaceHelpers::
|
||||
AppendElement(this, aValue, aRv);
|
||||
}
|
||||
|
||||
void TestInterfaceObservableArray::BooleanRemoveLastElementInternal(
|
||||
ErrorResult& aRv) {
|
||||
TestInterfaceObservableArray_Binding::ObservableArrayBooleanHelpers::
|
||||
RemoveLastElement(this, aRv);
|
||||
}
|
||||
|
||||
void TestInterfaceObservableArray::ObjectRemoveLastElementInternal(
|
||||
ErrorResult& aRv) {
|
||||
TestInterfaceObservableArray_Binding::ObservableArrayObjectHelpers::
|
||||
RemoveLastElement(this, aRv);
|
||||
}
|
||||
|
||||
void TestInterfaceObservableArray::InterfaceRemoveLastElementInternal(
|
||||
ErrorResult& aRv) {
|
||||
TestInterfaceObservableArray_Binding::ObservableArrayInterfaceHelpers::
|
||||
RemoveLastElement(this, aRv);
|
||||
}
|
||||
|
||||
uint32_t TestInterfaceObservableArray::BooleanLengthInternal(ErrorResult& aRv) {
|
||||
return TestInterfaceObservableArray_Binding::ObservableArrayBooleanHelpers::
|
||||
Length(this, aRv);
|
||||
}
|
||||
|
||||
uint32_t TestInterfaceObservableArray::ObjectLengthInternal(ErrorResult& aRv) {
|
||||
return TestInterfaceObservableArray_Binding::ObservableArrayObjectHelpers::
|
||||
Length(this, aRv);
|
||||
}
|
||||
|
||||
uint32_t TestInterfaceObservableArray::InterfaceLengthInternal(
|
||||
ErrorResult& aRv) {
|
||||
return TestInterfaceObservableArray_Binding::ObservableArrayInterfaceHelpers::
|
||||
Length(this, aRv);
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
|
|
@ -65,6 +65,36 @@ class TestInterfaceObservableArray final : public nsISupports,
|
|||
void OnDeleteObservableArrayInterface(TestInterfaceObservableArray* aValue,
|
||||
uint32_t aIndex, ErrorResult& aRv);
|
||||
|
||||
bool BooleanElementAtInternal(uint32_t aIndex, ErrorResult& aRv);
|
||||
void ObjectElementAtInternal(JSContext* aCx, uint32_t aIndex,
|
||||
JS::MutableHandle<JSObject*> aValue,
|
||||
ErrorResult& aRv);
|
||||
already_AddRefed<TestInterfaceObservableArray> InterfaceElementAtInternal(
|
||||
uint32_t aIndex, ErrorResult& aRv);
|
||||
|
||||
void BooleanReplaceElementAtInternal(uint32_t aIndex, bool aValue,
|
||||
ErrorResult& aRv);
|
||||
void ObjectReplaceElementAtInternal(JSContext* aCx, uint32_t aIndex,
|
||||
JS::Handle<JSObject*> aValue,
|
||||
ErrorResult& aRv);
|
||||
void InterfaceReplaceElementAtInternal(uint32_t aIndex,
|
||||
TestInterfaceObservableArray& aValue,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void BooleanAppendElementInternal(bool aValue, ErrorResult& aRv);
|
||||
void ObjectAppendElementInternal(JSContext* aCx, JS::Handle<JSObject*> aValue,
|
||||
ErrorResult& aRv);
|
||||
void InterfaceAppendElementInternal(TestInterfaceObservableArray& aValue,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void BooleanRemoveLastElementInternal(ErrorResult& aRv);
|
||||
void ObjectRemoveLastElementInternal(ErrorResult& aRv);
|
||||
void InterfaceRemoveLastElementInternal(ErrorResult& aRv);
|
||||
|
||||
uint32_t BooleanLengthInternal(ErrorResult& aRv);
|
||||
uint32_t ObjectLengthInternal(ErrorResult& aRv);
|
||||
uint32_t InterfaceLengthInternal(ErrorResult& aRv);
|
||||
|
||||
private:
|
||||
explicit TestInterfaceObservableArray(
|
||||
nsPIDOMWindowInner* aParent, const ObservableArrayCallbacks& aCallbacks);
|
||||
|
|
|
@ -96,4 +96,6 @@ skip-if = (debug == false || bits == 32) # Large ArrayBuffers are only supported
|
|||
skip-if = debug == false
|
||||
[test_observablearray_proxyhandler.html]
|
||||
skip-if = debug == false
|
||||
[test_observablearray_helper.html]
|
||||
skip-if = debug == false
|
||||
[test_large_imageData.html]
|
||||
|
|
|
@ -0,0 +1,376 @@
|
|||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test Helpers of Observable Array Type</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
/* global TestInterfaceObservableArray */
|
||||
|
||||
add_task(async function init() {
|
||||
await SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]});
|
||||
});
|
||||
|
||||
add_task(function testObservableArray_helper_elementAt() {
|
||||
let m = new TestInterfaceObservableArray();
|
||||
|
||||
[
|
||||
// [values, property, helper]
|
||||
[[true, false], "observableArrayBoolean", m.booleanElementAtInternal.bind(m)],
|
||||
[[new TestInterfaceObservableArray(), new TestInterfaceObservableArray()],
|
||||
"observableArrayInterface", m.interfaceElementAtInternal.bind(m)],
|
||||
[[{property: "test"}, {property: 2}], "observableArrayObject",
|
||||
m.objectElementAtInternal.bind(m)],
|
||||
].forEach(function([values, property, helper]) {
|
||||
m[property] = values;
|
||||
|
||||
let t = m[property];
|
||||
ok(Array.isArray(t), "observable array should be an array type");
|
||||
is(t.length, values.length, "length of observable array");
|
||||
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
isDeeply(values[i], helper(i), `check index ${i}`);
|
||||
}
|
||||
|
||||
SimpleTest.doesThrow(() => {
|
||||
helper(values.length);
|
||||
}, `getting element outside the range should throw`);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function testObservableArray_helper_replaceElementAt() {
|
||||
let setCallbackCount = 0;
|
||||
let deleteCallbackCount = 0;
|
||||
let setCallbackTests = null;
|
||||
let deleteCallbackTests = null;
|
||||
|
||||
let m = new TestInterfaceObservableArray({
|
||||
setBooleanCallback(value, index) {
|
||||
setCallbackCount++;
|
||||
if (typeof setCallbackTests === 'function') {
|
||||
setCallbackTests(value, index);
|
||||
}
|
||||
},
|
||||
deleteBooleanCallback(value, index) {
|
||||
deleteCallbackCount++;
|
||||
if (typeof deleteCallbackTests === 'function') {
|
||||
deleteCallbackTests(value, index);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
let b = m.observableArrayBoolean;
|
||||
ok(Array.isArray(b), "observable array should be an array type");
|
||||
is(b.length, 0, "length of observable array should be 0");
|
||||
|
||||
[
|
||||
// [index, value, shouldThrow]
|
||||
[b.length + 1, false, true],
|
||||
[b.length, false, false],
|
||||
[b.length + 1, false, false],
|
||||
[b.length + 1, true, false],
|
||||
].forEach(function([index, value, shouldThrow]) {
|
||||
// Initialize
|
||||
let oldValue = b[index];
|
||||
let oldLen = b.length;
|
||||
setCallbackCount = 0;
|
||||
deleteCallbackCount = 0;
|
||||
setCallbackTests = function(_value, _index) {
|
||||
info(`set callback for ${_index}`);
|
||||
is(_value, value, "setCallbackTests: test value argument");
|
||||
is(_index, index, "setCallbackTests: test index argument");
|
||||
};
|
||||
deleteCallbackTests = function(_value, _index) {
|
||||
info(`delete callback for ${_index}`);
|
||||
is(_value, oldValue, "deleteCallbackTests: test value argument");
|
||||
is(_index, index, "deleteCallbackTests: test index argument");
|
||||
};
|
||||
|
||||
// Test
|
||||
info(`setting value of property ${index} to ${value}`);
|
||||
try {
|
||||
m.booleanReplaceElementAtInternal(index, value);
|
||||
ok(!shouldThrow, `setting value should not throw`);
|
||||
} catch(e) {
|
||||
ok(shouldThrow, `setting value throws ${e}`);
|
||||
}
|
||||
is(setCallbackCount, shouldThrow ? 0 : 1, "setCallback count");
|
||||
is(deleteCallbackCount, (oldLen > index) ? 1 : 0, "deleteCallback count");
|
||||
is(b[index], shouldThrow ? oldValue : value, `property value`);
|
||||
is(b.length, shouldThrow ? oldLen : Math.max(oldLen, index + 1), `length of observable array`);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function testObservableArray_helper_replaceElementAt_callback_throw() {
|
||||
let setCallbackCount = 0;
|
||||
let deleteCallbackCount = 0;
|
||||
|
||||
let m = new TestInterfaceObservableArray({
|
||||
setBooleanCallback(value, index) {
|
||||
setCallbackCount++;
|
||||
if (value) {
|
||||
throw new Error("setBooleanCallback");
|
||||
}
|
||||
},
|
||||
deleteBooleanCallback(value, index) {
|
||||
deleteCallbackCount++;
|
||||
if (index < 2) {
|
||||
throw new Error("deleteBooleanCallback");
|
||||
}
|
||||
},
|
||||
});
|
||||
m.observableArrayBoolean = [false, false, false];
|
||||
|
||||
let b = m.observableArrayBoolean;
|
||||
ok(Array.isArray(b), "observable array should be an array type");
|
||||
is(b.length, 3, "length of observable array should be 3");
|
||||
|
||||
[
|
||||
// [index, value, shouldThrow]
|
||||
[b.length, true, true],
|
||||
[b.length, false, false],
|
||||
[b.length, true, true],
|
||||
[0, false, true],
|
||||
[0, true, true]
|
||||
].forEach(function([index, value, shouldThrow]) {
|
||||
// Initialize
|
||||
let oldValue = b[index];
|
||||
let oldLen = b.length;
|
||||
setCallbackCount = 0;
|
||||
deleteCallbackCount = 0;
|
||||
|
||||
// Test
|
||||
info(`setting value of property ${index} to ${value}`);
|
||||
try {
|
||||
m.booleanReplaceElementAtInternal(index, value);
|
||||
ok(!shouldThrow, `setting value should not throw`);
|
||||
} catch(e) {
|
||||
ok(shouldThrow, `setting value throws ${e}`);
|
||||
}
|
||||
is(setCallbackCount, (shouldThrow && index < 2) ? 0 : 1, "setCallback count");
|
||||
is(deleteCallbackCount, (oldLen > index) ? 1 : 0, "deleteCallback count");
|
||||
is(b[index], shouldThrow ? oldValue : value, "property value");
|
||||
is(b.length, shouldThrow ? oldLen : Math.max(oldLen, index + 1), `length of observable array`);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function testObservableArray_helper_appendElement() {
|
||||
let setCallbackCount = 0;
|
||||
let deleteCallbackCount = 0;
|
||||
let setCallbackTests = null;
|
||||
|
||||
let m = new TestInterfaceObservableArray({
|
||||
setBooleanCallback(value, index) {
|
||||
setCallbackCount++;
|
||||
if (typeof setCallbackTests === 'function') {
|
||||
setCallbackTests(value, index);
|
||||
}
|
||||
},
|
||||
deleteBooleanCallback(value, index) {
|
||||
deleteCallbackCount++;
|
||||
},
|
||||
});
|
||||
|
||||
let b = m.observableArrayBoolean;
|
||||
ok(Array.isArray(b), "observable array should be an array type");
|
||||
is(b.length, 0, "length of observable array should be 0");
|
||||
|
||||
[true, false, true, false].forEach(function(value) {
|
||||
// Initialize
|
||||
let oldLen = b.length;
|
||||
let index = oldLen;
|
||||
setCallbackCount = 0;
|
||||
deleteCallbackCount = 0;
|
||||
setCallbackTests = function(_value, _index) {
|
||||
info(`set callback for ${_index}`);
|
||||
is(_value, value, "setCallbackTests: test value argument");
|
||||
is(_index, index, "setCallbackTests: test index argument");
|
||||
};
|
||||
|
||||
// Test
|
||||
info(`append ${value}`);
|
||||
try {
|
||||
m.booleanAppendElementInternal(value);
|
||||
ok(true, `appending value should not throw`);
|
||||
} catch(e) {
|
||||
ok(false, `appending value throws ${e}`);
|
||||
}
|
||||
is(setCallbackCount, 1, "setCallback should be called");
|
||||
is(deleteCallbackCount, 0, "deleteCallback should not be called");
|
||||
is(b[index], value, `property value`);
|
||||
is(b.length, oldLen + 1, `length of observable array`);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function testObservableArray_helper_appendElement_callback_throw() {
|
||||
let setCallbackCount = 0;
|
||||
let deleteCallbackCount = 0;
|
||||
|
||||
let m = new TestInterfaceObservableArray({
|
||||
setBooleanCallback(value, index) {
|
||||
setCallbackCount++;
|
||||
if (value) {
|
||||
throw new Error("setBooleanCallback");
|
||||
}
|
||||
},
|
||||
deleteBooleanCallback(value, index) {
|
||||
deleteCallbackCount++;
|
||||
},
|
||||
});
|
||||
m.observableArrayBoolean = [false, false, false];
|
||||
|
||||
let b = m.observableArrayBoolean;
|
||||
ok(Array.isArray(b), "observable array should be an array type");
|
||||
is(b.length, 3, "length of observable array should be 3");
|
||||
|
||||
[true, false, true, false].forEach(function(value) {
|
||||
// Initialize
|
||||
let oldLen = b.length;
|
||||
let index = oldLen;
|
||||
let oldValue = b[index];
|
||||
let shouldThrow = value;
|
||||
setCallbackCount = 0;
|
||||
deleteCallbackCount = 0;
|
||||
|
||||
// Test
|
||||
info(`append ${value}`);
|
||||
try {
|
||||
m.booleanAppendElementInternal(value);
|
||||
ok(!shouldThrow, `appending value should not throw`);
|
||||
} catch(e) {
|
||||
ok(shouldThrow, `appending value throws ${e}`);
|
||||
}
|
||||
is(setCallbackCount, 1, "setCallback should be called");
|
||||
is(deleteCallbackCount, 0, "deleteCallback should not be called");
|
||||
is(b[index], shouldThrow ? oldValue : value, "property value");
|
||||
is(b.length, shouldThrow ? oldLen : oldLen + 1, `length of observable array`);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function testObservableArray_helper_removeLastElement() {
|
||||
let setCallbackCount = 0;
|
||||
let deleteCallbackCount = 0;
|
||||
let deleteCallbackTests = null;
|
||||
|
||||
let m = new TestInterfaceObservableArray({
|
||||
setBooleanCallback(value, index) {
|
||||
setCallbackCount++;
|
||||
},
|
||||
deleteBooleanCallback(value, index) {
|
||||
deleteCallbackCount++;
|
||||
if (typeof deleteCallbackTests === 'function') {
|
||||
deleteCallbackTests(value, index);
|
||||
}
|
||||
},
|
||||
});
|
||||
m.observableArrayBoolean = [true, false, true, false];
|
||||
|
||||
let b = m.observableArrayBoolean;
|
||||
ok(Array.isArray(b), "observable array should be an array type");
|
||||
is(b.length, 4, "length of observable array should be 4");
|
||||
|
||||
let oldValues = b.slice();
|
||||
while (oldValues.length) {
|
||||
// Initialize
|
||||
let oldValue = oldValues.pop();
|
||||
let index = oldValues.length;
|
||||
setCallbackCount = 0;
|
||||
deleteCallbackCount = 0;
|
||||
deleteCallbackTests = function(_value, _index) {
|
||||
info(`delete callback for ${_index}`);
|
||||
is(_value, oldValue, "deleteCallbackTests: test value argument");
|
||||
is(_index, index, "deleteCallbackTests: test index argument");
|
||||
};
|
||||
|
||||
// Test
|
||||
info(`remove last element`);
|
||||
try {
|
||||
m.booleanRemoveLastElementInternal();
|
||||
ok(true, `removing last element should not throw`);
|
||||
} catch(e) {
|
||||
ok(false, `removing last element throws ${e}`);
|
||||
}
|
||||
is(setCallbackCount, 0, "setCallback should not be called");
|
||||
is(deleteCallbackCount, 1, "deleteCallback should be called");
|
||||
isDeeply(b, oldValues, `property value`);
|
||||
is(b.length, oldValues.length, `length of observable array`);
|
||||
}
|
||||
|
||||
// test when array is empty
|
||||
setCallbackCount = 0;
|
||||
deleteCallbackCount = 0;
|
||||
SimpleTest.doesThrow(() => {
|
||||
m.booleanRemoveLastElementInternal();
|
||||
}, `removing last element should throw when array is empty`);
|
||||
is(setCallbackCount, 0, "setCallback should not be called");
|
||||
is(deleteCallbackCount, 0, "deleteCallback should not be called");
|
||||
is(b.length, 0, `length of observable array`);
|
||||
});
|
||||
|
||||
add_task(function testObservableArray_helper_removeLastElement_callback_throw() {
|
||||
let setCallbackCount = 0;
|
||||
let deleteCallbackCount = 0;
|
||||
|
||||
let m = new TestInterfaceObservableArray({
|
||||
setBooleanCallback(value, index) {
|
||||
setCallbackCount++;
|
||||
},
|
||||
deleteBooleanCallback(value, index) {
|
||||
deleteCallbackCount++;
|
||||
if (value) {
|
||||
throw new Error("deleteBooleanCallback");
|
||||
}
|
||||
},
|
||||
});
|
||||
m.observableArrayBoolean = [false, true, false, false];
|
||||
|
||||
let b = m.observableArrayBoolean;
|
||||
ok(Array.isArray(b), "observable array should be an array type");
|
||||
is(b.length, 4, "length of observable array should be 4");
|
||||
|
||||
let shouldThrow = false;
|
||||
while (!shouldThrow && b.length) {
|
||||
// Initialize
|
||||
let oldValues = b.slice();
|
||||
let oldLen = b.length;
|
||||
shouldThrow = oldValues[oldLen - 1];
|
||||
setCallbackCount = 0;
|
||||
deleteCallbackCount = 0;
|
||||
|
||||
// Test
|
||||
info(`remove last element`);
|
||||
try {
|
||||
m.booleanRemoveLastElementInternal();
|
||||
ok(!shouldThrow, `removing last element should not throw`);
|
||||
} catch(e) {
|
||||
ok(shouldThrow, `removing last element throws ${e}`);
|
||||
}
|
||||
is(setCallbackCount, 0, "setCallback should not be called");
|
||||
is(deleteCallbackCount, 1, "deleteCallback should be called");
|
||||
isDeeply(b, shouldThrow ? oldValues : oldValues.slice(0, oldLen - 1), `property value`);
|
||||
is(b.length, shouldThrow ? oldLen : oldLen - 1, `length of observable array`);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function testObservableArray_helper_length() {
|
||||
let m = new TestInterfaceObservableArray();
|
||||
let b = m.observableArrayBoolean;
|
||||
ok(Array.isArray(b), "observable array should be an array type");
|
||||
is(b.length, 0, "length of observable array");
|
||||
|
||||
[
|
||||
[false, true, false, true],
|
||||
[true, false],
|
||||
[false, true, false],
|
||||
].forEach(function(values) {
|
||||
m.observableArrayBoolean = values;
|
||||
is(b.length, m.booleanLengthInternal(), "length helper function");
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -27,4 +27,40 @@ interface TestInterfaceObservableArray {
|
|||
attribute ObservableArray<boolean> observableArrayBoolean;
|
||||
attribute ObservableArray<object> observableArrayObject;
|
||||
attribute ObservableArray<TestInterfaceObservableArray> observableArrayInterface;
|
||||
|
||||
// Tests for C++ helper function
|
||||
[Throws]
|
||||
boolean booleanElementAtInternal(unsigned long index);
|
||||
[Throws]
|
||||
TestInterfaceObservableArray interfaceElementAtInternal(unsigned long index);
|
||||
[Throws]
|
||||
object objectElementAtInternal(unsigned long index);
|
||||
|
||||
[Throws]
|
||||
void booleanReplaceElementAtInternal(unsigned long index, boolean value);
|
||||
[Throws]
|
||||
void interfaceReplaceElementAtInternal(unsigned long index, TestInterfaceObservableArray value);
|
||||
[Throws]
|
||||
void objectReplaceElementAtInternal(unsigned long index, object value);
|
||||
|
||||
[Throws]
|
||||
void booleanAppendElementInternal(boolean value);
|
||||
[Throws]
|
||||
void interfaceAppendElementInternal(TestInterfaceObservableArray value);
|
||||
[Throws]
|
||||
void objectAppendElementInternal(object value);
|
||||
|
||||
[Throws]
|
||||
void booleanRemoveLastElementInternal();
|
||||
[Throws]
|
||||
void interfaceRemoveLastElementInternal();
|
||||
[Throws]
|
||||
void objectRemoveLastElementInternal();
|
||||
|
||||
[Throws]
|
||||
unsigned long booleanLengthInternal();
|
||||
[Throws]
|
||||
unsigned long interfaceLengthInternal();
|
||||
[Throws]
|
||||
unsigned long objectLengthInternal();
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче