summaryrefslogtreecommitdiffstats
path: root/dom/bindings
diff options
context:
space:
mode:
Diffstat (limited to 'dom/bindings')
-rw-r--r--dom/bindings/AtomList.h25
-rw-r--r--dom/bindings/BindingCallContext.h66
-rw-r--r--dom/bindings/BindingDeclarations.h547
-rw-r--r--dom/bindings/BindingIPCUtils.h19
-rw-r--r--dom/bindings/BindingUtils.cpp4375
-rw-r--r--dom/bindings/BindingUtils.h3267
-rw-r--r--dom/bindings/Bindings.conf2131
-rw-r--r--dom/bindings/CallbackFunction.h55
-rw-r--r--dom/bindings/CallbackInterface.cpp35
-rw-r--r--dom/bindings/CallbackInterface.h50
-rw-r--r--dom/bindings/CallbackObject.cpp433
-rw-r--r--dom/bindings/CallbackObject.h664
-rw-r--r--dom/bindings/Codegen.py24278
-rw-r--r--dom/bindings/Configuration.py1235
-rw-r--r--dom/bindings/DOMExceptionNames.h53
-rw-r--r--dom/bindings/DOMJSClass.h622
-rw-r--r--dom/bindings/DOMJSProxyHandler.cpp331
-rw-r--r--dom/bindings/DOMJSProxyHandler.h163
-rw-r--r--dom/bindings/DOMString.h332
-rw-r--r--dom/bindings/ErrorIPCUtils.h106
-rw-r--r--dom/bindings/ErrorResult.h928
-rw-r--r--dom/bindings/Errors.msg97
-rw-r--r--dom/bindings/Exceptions.cpp759
-rw-r--r--dom/bindings/Exceptions.h64
-rw-r--r--dom/bindings/FakeString.h267
-rw-r--r--dom/bindings/GenerateCSS2PropertiesWebIDL.py104
-rw-r--r--dom/bindings/IterableIterator.cpp338
-rw-r--r--dom/bindings/IterableIterator.h435
-rw-r--r--dom/bindings/JSSlots.h41
-rw-r--r--dom/bindings/Makefile.in53
-rw-r--r--dom/bindings/NonRefcountedDOMObject.h37
-rw-r--r--dom/bindings/Nullable.h108
-rw-r--r--dom/bindings/ObservableArrayProxyHandler.cpp372
-rw-r--r--dom/bindings/ObservableArrayProxyHandler.h114
-rw-r--r--dom/bindings/PinnedStringId.h48
-rw-r--r--dom/bindings/PrimitiveConversions.h330
-rw-r--r--dom/bindings/ProxyHandlerUtils.h62
-rw-r--r--dom/bindings/Record.h85
-rw-r--r--dom/bindings/RemoteObjectProxy.cpp182
-rw-r--r--dom/bindings/RemoteObjectProxy.h204
-rw-r--r--dom/bindings/RootedDictionary.h41
-rw-r--r--dom/bindings/RootedOwningNonNull.h58
-rw-r--r--dom/bindings/RootedRecord.h28
-rw-r--r--dom/bindings/RootedRefPtr.h48
-rw-r--r--dom/bindings/RootedSequence.h28
-rw-r--r--dom/bindings/SimpleGlobalObject.cpp177
-rw-r--r--dom/bindings/SimpleGlobalObject.h96
-rw-r--r--dom/bindings/SpiderMonkeyInterface.h114
-rw-r--r--dom/bindings/ToJSValue.cpp123
-rw-r--r--dom/bindings/ToJSValue.h486
-rw-r--r--dom/bindings/TypedArray.h290
-rw-r--r--dom/bindings/UnionMember.h45
-rw-r--r--dom/bindings/WebIDLGlobalNameHash.cpp274
-rw-r--r--dom/bindings/WebIDLGlobalNameHash.h92
-rw-r--r--dom/bindings/XrayExpandoClass.h37
-rw-r--r--dom/bindings/crashtests/1010658-1.html16
-rw-r--r--dom/bindings/crashtests/1010658-2.html16
-rw-r--r--dom/bindings/crashtests/769464.html11
-rw-r--r--dom/bindings/crashtests/822340-1.html11
-rw-r--r--dom/bindings/crashtests/822340-2.html8
-rw-r--r--dom/bindings/crashtests/832899.html5
-rw-r--r--dom/bindings/crashtests/860551.html4
-rw-r--r--dom/bindings/crashtests/860591.html20
-rw-r--r--dom/bindings/crashtests/862092.html19
-rw-r--r--dom/bindings/crashtests/862610.html20
-rw-r--r--dom/bindings/crashtests/869038.html22
-rw-r--r--dom/bindings/crashtests/949940.html16
-rw-r--r--dom/bindings/crashtests/crashtests.list13
-rw-r--r--dom/bindings/crashtests/stringbuffer-USVString.html11
-rw-r--r--dom/bindings/docs/index.rst124
-rw-r--r--dom/bindings/mach_commands.py62
-rw-r--r--dom/bindings/moz.build204
-rw-r--r--dom/bindings/mozwebidlcodegen/__init__.py681
-rw-r--r--dom/bindings/mozwebidlcodegen/test/Child.webidl3
-rw-r--r--dom/bindings/mozwebidlcodegen/test/ExampleBinding.webidl3
-rw-r--r--dom/bindings/mozwebidlcodegen/test/Parent.webidl3
-rw-r--r--dom/bindings/mozwebidlcodegen/test/TestEvent.webidl13
-rw-r--r--dom/bindings/mozwebidlcodegen/test/python.ini4
-rw-r--r--dom/bindings/mozwebidlcodegen/test/test_mozwebidlcodegen.py297
-rw-r--r--dom/bindings/nsIScriptError.idl296
-rw-r--r--dom/bindings/nsScriptError.cpp531
-rw-r--r--dom/bindings/nsScriptError.h143
-rw-r--r--dom/bindings/nsScriptErrorWithStack.cpp188
-rw-r--r--dom/bindings/parser/README1
-rw-r--r--dom/bindings/parser/UPSTREAM1
-rw-r--r--dom/bindings/parser/WebIDL.py9139
-rw-r--r--dom/bindings/parser/runtests.py142
-rw-r--r--dom/bindings/parser/tests/test_any_null.py16
-rw-r--r--dom/bindings/parser/tests/test_argument_identifier_conflicts.py16
-rw-r--r--dom/bindings/parser/tests/test_argument_keywords.py22
-rw-r--r--dom/bindings/parser/tests/test_argument_novoid.py16
-rw-r--r--dom/bindings/parser/tests/test_arraybuffer.py95
-rw-r--r--dom/bindings/parser/tests/test_attr.py199
-rw-r--r--dom/bindings/parser/tests/test_attr_sequence_type.py77
-rw-r--r--dom/bindings/parser/tests/test_attributes_on_types.py567
-rw-r--r--dom/bindings/parser/tests/test_builtin_filename.py11
-rw-r--r--dom/bindings/parser/tests/test_builtins.py59
-rw-r--r--dom/bindings/parser/tests/test_bytestring.py125
-rw-r--r--dom/bindings/parser/tests/test_callback.py42
-rw-r--r--dom/bindings/parser/tests/test_callback_constructor.py84
-rw-r--r--dom/bindings/parser/tests/test_callback_interface.py103
-rw-r--r--dom/bindings/parser/tests/test_cereactions.py157
-rw-r--r--dom/bindings/parser/tests/test_conditional_dictionary_member.py128
-rw-r--r--dom/bindings/parser/tests/test_const.py96
-rw-r--r--dom/bindings/parser/tests/test_constructor.py596
-rw-r--r--dom/bindings/parser/tests/test_constructor_global.py69
-rw-r--r--dom/bindings/parser/tests/test_constructor_no_interface_object.py47
-rw-r--r--dom/bindings/parser/tests/test_deduplicate.py17
-rw-r--r--dom/bindings/parser/tests/test_dictionary.py875
-rw-r--r--dom/bindings/parser/tests/test_distinguishability.py421
-rw-r--r--dom/bindings/parser/tests/test_double_null.py16
-rw-r--r--dom/bindings/parser/tests/test_duplicate_qualifiers.py64
-rw-r--r--dom/bindings/parser/tests/test_empty_enum.py14
-rw-r--r--dom/bindings/parser/tests/test_empty_sequence_default_value.py54
-rw-r--r--dom/bindings/parser/tests/test_enum.py107
-rw-r--r--dom/bindings/parser/tests/test_enum_duplicate_values.py13
-rw-r--r--dom/bindings/parser/tests/test_error_colno.py24
-rw-r--r--dom/bindings/parser/tests/test_error_lineno.py38
-rw-r--r--dom/bindings/parser/tests/test_exposed_extended_attribute.py383
-rw-r--r--dom/bindings/parser/tests/test_extended_attributes.py128
-rw-r--r--dom/bindings/parser/tests/test_float_types.py145
-rw-r--r--dom/bindings/parser/tests/test_forward_decl.py15
-rw-r--r--dom/bindings/parser/tests/test_global_extended_attr.py129
-rw-r--r--dom/bindings/parser/tests/test_identifier_conflict.py45
-rw-r--r--dom/bindings/parser/tests/test_incomplete_parent.py18
-rw-r--r--dom/bindings/parser/tests/test_incomplete_types.py61
-rw-r--r--dom/bindings/parser/tests/test_interface.py459
-rw-r--r--dom/bindings/parser/tests/test_interface_const_identifier_conflicts.py17
-rw-r--r--dom/bindings/parser/tests/test_interface_identifier_conflicts_across_members.py168
-rw-r--r--dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py935
-rw-r--r--dom/bindings/parser/tests/test_interfacemixin.py534
-rw-r--r--dom/bindings/parser/tests/test_lenientSetter.py84
-rw-r--r--dom/bindings/parser/tests/test_method.py430
-rw-r--r--dom/bindings/parser/tests/test_namespace.py232
-rw-r--r--dom/bindings/parser/tests/test_newobject.py73
-rw-r--r--dom/bindings/parser/tests/test_nullable_equivalency.py138
-rw-r--r--dom/bindings/parser/tests/test_nullable_void.py16
-rw-r--r--dom/bindings/parser/tests/test_observableArray.py288
-rw-r--r--dom/bindings/parser/tests/test_optional_constraints.py35
-rw-r--r--dom/bindings/parser/tests/test_overload.py74
-rw-r--r--dom/bindings/parser/tests/test_promise.py177
-rw-r--r--dom/bindings/parser/tests/test_prototype_ident.py107
-rw-r--r--dom/bindings/parser/tests/test_putForwards.py119
-rw-r--r--dom/bindings/parser/tests/test_record.py61
-rw-r--r--dom/bindings/parser/tests/test_replaceable.py84
-rw-r--r--dom/bindings/parser/tests/test_sanity.py7
-rw-r--r--dom/bindings/parser/tests/test_securecontext_extended_attribute.py538
-rw-r--r--dom/bindings/parser/tests/test_special_method_signature_mismatch.py256
-rw-r--r--dom/bindings/parser/tests/test_special_methods.py117
-rw-r--r--dom/bindings/parser/tests/test_special_methods_uniqueness.py51
-rw-r--r--dom/bindings/parser/tests/test_stringifier.py196
-rw-r--r--dom/bindings/parser/tests/test_toJSON.py309
-rw-r--r--dom/bindings/parser/tests/test_treatNonCallableAsNull.py77
-rw-r--r--dom/bindings/parser/tests/test_typedef.py94
-rw-r--r--dom/bindings/parser/tests/test_typedef_identifier_conflict.py19
-rw-r--r--dom/bindings/parser/tests/test_undefined.py243
-rw-r--r--dom/bindings/parser/tests/test_unenumerable_own_properties.py71
-rw-r--r--dom/bindings/parser/tests/test_unforgeable.py311
-rw-r--r--dom/bindings/parser/tests/test_union.py196
-rw-r--r--dom/bindings/parser/tests/test_union_any.py16
-rw-r--r--dom/bindings/parser/tests/test_union_nullable.py60
-rw-r--r--dom/bindings/parser/tests/test_usvstring.py40
-rw-r--r--dom/bindings/parser/tests/test_variadic_callback.py10
-rw-r--r--dom/bindings/parser/tests/test_variadic_constraints.py74
-rw-r--r--dom/bindings/test/Makefile.in17
-rw-r--r--dom/bindings/test/TestBindingHeader.h1772
-rw-r--r--dom/bindings/test/TestCImplementedInterface.h37
-rw-r--r--dom/bindings/test/TestCallback.webidl10
-rw-r--r--dom/bindings/test/TestCodeGen.webidl1529
-rw-r--r--dom/bindings/test/TestDictionary.webidl9
-rw-r--r--dom/bindings/test/TestExampleGen.webidl911
-rw-r--r--dom/bindings/test/TestFunctions.cpp315
-rw-r--r--dom/bindings/test/TestFunctions.h138
-rw-r--r--dom/bindings/test/TestInterfaceAsyncIterableDouble.cpp98
-rw-r--r--dom/bindings/test/TestInterfaceAsyncIterableDouble.h64
-rw-r--r--dom/bindings/test/TestInterfaceAsyncIterableDoubleUnion.cpp107
-rw-r--r--dom/bindings/test/TestInterfaceAsyncIterableDoubleUnion.h64
-rw-r--r--dom/bindings/test/TestInterfaceAsyncIterableSingle.cpp130
-rw-r--r--dom/bindings/test/TestInterfaceAsyncIterableSingle.h81
-rw-r--r--dom/bindings/test/TestInterfaceAsyncIterableSingleWithArgs.cpp118
-rw-r--r--dom/bindings/test/TestInterfaceAsyncIterableSingleWithArgs.h60
-rw-r--r--dom/bindings/test/TestInterfaceIterableDouble.cpp71
-rw-r--r--dom/bindings/test/TestInterfaceIterableDouble.h53
-rw-r--r--dom/bindings/test/TestInterfaceIterableDoubleUnion.cpp76
-rw-r--r--dom/bindings/test/TestInterfaceIterableDoubleUnion.h52
-rw-r--r--dom/bindings/test/TestInterfaceIterableSingle.cpp72
-rw-r--r--dom/bindings/test/TestInterfaceIterableSingle.h51
-rw-r--r--dom/bindings/test/TestInterfaceJS.sys.mjs220
-rw-r--r--dom/bindings/test/TestInterfaceLength.cpp24
-rw-r--r--dom/bindings/test/TestInterfaceLength.h39
-rw-r--r--dom/bindings/test/TestInterfaceMaplike.cpp74
-rw-r--r--dom/bindings/test/TestInterfaceMaplike.h52
-rw-r--r--dom/bindings/test/TestInterfaceMaplikeJSObject.cpp85
-rw-r--r--dom/bindings/test/TestInterfaceMaplikeJSObject.h55
-rw-r--r--dom/bindings/test/TestInterfaceMaplikeObject.cpp81
-rw-r--r--dom/bindings/test/TestInterfaceMaplikeObject.h55
-rw-r--r--dom/bindings/test/TestInterfaceObservableArray.cpp225
-rw-r--r--dom/bindings/test/TestInterfaceObservableArray.h115
-rw-r--r--dom/bindings/test/TestInterfaceSetlike.cpp51
-rw-r--r--dom/bindings/test/TestInterfaceSetlike.h44
-rw-r--r--dom/bindings/test/TestInterfaceSetlikeNode.cpp53
-rw-r--r--dom/bindings/test/TestInterfaceSetlikeNode.h46
-rw-r--r--dom/bindings/test/TestJSImplGen.webidl884
-rw-r--r--dom/bindings/test/TestJSImplInheritanceGen.webidl39
-rw-r--r--dom/bindings/test/TestTrialInterface.cpp24
-rw-r--r--dom/bindings/test/TestTrialInterface.h39
-rw-r--r--dom/bindings/test/TestTypedef.webidl7
-rw-r--r--dom/bindings/test/WrapperCachedNonISupportsTestInterface.cpp28
-rw-r--r--dom/bindings/test/WrapperCachedNonISupportsTestInterface.h45
-rw-r--r--dom/bindings/test/chrome.ini19
-rw-r--r--dom/bindings/test/file_InstanceOf.html11
-rw-r--r--dom/bindings/test/file_barewordGetsWindow_frame1.html1
-rw-r--r--dom/bindings/test/file_barewordGetsWindow_frame2.html1
-rw-r--r--dom/bindings/test/file_bug1808352_frame.html4
-rw-r--r--dom/bindings/test/file_bug1808352b_frame.html3
-rw-r--r--dom/bindings/test/file_bug775543.html5
-rw-r--r--dom/bindings/test/file_document_location_set_via_xray.html5
-rw-r--r--dom/bindings/test/file_dom_xrays.html24
-rw-r--r--dom/bindings/test/file_proxies_via_xray.html8
-rw-r--r--dom/bindings/test/forOf_iframe.html13
-rw-r--r--dom/bindings/test/mochitest.ini117
-rw-r--r--dom/bindings/test/moz.build69
-rw-r--r--dom/bindings/test/mozITestInterfaceJS.idl21
-rw-r--r--dom/bindings/test/test_ByteString.html31
-rw-r--r--dom/bindings/test/test_InstanceOf.html53
-rw-r--r--dom/bindings/test/test_Object.prototype_props.html21
-rw-r--r--dom/bindings/test/test_async_iterable.html300
-rw-r--r--dom/bindings/test/test_async_stacks.html109
-rw-r--r--dom/bindings/test/test_attributes_on_types.html246
-rw-r--r--dom/bindings/test/test_barewordGetsWindow.html60
-rw-r--r--dom/bindings/test/test_bug1036214.html141
-rw-r--r--dom/bindings/test/test_bug1041646.html49
-rw-r--r--dom/bindings/test/test_bug1123516_maplikesetlike.html278
-rw-r--r--dom/bindings/test/test_bug1123516_maplikesetlikechrome.xhtml70
-rw-r--r--dom/bindings/test/test_bug1123875.html15
-rw-r--r--dom/bindings/test/test_bug1287912.html36
-rw-r--r--dom/bindings/test/test_bug1457051.html34
-rw-r--r--dom/bindings/test/test_bug1808352.html24
-rw-r--r--dom/bindings/test/test_bug1808352b.html25
-rw-r--r--dom/bindings/test/test_bug560072.html34
-rw-r--r--dom/bindings/test/test_bug742191.html36
-rw-r--r--dom/bindings/test/test_bug759621.html29
-rw-r--r--dom/bindings/test/test_bug773326.html13
-rw-r--r--dom/bindings/test/test_bug775543.html36
-rw-r--r--dom/bindings/test/test_bug788369.html30
-rw-r--r--dom/bindings/test/test_bug852846.html34
-rw-r--r--dom/bindings/test/test_bug862092.html36
-rw-r--r--dom/bindings/test/test_callback_across_document_open.html22
-rw-r--r--dom/bindings/test/test_callback_default_thisval.html36
-rw-r--r--dom/bindings/test/test_callback_exceptions.html19
-rw-r--r--dom/bindings/test/test_cloneAndImportNode.html48
-rw-r--r--dom/bindings/test/test_crossOriginWindowSymbolAccess.html29
-rw-r--r--dom/bindings/test/test_defineProperty.html157
-rw-r--r--dom/bindings/test/test_document_location_set_via_xray.html48
-rw-r--r--dom/bindings/test/test_document_location_via_xray_cached.html35
-rw-r--r--dom/bindings/test/test_domProxyArrayLengthGetter.html28
-rw-r--r--dom/bindings/test/test_dom_xrays.html383
-rw-r--r--dom/bindings/test/test_enums.html16
-rw-r--r--dom/bindings/test/test_exceptionSanitization.html56
-rw-r--r--dom/bindings/test/test_exceptionThrowing.html56
-rw-r--r--dom/bindings/test/test_exception_messages.html83
-rw-r--r--dom/bindings/test/test_exception_options_from_jsimplemented.html166
-rw-r--r--dom/bindings/test/test_exceptions_from_jsimplemented.html56
-rw-r--r--dom/bindings/test/test_forOf.html87
-rw-r--r--dom/bindings/test/test_integers.html50
-rw-r--r--dom/bindings/test/test_interfaceLength.html34
-rw-r--r--dom/bindings/test/test_interfaceLength_chrome.html34
-rw-r--r--dom/bindings/test/test_interfaceName.html28
-rw-r--r--dom/bindings/test/test_interfaceToString.html45
-rw-r--r--dom/bindings/test/test_iterable.html241
-rw-r--r--dom/bindings/test/test_jsimplemented_cross_realm_this.html44
-rw-r--r--dom/bindings/test/test_jsimplemented_eventhandler.html47
-rw-r--r--dom/bindings/test/test_jsimplemented_subclassing.html31
-rw-r--r--dom/bindings/test/test_large_arraybuffers.html97
-rw-r--r--dom/bindings/test/test_large_imageData.html68
-rw-r--r--dom/bindings/test/test_lenientThis.html28
-rw-r--r--dom/bindings/test/test_lookupGetter.html49
-rw-r--r--dom/bindings/test/test_namedNoIndexed.html36
-rw-r--r--dom/bindings/test/test_named_getter_enumerability.html41
-rw-r--r--dom/bindings/test/test_observablearray.html546
-rw-r--r--dom/bindings/test/test_observablearray_helper.html376
-rw-r--r--dom/bindings/test/test_observablearray_proxyhandler.html859
-rw-r--r--dom/bindings/test/test_oom_reporting.html42
-rw-r--r--dom/bindings/test/test_prefOnConstructor.html57
-rw-r--r--dom/bindings/test/test_primitive_this.html44
-rw-r--r--dom/bindings/test/test_promise_rejections_from_jsimplemented.html143
-rw-r--r--dom/bindings/test/test_proxies_via_xray.html98
-rw-r--r--dom/bindings/test/test_proxy_accessors.html78
-rw-r--r--dom/bindings/test/test_proxy_expandos.html86
-rw-r--r--dom/bindings/test/test_remoteProxyAsPrototype.html33
-rw-r--r--dom/bindings/test/test_returnUnion.html59
-rw-r--r--dom/bindings/test/test_sequence_detection.html53
-rw-r--r--dom/bindings/test/test_sequence_wrapping.html59
-rw-r--r--dom/bindings/test/test_setWithNamedGetterNoNamedSetter.html40
-rw-r--r--dom/bindings/test/test_stringBindings.html109
-rw-r--r--dom/bindings/test/test_throwing_method_noDCE.html30
-rw-r--r--dom/bindings/test/test_toJSON.html59
-rw-r--r--dom/bindings/test/test_traceProtos.html37
-rw-r--r--dom/bindings/test/test_treat_non_object_as_null.html39
-rw-r--r--dom/bindings/test/test_unforgeablesonexpando.html19
-rw-r--r--dom/bindings/test/test_usvstring.html43
-rw-r--r--dom/bindings/test/test_worker_UnwrapArg.html58
302 files changed, 84513 insertions, 0 deletions
diff --git a/dom/bindings/AtomList.h b/dom/bindings/AtomList.h
new file mode 100644
index 0000000000..844e481fc4
--- /dev/null
+++ b/dom/bindings/AtomList.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_AtomList_h
+#define mozilla_dom_AtomList_h
+
+#include "jsapi.h"
+#include "js/Context.h"
+#include "mozilla/dom/GeneratedAtomList.h"
+
+namespace mozilla::dom {
+
+template <class T>
+T* GetAtomCache(JSContext* aCx) {
+ auto atomCache = static_cast<PerThreadAtomCache*>(JS_GetContextPrivate(aCx));
+
+ return static_cast<T*>(atomCache);
+}
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_AtomList_h
diff --git a/dom/bindings/BindingCallContext.h b/dom/bindings/BindingCallContext.h
new file mode 100644
index 0000000000..41978eb167
--- /dev/null
+++ b/dom/bindings/BindingCallContext.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/**
+ * A struct that encapsulates a JSContex and information about
+ * which binding method was called. The idea is to automatically annotate
+ * exceptions thrown via the BindingCallContext with the method name.
+ */
+
+#ifndef mozilla_dom_BindingCallContext_h
+#define mozilla_dom_BindingCallContext_h
+
+#include <utility>
+
+#include "js/TypeDecls.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+
+namespace mozilla::dom {
+
+class MOZ_NON_TEMPORARY_CLASS MOZ_STACK_CLASS BindingCallContext {
+ public:
+ // aCx is allowed to be null. If it is, the BindingCallContext should
+ // generally act like a null JSContext*: test false when tested as a boolean
+ // and produce nullptr when used as a JSContext*.
+ //
+ // aMethodDescription should be something with longer lifetime than this
+ // BindingCallContext. Most simply, a string literal. nullptr or "" is
+ // allowed if we want to not have any particular message description. This
+ // argument corresponds to the "context" string used for DOM error codes that
+ // support one. See Errors.msg and the documentation for
+ // ErrorResult::MaybeSetPendingException for details on he context arg.
+ BindingCallContext(JSContext* aCx, const char* aMethodDescription)
+ : mCx(aCx), mDescription(aMethodDescription) {}
+
+ ~BindingCallContext() = default;
+
+ // Allow passing a BindingCallContext as a JSContext*, as needed.
+ operator JSContext*() const { return mCx; }
+
+ // Allow testing a BindingCallContext for falsiness, just like a
+ // JSContext* could be tested.
+ explicit operator bool() const { return !!mCx; }
+
+ // Allow throwing an error message, if it has a context.
+ template <dom::ErrNum errorNumber, typename... Ts>
+ bool ThrowErrorMessage(Ts&&... aMessageArgs) const {
+ static_assert(ErrorFormatHasContext[errorNumber],
+ "We plan to add a context; it better be expected!");
+ MOZ_ASSERT(mCx);
+ return dom::ThrowErrorMessage<errorNumber>(
+ mCx, mDescription, std::forward<Ts>(aMessageArgs)...);
+ }
+
+ private:
+ JSContext* const mCx;
+ const char* const mDescription;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_BindingCallContext_h
diff --git a/dom/bindings/BindingDeclarations.h b/dom/bindings/BindingDeclarations.h
new file mode 100644
index 0000000000..8209f6323d
--- /dev/null
+++ b/dom/bindings/BindingDeclarations.h
@@ -0,0 +1,547 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/**
+ * A header for declaring various things that binding implementation headers
+ * might need. The idea is to make binding implementation headers safe to
+ * include anywhere without running into include hell like we do with
+ * BindingUtils.h
+ */
+#ifndef mozilla_dom_BindingDeclarations_h__
+#define mozilla_dom_BindingDeclarations_h__
+
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+
+#include "mozilla/Maybe.h"
+
+#include "mozilla/dom/DOMString.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#include <type_traits>
+
+#include "js/Value.h"
+#include "mozilla/RootedOwningNonNull.h"
+#include "mozilla/RootedRefPtr.h"
+
+class nsIPrincipal;
+class nsWrapperCache;
+
+namespace mozilla {
+
+class ErrorResult;
+class OOMReporter;
+class CopyableErrorResult;
+
+namespace dom {
+
+class BindingCallContext;
+
+// Struct that serves as a base class for all dictionaries. Particularly useful
+// so we can use std::is_base_of to detect dictionary template arguments.
+struct DictionaryBase {
+ protected:
+ bool ParseJSON(JSContext* aCx, const nsAString& aJSON,
+ JS::MutableHandle<JS::Value> aVal);
+
+ bool StringifyToJSON(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ nsAString& aJSON) const;
+
+ // Struct used as a way to force a dictionary constructor to not init the
+ // dictionary (via constructing from a pointer to this class). We're putting
+ // it here so that all the dictionaries will have access to it, but outside
+ // code will not.
+ struct FastDictionaryInitializer {};
+
+ bool mIsAnyMemberPresent = false;
+
+ private:
+ // aString is expected to actually be an nsAString*. Should only be
+ // called from StringifyToJSON.
+ static bool AppendJSONToString(const char16_t* aJSONData,
+ uint32_t aDataLength, void* aString);
+
+ public:
+ bool IsAnyMemberPresent() const { return mIsAnyMemberPresent; }
+};
+
+template <typename T>
+inline std::enable_if_t<std::is_base_of<DictionaryBase, T>::value, void>
+ImplCycleCollectionUnlink(T& aDictionary) {
+ aDictionary.UnlinkForCC();
+}
+
+template <typename T>
+inline std::enable_if_t<std::is_base_of<DictionaryBase, T>::value, void>
+ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+ T& aDictionary, const char* aName,
+ uint32_t aFlags = 0) {
+ aDictionary.TraverseForCC(aCallback, aFlags);
+}
+
+template <typename T>
+inline std::enable_if_t<std::is_base_of<DictionaryBase, T>::value, void>
+ImplCycleCollectionUnlink(UniquePtr<T>& aDictionary) {
+ aDictionary.reset();
+}
+
+template <typename T>
+inline std::enable_if_t<std::is_base_of<DictionaryBase, T>::value, void>
+ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+ UniquePtr<T>& aDictionary, const char* aName,
+ uint32_t aFlags = 0) {
+ if (aDictionary) {
+ ImplCycleCollectionTraverse(aCallback, *aDictionary, aName, aFlags);
+ }
+}
+// Struct that serves as a base class for all typed arrays and array buffers and
+// array buffer views. Particularly useful so we can use std::is_base_of to
+// detect typed array/buffer/view template arguments.
+struct AllTypedArraysBase {};
+
+// Struct that serves as a base class for all owning unions.
+// Particularly useful so we can use std::is_base_of to detect owning union
+// template arguments.
+struct AllOwningUnionBase {};
+
+struct EnumEntry {
+ const char* value;
+ size_t length;
+};
+
+enum class CallerType : uint32_t;
+
+class MOZ_STACK_CLASS GlobalObject {
+ public:
+ GlobalObject(JSContext* aCx, JSObject* aObject);
+
+ JSObject* Get() const { return mGlobalJSObject; }
+
+ nsISupports* GetAsSupports() const;
+
+ // The context that this returns is not guaranteed to be in the compartment of
+ // the object returned from Get(), in fact it's generally in the caller's
+ // compartment.
+ JSContext* Context() const { return mCx; }
+
+ bool Failed() const { return !Get(); }
+
+ // It returns the subjectPrincipal if called on the main-thread, otherwise
+ // a nullptr is returned.
+ nsIPrincipal* GetSubjectPrincipal() const;
+
+ // Get the caller type. Note that this needs to be called before anyone has
+ // had a chance to mess with the JSContext.
+ dom::CallerType CallerType() const;
+
+ protected:
+ JS::Rooted<JSObject*> mGlobalJSObject;
+ JSContext* mCx;
+ mutable nsISupports* MOZ_UNSAFE_REF(
+ "Valid because GlobalObject is a stack "
+ "class, and mGlobalObject points to the "
+ "global, so it won't be destroyed as long "
+ "as GlobalObject lives on the stack") mGlobalObject;
+};
+
+// Class for representing optional arguments.
+template <typename T, typename InternalType>
+class Optional_base {
+ public:
+ Optional_base() = default;
+
+ Optional_base(Optional_base&&) = default;
+ Optional_base& operator=(Optional_base&&) = default;
+
+ explicit Optional_base(const T& aValue) { mImpl.emplace(aValue); }
+ explicit Optional_base(T&& aValue) { mImpl.emplace(std::move(aValue)); }
+
+ bool operator==(const Optional_base<T, InternalType>& aOther) const {
+ return mImpl == aOther.mImpl;
+ }
+
+ bool operator!=(const Optional_base<T, InternalType>& aOther) const {
+ return mImpl != aOther.mImpl;
+ }
+
+ template <typename T1, typename T2>
+ explicit Optional_base(const T1& aValue1, const T2& aValue2) {
+ mImpl.emplace(aValue1, aValue2);
+ }
+
+ bool WasPassed() const { return mImpl.isSome(); }
+
+ // Return InternalType here so we can work with it usefully.
+ template <typename... Args>
+ InternalType& Construct(Args&&... aArgs) {
+ mImpl.emplace(std::forward<Args>(aArgs)...);
+ return *mImpl;
+ }
+
+ void Reset() { mImpl.reset(); }
+
+ const T& Value() const { return *mImpl; }
+
+ // Return InternalType here so we can work with it usefully.
+ InternalType& Value() { return *mImpl; }
+
+ // And an explicit way to get the InternalType even if we're const.
+ const InternalType& InternalValue() const { return *mImpl; }
+
+ // If we ever decide to add conversion operators for optional arrays
+ // like the ones Nullable has, we'll need to ensure that Maybe<> has
+ // the boolean before the actual data.
+
+ private:
+ // Forbid copy-construction and assignment
+ Optional_base(const Optional_base& other) = delete;
+ const Optional_base& operator=(const Optional_base& other) = delete;
+
+ protected:
+ Maybe<InternalType> mImpl;
+};
+
+template <typename T>
+class Optional : public Optional_base<T, T> {
+ public:
+ MOZ_ALLOW_TEMPORARY Optional() : Optional_base<T, T>() {}
+
+ explicit Optional(const T& aValue) : Optional_base<T, T>(aValue) {}
+ Optional(Optional&&) = default;
+};
+
+template <typename T>
+class Optional<JS::Handle<T>>
+ : public Optional_base<JS::Handle<T>, JS::Rooted<T>> {
+ public:
+ MOZ_ALLOW_TEMPORARY Optional()
+ : Optional_base<JS::Handle<T>, JS::Rooted<T>>() {}
+
+ explicit Optional(JSContext* cx)
+ : Optional_base<JS::Handle<T>, JS::Rooted<T>>() {
+ this->Construct(cx);
+ }
+
+ Optional(JSContext* cx, const T& aValue)
+ : Optional_base<JS::Handle<T>, JS::Rooted<T>>(cx, aValue) {}
+
+ // Override the const Value() to return the right thing so we're not
+ // returning references to temporaries.
+ JS::Handle<T> Value() const { return *this->mImpl; }
+
+ // And we have to override the non-const one too, since we're
+ // shadowing the one on the superclass.
+ JS::Rooted<T>& Value() { return *this->mImpl; }
+};
+
+// A specialization of Optional for JSObject* to make sure that when someone
+// calls Construct() on it we will pre-initialized the JSObject* to nullptr so
+// it can be traced safely.
+template <>
+class Optional<JSObject*> : public Optional_base<JSObject*, JSObject*> {
+ public:
+ Optional() = default;
+
+ explicit Optional(JSObject* aValue)
+ : Optional_base<JSObject*, JSObject*>(aValue) {}
+
+ // Don't allow us to have an uninitialized JSObject*
+ JSObject*& Construct() {
+ // The Android compiler sucks and thinks we're trying to construct
+ // a JSObject* from an int if we don't cast here. :(
+ return Optional_base<JSObject*, JSObject*>::Construct(
+ static_cast<JSObject*>(nullptr));
+ }
+
+ template <class T1>
+ JSObject*& Construct(const T1& t1) {
+ return Optional_base<JSObject*, JSObject*>::Construct(t1);
+ }
+};
+
+// A specialization of Optional for JS::Value to make sure no one ever uses it.
+template <>
+class Optional<JS::Value> {
+ private:
+ Optional() = delete;
+
+ explicit Optional(const JS::Value& aValue) = delete;
+};
+
+// A specialization of Optional for NonNull that lets us get a T& from Value()
+template <typename U>
+class NonNull;
+template <typename T>
+class Optional<NonNull<T>> : public Optional_base<T, NonNull<T>> {
+ public:
+ // We want our Value to actually return a non-const reference, even
+ // if we're const. At least for things that are normally pointer
+ // types...
+ T& Value() const { return *this->mImpl->get(); }
+
+ // And we have to override the non-const one too, since we're
+ // shadowing the one on the superclass.
+ NonNull<T>& Value() { return *this->mImpl; }
+};
+
+// A specialization of Optional for OwningNonNull that lets us get a
+// T& from Value()
+template <typename T>
+class Optional<OwningNonNull<T>> : public Optional_base<T, OwningNonNull<T>> {
+ public:
+ // We want our Value to actually return a non-const reference, even
+ // if we're const. At least for things that are normally pointer
+ // types...
+ T& Value() const { return *this->mImpl->get(); }
+
+ // And we have to override the non-const one too, since we're
+ // shadowing the one on the superclass.
+ OwningNonNull<T>& Value() { return *this->mImpl; }
+};
+
+// Specialization for strings.
+// XXXbz we can't pull in FakeString here, because it depends on internal
+// strings. So we just have to forward-declare it and reimplement its
+// ToAStringPtr.
+
+namespace binding_detail {
+template <typename CharT>
+struct FakeString;
+} // namespace binding_detail
+
+template <typename CharT>
+class Optional<nsTSubstring<CharT>> {
+ using AString = nsTSubstring<CharT>;
+
+ public:
+ Optional() : mStr(nullptr) {}
+
+ bool WasPassed() const { return !!mStr; }
+
+ void operator=(const AString* str) {
+ MOZ_ASSERT(str);
+ mStr = str;
+ }
+
+ // If this code ever goes away, remove the comment pointing to it in the
+ // FakeString class in BindingUtils.h.
+ void operator=(const binding_detail::FakeString<CharT>* str) {
+ MOZ_ASSERT(str);
+ mStr = reinterpret_cast<const nsTString<CharT>*>(str);
+ }
+
+ const AString& Value() const {
+ MOZ_ASSERT(WasPassed());
+ return *mStr;
+ }
+
+ private:
+ // Forbid copy-construction and assignment
+ Optional(const Optional& other) = delete;
+ const Optional& operator=(const Optional& other) = delete;
+
+ const AString* mStr;
+};
+
+template <typename T>
+inline void ImplCycleCollectionUnlink(Optional<T>& aField) {
+ if (aField.WasPassed()) {
+ ImplCycleCollectionUnlink(aField.Value());
+ }
+}
+
+template <typename T>
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback, Optional<T>& aField,
+ const char* aName, uint32_t aFlags = 0) {
+ if (aField.WasPassed()) {
+ ImplCycleCollectionTraverse(aCallback, aField.Value(), aName, aFlags);
+ }
+}
+
+template <class T>
+class NonNull {
+ public:
+ NonNull()
+#ifdef DEBUG
+ : inited(false)
+#endif
+ {
+ }
+
+ // This is no worse than get() in terms of const handling.
+ operator T&() const {
+ MOZ_ASSERT(inited);
+ MOZ_ASSERT(ptr, "NonNull<T> was set to null");
+ return *ptr;
+ }
+
+ operator T*() const {
+ MOZ_ASSERT(inited);
+ MOZ_ASSERT(ptr, "NonNull<T> was set to null");
+ return ptr;
+ }
+
+ void operator=(T* t) {
+ ptr = t;
+ MOZ_ASSERT(ptr);
+#ifdef DEBUG
+ inited = true;
+#endif
+ }
+
+ template <typename U>
+ void operator=(U* t) {
+ ptr = t->ToAStringPtr();
+ MOZ_ASSERT(ptr);
+#ifdef DEBUG
+ inited = true;
+#endif
+ }
+
+ T** Slot() {
+#ifdef DEBUG
+ inited = true;
+#endif
+ return &ptr;
+ }
+
+ T* Ptr() {
+ MOZ_ASSERT(inited);
+ MOZ_ASSERT(ptr, "NonNull<T> was set to null");
+ return ptr;
+ }
+
+ // Make us work with smart-ptr helpers that expect a get()
+ T* get() const {
+ MOZ_ASSERT(inited);
+ MOZ_ASSERT(ptr);
+ return ptr;
+ }
+
+ protected:
+ // ptr is left uninitialized for optimization purposes.
+ MOZ_INIT_OUTSIDE_CTOR T* ptr;
+#ifdef DEBUG
+ bool inited;
+#endif
+};
+
+// Class for representing sequences in arguments. We use a non-auto array
+// because that allows us to use sequences of sequences and the like. This
+// needs to be fallible because web content controls the length of the array,
+// and can easily try to create very large lengths.
+template <typename T>
+class Sequence : public FallibleTArray<T> {
+ public:
+ Sequence() : FallibleTArray<T>() {}
+ MOZ_IMPLICIT Sequence(FallibleTArray<T>&& aArray)
+ : FallibleTArray<T>(std::move(aArray)) {}
+ MOZ_IMPLICIT Sequence(nsTArray<T>&& aArray)
+ : FallibleTArray<T>(std::move(aArray)) {}
+
+ Sequence(Sequence&&) = default;
+ Sequence& operator=(Sequence&&) = default;
+
+ // XXX(Bug 1631461) Codegen.py must be adapted to allow making Sequence
+ // uncopyable.
+ Sequence(const Sequence& aOther) {
+ if (!this->AppendElements(aOther, fallible)) {
+ MOZ_CRASH("Out of memory");
+ }
+ }
+ Sequence& operator=(const Sequence& aOther) {
+ if (this != &aOther) {
+ this->Clear();
+ if (!this->AppendElements(aOther, fallible)) {
+ MOZ_CRASH("Out of memory");
+ }
+ }
+ return *this;
+ }
+};
+
+inline nsWrapperCache* GetWrapperCache(nsWrapperCache* cache) { return cache; }
+
+inline nsWrapperCache* GetWrapperCache(void* p) { return nullptr; }
+
+// Helper template for smart pointers to resolve ambiguity between
+// GetWrappeCache(void*) and GetWrapperCache(const ParentObject&).
+template <template <typename> class SmartPtr, typename T>
+inline nsWrapperCache* GetWrapperCache(const SmartPtr<T>& aObject) {
+ return GetWrapperCache(aObject.get());
+}
+
+enum class ReflectionScope { Content, NAC, UAWidget };
+
+struct MOZ_STACK_CLASS ParentObject {
+ template <class T>
+ MOZ_IMPLICIT ParentObject(T* aObject)
+ : mObject(ToSupports(aObject)),
+ mWrapperCache(GetWrapperCache(aObject)),
+ mReflectionScope(ReflectionScope::Content) {}
+
+ template <class T, template <typename> class SmartPtr>
+ MOZ_IMPLICIT ParentObject(const SmartPtr<T>& aObject)
+ : mObject(aObject.get()),
+ mWrapperCache(GetWrapperCache(aObject.get())),
+ mReflectionScope(ReflectionScope::Content) {}
+
+ ParentObject(nsISupports* aObject, nsWrapperCache* aCache)
+ : mObject(aObject),
+ mWrapperCache(aCache),
+ mReflectionScope(ReflectionScope::Content) {}
+
+ // We don't want to make this an nsCOMPtr because of performance reasons, but
+ // it's safe because ParentObject is a stack class.
+ nsISupports* const MOZ_NON_OWNING_REF mObject;
+ nsWrapperCache* const mWrapperCache;
+ ReflectionScope mReflectionScope;
+};
+
+namespace binding_detail {
+
+// Class for simple sequence arguments, only used internally by codegen.
+template <typename T>
+class AutoSequence : public AutoTArray<T, 16> {
+ public:
+ AutoSequence() : AutoTArray<T, 16>() {}
+
+ // Allow converting to const sequences as needed
+ operator const Sequence<T>&() const {
+ return *reinterpret_cast<const Sequence<T>*>(this);
+ }
+};
+
+} // namespace binding_detail
+
+// Enum to represent a system or non-system caller type.
+enum class CallerType : uint32_t { System, NonSystem };
+
+// A class that can be passed (by value or const reference) to indicate that the
+// caller is always a system caller. This can be used as the type of an
+// argument to force only system callers to call a function.
+class SystemCallerGuarantee {
+ public:
+ operator CallerType() const { return CallerType::System; }
+};
+
+class ProtoAndIfaceCache;
+typedef void (*CreateInterfaceObjectsMethod)(JSContext* aCx,
+ JS::Handle<JSObject*> aGlobal,
+ ProtoAndIfaceCache& aCache,
+ bool aDefineOnGlobal);
+JS::Handle<JSObject*> GetPerInterfaceObjectHandle(
+ JSContext* aCx, size_t aSlotId, CreateInterfaceObjectsMethod aCreator,
+ bool aDefineOnGlobal);
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_BindingDeclarations_h__
diff --git a/dom/bindings/BindingIPCUtils.h b/dom/bindings/BindingIPCUtils.h
new file mode 100644
index 0000000000..5598e5896b
--- /dev/null
+++ b/dom/bindings/BindingIPCUtils.h
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_BindingIPCUtils_h
+#define _mozilla_dom_BindingIPCUtils_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "ipc/EnumSerializer.h"
+
+namespace IPC {
+template <>
+struct ParamTraits<mozilla::dom::CallerType>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::dom::CallerType, mozilla::dom::CallerType::System,
+ mozilla::dom::CallerType::NonSystem> {};
+} // namespace IPC
+#endif // _mozilla_dom_BindingIPCUtils_h
diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp
new file mode 100644
index 0000000000..40e6a066ba
--- /dev/null
+++ b/dom/bindings/BindingUtils.cpp
@@ -0,0 +1,4375 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "BindingUtils.h"
+
+#include <algorithm>
+#include <stdarg.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/UseCounter.h"
+
+#include "AccessCheck.h"
+#include "js/CallAndConstruct.h" // JS::Call, JS::IsCallable
+#include "js/experimental/JitInfo.h" // JSJit{Getter,Setter,Method}CallArgs, JSJit{Getter,Setter}Op, JSJitInfo
+#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
+#include "js/Id.h"
+#include "js/JSON.h"
+#include "js/MapAndSet.h"
+#include "js/Object.h" // JS::GetClass, JS::GetCompartment, JS::GetReservedSlot, JS::SetReservedSlot
+#include "js/PropertyAndElement.h" // JS_AlreadyHasOwnPropertyById, JS_DefineFunction, JS_DefineFunctionById, JS_DefineFunctions, JS_DefineProperties, JS_DefineProperty, JS_DefinePropertyById, JS_ForwardGetPropertyTo, JS_GetProperty, JS_HasProperty, JS_HasPropertyById
+#include "js/StableStringChars.h"
+#include "js/String.h" // JS::GetStringLength, JS::MaxStringLength, JS::StringHasLatin1Chars
+#include "js/Symbol.h"
+#include "jsfriendapi.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "nsHTMLTags.h"
+#include "nsIDOMGlobalPropertyInitializer.h"
+#include "nsINode.h"
+#include "nsIOService.h"
+#include "nsIPrincipal.h"
+#include "nsIXPConnect.h"
+#include "nsUTF8Utils.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+#include "WrapperFactory.h"
+#include "xpcprivate.h"
+#include "XrayWrapper.h"
+#include "nsPrintfCString.h"
+#include "mozilla/Sprintf.h"
+#include "nsReadableUtils.h"
+#include "nsWrapperCacheInlines.h"
+
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/CustomElementRegistry.h"
+#include "mozilla/dom/DeprecationReportBody.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/ElementBinding.h"
+#include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/HTMLObjectElement.h"
+#include "mozilla/dom/HTMLObjectElementBinding.h"
+#include "mozilla/dom/HTMLEmbedElement.h"
+#include "mozilla/dom/HTMLElementBinding.h"
+#include "mozilla/dom/HTMLEmbedElementBinding.h"
+#include "mozilla/dom/MaybeCrossOriginObject.h"
+#include "mozilla/dom/ObservableArrayProxyHandler.h"
+#include "mozilla/dom/ReportingUtils.h"
+#include "mozilla/dom/XULElementBinding.h"
+#include "mozilla/dom/XULFrameElementBinding.h"
+#include "mozilla/dom/XULMenuElementBinding.h"
+#include "mozilla/dom/XULPopupElementBinding.h"
+#include "mozilla/dom/XULResizerElementBinding.h"
+#include "mozilla/dom/XULTextElementBinding.h"
+#include "mozilla/dom/XULTreeElementBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/WebIDLGlobalNameHash.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/dom/XrayExpandoClass.h"
+#include "mozilla/dom/WindowProxyHolder.h"
+#include "ipc/ErrorIPCUtils.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "mozilla/dom/DocGroup.h"
+#include "nsXULElement.h"
+
+namespace mozilla {
+namespace dom {
+
+// Forward declare GetConstructorObject methods.
+#define HTML_TAG(_tag, _classname, _interfacename) \
+ namespace HTML##_interfacename##Element_Binding { \
+ JSObject* GetConstructorObject(JSContext*); \
+ }
+#define HTML_OTHER(_tag)
+#include "nsHTMLTagList.h"
+#undef HTML_TAG
+#undef HTML_OTHER
+
+using constructorGetterCallback = JSObject* (*)(JSContext*);
+
+// Mapping of html tag and GetConstructorObject methods.
+#define HTML_TAG(_tag, _classname, _interfacename) \
+ HTML##_interfacename##Element_Binding::GetConstructorObject,
+#define HTML_OTHER(_tag) nullptr,
+// We use eHTMLTag_foo (where foo is the tag) which is defined in nsHTMLTags.h
+// to index into this array.
+static const constructorGetterCallback sConstructorGetterCallback[] = {
+ HTMLUnknownElement_Binding::GetConstructorObject,
+#include "nsHTMLTagList.h"
+#undef HTML_TAG
+#undef HTML_OTHER
+};
+
+static const JSErrorFormatString ErrorFormatString[] = {
+#define MSG_DEF(_name, _argc, _has_context, _exn, _str) \
+ {#_name, _str, _argc, _exn},
+#include "mozilla/dom/Errors.msg"
+#undef MSG_DEF
+};
+
+#define MSG_DEF(_name, _argc, _has_context, _exn, _str) \
+ static_assert( \
+ (_argc) < JS::MaxNumErrorArguments, #_name \
+ " must only have as many error arguments as the JS engine can support");
+#include "mozilla/dom/Errors.msg"
+#undef MSG_DEF
+
+static const JSErrorFormatString* GetErrorMessage(void* aUserRef,
+ const unsigned aErrorNumber) {
+ MOZ_ASSERT(aErrorNumber < ArrayLength(ErrorFormatString));
+ return &ErrorFormatString[aErrorNumber];
+}
+
+uint16_t GetErrorArgCount(const ErrNum aErrorNumber) {
+ return GetErrorMessage(nullptr, aErrorNumber)->argCount;
+}
+
+// aErrorNumber needs to be unsigned, not an ErrNum, because the latter makes
+// va_start have undefined behavior, and we do not want undefined behavior.
+void binding_detail::ThrowErrorMessage(JSContext* aCx,
+ const unsigned aErrorNumber, ...) {
+ va_list ap;
+ va_start(ap, aErrorNumber);
+
+ if (!ErrorFormatHasContext[aErrorNumber]) {
+ JS_ReportErrorNumberUTF8VA(aCx, GetErrorMessage, nullptr, aErrorNumber, ap);
+ va_end(ap);
+ return;
+ }
+
+ // Our first arg is the context arg. We want to replace nullptr with empty
+ // string, leave empty string alone, and for anything else append ": " to the
+ // end. See also the behavior of
+ // TErrorResult::SetPendingExceptionWithMessage, which this is mirroring for
+ // exceptions that are thrown directly, not via an ErrorResult.
+ const char* args[JS::MaxNumErrorArguments + 1];
+ size_t argCount = GetErrorArgCount(static_cast<ErrNum>(aErrorNumber));
+ MOZ_ASSERT(argCount > 0, "We have a context arg!");
+ nsAutoCString firstArg;
+
+ for (size_t i = 0; i < argCount; ++i) {
+ args[i] = va_arg(ap, const char*);
+ if (i == 0) {
+ if (args[0] && *args[0]) {
+ firstArg.Append(args[0]);
+ firstArg.AppendLiteral(": ");
+ }
+ args[0] = firstArg.get();
+ }
+ }
+
+ JS_ReportErrorNumberUTF8Array(aCx, GetErrorMessage, nullptr, aErrorNumber,
+ args);
+ va_end(ap);
+}
+
+static bool ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs,
+ bool aSecurityError, const char* aInterfaceName) {
+ NS_ConvertASCIItoUTF16 ifaceName(aInterfaceName);
+ // This should only be called for DOM methods/getters/setters, which
+ // are JSNative-backed functions, so we can assume that
+ // JS_ValueToFunction and JS_GetFunctionDisplayId will both return
+ // non-null and that JS_GetStringCharsZ returns non-null.
+ JS::Rooted<JSFunction*> func(aCx, JS_ValueToFunction(aCx, aArgs.calleev()));
+ MOZ_ASSERT(func);
+ JS::Rooted<JSString*> funcName(aCx, JS_GetFunctionDisplayId(func));
+ MOZ_ASSERT(funcName);
+ nsAutoJSString funcNameStr;
+ if (!funcNameStr.init(aCx, funcName)) {
+ return false;
+ }
+ if (aSecurityError) {
+ return Throw(aCx, NS_ERROR_DOM_SECURITY_ERR,
+ nsPrintfCString("Permission to call '%s' denied.",
+ NS_ConvertUTF16toUTF8(funcNameStr).get()));
+ }
+
+ const ErrNum errorNumber = MSG_METHOD_THIS_DOES_NOT_IMPLEMENT_INTERFACE;
+ MOZ_RELEASE_ASSERT(GetErrorArgCount(errorNumber) == 2);
+ JS_ReportErrorNumberUC(aCx, GetErrorMessage, nullptr,
+ static_cast<unsigned>(errorNumber),
+ static_cast<const char16_t*>(funcNameStr.get()),
+ static_cast<const char16_t*>(ifaceName.get()));
+ return false;
+}
+
+bool ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs,
+ bool aSecurityError, prototypes::ID aProtoId) {
+ return ThrowInvalidThis(aCx, aArgs, aSecurityError,
+ NamesOfInterfacesWithProtos(aProtoId));
+}
+
+bool ThrowNoSetterArg(JSContext* aCx, const JS::CallArgs& aArgs,
+ prototypes::ID aProtoId) {
+ nsPrintfCString errorMessage("%s attribute setter",
+ NamesOfInterfacesWithProtos(aProtoId));
+ return aArgs.requireAtLeast(aCx, errorMessage.get(), 1);
+}
+
+} // namespace dom
+
+namespace binding_danger {
+
+template <typename CleanupPolicy>
+struct TErrorResult<CleanupPolicy>::Message {
+ Message() : mErrorNumber(dom::Err_Limit) {
+ MOZ_COUNT_CTOR(TErrorResult::Message);
+ }
+ ~Message() { MOZ_COUNT_DTOR(TErrorResult::Message); }
+
+ // UTF-8 strings (probably ASCII in most cases) in mArgs.
+ nsTArray<nsCString> mArgs;
+ dom::ErrNum mErrorNumber;
+
+ bool HasCorrectNumberOfArguments() {
+ return GetErrorArgCount(mErrorNumber) == mArgs.Length();
+ }
+
+ bool operator==(const TErrorResult<CleanupPolicy>::Message& aRight) const {
+ return mErrorNumber == aRight.mErrorNumber && mArgs == aRight.mArgs;
+ }
+};
+
+template <typename CleanupPolicy>
+nsTArray<nsCString>& TErrorResult<CleanupPolicy>::CreateErrorMessageHelper(
+ const dom::ErrNum errorNumber, nsresult errorType) {
+ AssertInOwningThread();
+ mResult = errorType;
+
+ Message* message = InitMessage(new Message());
+ message->mErrorNumber = errorNumber;
+ return message->mArgs;
+}
+
+template <typename CleanupPolicy>
+void TErrorResult<CleanupPolicy>::SerializeMessage(
+ IPC::MessageWriter* aWriter) const {
+ using namespace IPC;
+ AssertInOwningThread();
+ MOZ_ASSERT(mUnionState == HasMessage);
+ MOZ_ASSERT(mExtra.mMessage);
+ WriteParam(aWriter, mExtra.mMessage->mArgs);
+ WriteParam(aWriter, mExtra.mMessage->mErrorNumber);
+}
+
+template <typename CleanupPolicy>
+bool TErrorResult<CleanupPolicy>::DeserializeMessage(
+ IPC::MessageReader* aReader) {
+ using namespace IPC;
+ AssertInOwningThread();
+ auto readMessage = MakeUnique<Message>();
+ if (!ReadParam(aReader, &readMessage->mArgs) ||
+ !ReadParam(aReader, &readMessage->mErrorNumber)) {
+ return false;
+ }
+ if (!readMessage->HasCorrectNumberOfArguments()) {
+ return false;
+ }
+
+ MOZ_ASSERT(mUnionState == HasNothing);
+ InitMessage(readMessage.release());
+#ifdef DEBUG
+ mUnionState = HasMessage;
+#endif // DEBUG
+ return true;
+}
+
+template <typename CleanupPolicy>
+void TErrorResult<CleanupPolicy>::SetPendingExceptionWithMessage(
+ JSContext* aCx, const char* context) {
+ AssertInOwningThread();
+ MOZ_ASSERT(mUnionState == HasMessage);
+ MOZ_ASSERT(mExtra.mMessage,
+ "SetPendingExceptionWithMessage() can be called only once");
+
+ Message* message = mExtra.mMessage;
+ MOZ_RELEASE_ASSERT(message->HasCorrectNumberOfArguments());
+ if (dom::ErrorFormatHasContext[message->mErrorNumber]) {
+ MOZ_ASSERT(!message->mArgs.IsEmpty(), "How could we have no args here?");
+ MOZ_ASSERT(message->mArgs[0].IsEmpty(), "Context should not be set yet!");
+ if (context) {
+ // Prepend our context and ": "; see API documentation.
+ message->mArgs[0].AssignASCII(context);
+ message->mArgs[0].AppendLiteral(": ");
+ }
+ }
+ const uint32_t argCount = message->mArgs.Length();
+ const char* args[JS::MaxNumErrorArguments + 1];
+ for (uint32_t i = 0; i < argCount; ++i) {
+ args[i] = message->mArgs.ElementAt(i).get();
+ }
+ args[argCount] = nullptr;
+
+ JS_ReportErrorNumberUTF8Array(aCx, dom::GetErrorMessage, nullptr,
+ static_cast<unsigned>(message->mErrorNumber),
+ argCount > 0 ? args : nullptr);
+
+ ClearMessage();
+ mResult = NS_OK;
+}
+
+template <typename CleanupPolicy>
+void TErrorResult<CleanupPolicy>::ClearMessage() {
+ AssertInOwningThread();
+ MOZ_ASSERT(IsErrorWithMessage());
+ MOZ_ASSERT(mUnionState == HasMessage);
+ delete mExtra.mMessage;
+ mExtra.mMessage = nullptr;
+#ifdef DEBUG
+ mUnionState = HasNothing;
+#endif // DEBUG
+}
+
+template <typename CleanupPolicy>
+void TErrorResult<CleanupPolicy>::ThrowJSException(JSContext* cx,
+ JS::Handle<JS::Value> exn) {
+ AssertInOwningThread();
+ MOZ_ASSERT(mMightHaveUnreportedJSException,
+ "Why didn't you tell us you planned to throw a JS exception?");
+
+ ClearUnionData();
+
+ // Make sure mExtra.mJSException is initialized _before_ we try to root it.
+ // But don't set it to exn yet, because we don't want to do that until after
+ // we root.
+ JS::Value& exc = InitJSException();
+ if (!js::AddRawValueRoot(cx, &exc, "TErrorResult::mExtra::mJSException")) {
+ // Don't use NS_ERROR_INTERNAL_ERRORRESULT_JS_EXCEPTION, because that
+ // indicates we have in fact rooted mExtra.mJSException.
+ mResult = NS_ERROR_OUT_OF_MEMORY;
+ } else {
+ exc = exn;
+ mResult = NS_ERROR_INTERNAL_ERRORRESULT_JS_EXCEPTION;
+#ifdef DEBUG
+ mUnionState = HasJSException;
+#endif // DEBUG
+ }
+}
+
+template <typename CleanupPolicy>
+void TErrorResult<CleanupPolicy>::SetPendingJSException(JSContext* cx) {
+ AssertInOwningThread();
+ MOZ_ASSERT(!mMightHaveUnreportedJSException,
+ "Why didn't you tell us you planned to handle JS exceptions?");
+ MOZ_ASSERT(mUnionState == HasJSException);
+
+ JS::Rooted<JS::Value> exception(cx, mExtra.mJSException);
+ if (JS_WrapValue(cx, &exception)) {
+ JS_SetPendingException(cx, exception);
+ }
+ mExtra.mJSException = exception;
+ // If JS_WrapValue failed, not much we can do about it... No matter
+ // what, go ahead and unroot mExtra.mJSException.
+ js::RemoveRawValueRoot(cx, &mExtra.mJSException);
+
+ mResult = NS_OK;
+#ifdef DEBUG
+ mUnionState = HasNothing;
+#endif // DEBUG
+}
+
+template <typename CleanupPolicy>
+struct TErrorResult<CleanupPolicy>::DOMExceptionInfo {
+ DOMExceptionInfo(nsresult rv, const nsACString& message)
+ : mMessage(message), mRv(rv) {}
+
+ nsCString mMessage;
+ nsresult mRv;
+
+ bool operator==(
+ const TErrorResult<CleanupPolicy>::DOMExceptionInfo& aRight) const {
+ return mRv == aRight.mRv && mMessage == aRight.mMessage;
+ }
+};
+
+template <typename CleanupPolicy>
+void TErrorResult<CleanupPolicy>::SerializeDOMExceptionInfo(
+ IPC::MessageWriter* aWriter) const {
+ using namespace IPC;
+ AssertInOwningThread();
+ MOZ_ASSERT(mUnionState == HasDOMExceptionInfo);
+ MOZ_ASSERT(mExtra.mDOMExceptionInfo);
+ WriteParam(aWriter, mExtra.mDOMExceptionInfo->mMessage);
+ WriteParam(aWriter, mExtra.mDOMExceptionInfo->mRv);
+}
+
+template <typename CleanupPolicy>
+bool TErrorResult<CleanupPolicy>::DeserializeDOMExceptionInfo(
+ IPC::MessageReader* aReader) {
+ using namespace IPC;
+ AssertInOwningThread();
+ nsCString message;
+ nsresult rv;
+ if (!ReadParam(aReader, &message) || !ReadParam(aReader, &rv)) {
+ return false;
+ }
+
+ MOZ_ASSERT(mUnionState == HasNothing);
+ MOZ_ASSERT(IsDOMException());
+ InitDOMExceptionInfo(new DOMExceptionInfo(rv, message));
+#ifdef DEBUG
+ mUnionState = HasDOMExceptionInfo;
+#endif // DEBUG
+ return true;
+}
+
+template <typename CleanupPolicy>
+void TErrorResult<CleanupPolicy>::ThrowDOMException(nsresult rv,
+ const nsACString& message) {
+ AssertInOwningThread();
+ ClearUnionData();
+
+ mResult = NS_ERROR_INTERNAL_ERRORRESULT_DOMEXCEPTION;
+ InitDOMExceptionInfo(new DOMExceptionInfo(rv, message));
+#ifdef DEBUG
+ mUnionState = HasDOMExceptionInfo;
+#endif
+}
+
+template <typename CleanupPolicy>
+void TErrorResult<CleanupPolicy>::SetPendingDOMException(JSContext* cx,
+ const char* context) {
+ AssertInOwningThread();
+ MOZ_ASSERT(mUnionState == HasDOMExceptionInfo);
+ MOZ_ASSERT(mExtra.mDOMExceptionInfo,
+ "SetPendingDOMException() can be called only once");
+
+ if (context && !mExtra.mDOMExceptionInfo->mMessage.IsEmpty()) {
+ // Prepend our context and ": "; see API documentation.
+ nsAutoCString prefix(context);
+ prefix.AppendLiteral(": ");
+ mExtra.mDOMExceptionInfo->mMessage.Insert(prefix, 0);
+ }
+
+ dom::Throw(cx, mExtra.mDOMExceptionInfo->mRv,
+ mExtra.mDOMExceptionInfo->mMessage);
+
+ ClearDOMExceptionInfo();
+ mResult = NS_OK;
+}
+
+template <typename CleanupPolicy>
+void TErrorResult<CleanupPolicy>::ClearDOMExceptionInfo() {
+ AssertInOwningThread();
+ MOZ_ASSERT(IsDOMException());
+ MOZ_ASSERT(mUnionState == HasDOMExceptionInfo);
+ delete mExtra.mDOMExceptionInfo;
+ mExtra.mDOMExceptionInfo = nullptr;
+#ifdef DEBUG
+ mUnionState = HasNothing;
+#endif // DEBUG
+}
+
+template <typename CleanupPolicy>
+void TErrorResult<CleanupPolicy>::ClearUnionData() {
+ AssertInOwningThread();
+ if (IsJSException()) {
+ JSContext* cx = dom::danger::GetJSContext();
+ MOZ_ASSERT(cx);
+ mExtra.mJSException.setUndefined();
+ js::RemoveRawValueRoot(cx, &mExtra.mJSException);
+#ifdef DEBUG
+ mUnionState = HasNothing;
+#endif // DEBUG
+ } else if (IsErrorWithMessage()) {
+ ClearMessage();
+ } else if (IsDOMException()) {
+ ClearDOMExceptionInfo();
+ }
+}
+
+template <typename CleanupPolicy>
+void TErrorResult<CleanupPolicy>::SetPendingGenericErrorException(
+ JSContext* cx) {
+ AssertInOwningThread();
+ MOZ_ASSERT(!IsErrorWithMessage());
+ MOZ_ASSERT(!IsJSException());
+ MOZ_ASSERT(!IsDOMException());
+ dom::Throw(cx, ErrorCode());
+ mResult = NS_OK;
+}
+
+template <typename CleanupPolicy>
+TErrorResult<CleanupPolicy>& TErrorResult<CleanupPolicy>::operator=(
+ TErrorResult<CleanupPolicy>&& aRHS) {
+ AssertInOwningThread();
+ aRHS.AssertInOwningThread();
+ // Clear out any union members we may have right now, before we
+ // start writing to it.
+ ClearUnionData();
+
+#ifdef DEBUG
+ mMightHaveUnreportedJSException = aRHS.mMightHaveUnreportedJSException;
+ aRHS.mMightHaveUnreportedJSException = false;
+#endif
+ if (aRHS.IsErrorWithMessage()) {
+ InitMessage(aRHS.mExtra.mMessage);
+ aRHS.mExtra.mMessage = nullptr;
+ } else if (aRHS.IsJSException()) {
+ JSContext* cx = dom::danger::GetJSContext();
+ MOZ_ASSERT(cx);
+ JS::Value& exn = InitJSException();
+ if (!js::AddRawValueRoot(cx, &exn, "TErrorResult::mExtra::mJSException")) {
+ MOZ_CRASH("Could not root mExtra.mJSException, we're about to OOM");
+ }
+ mExtra.mJSException = aRHS.mExtra.mJSException;
+ aRHS.mExtra.mJSException.setUndefined();
+ js::RemoveRawValueRoot(cx, &aRHS.mExtra.mJSException);
+ } else if (aRHS.IsDOMException()) {
+ InitDOMExceptionInfo(aRHS.mExtra.mDOMExceptionInfo);
+ aRHS.mExtra.mDOMExceptionInfo = nullptr;
+ } else {
+ // Null out the union on both sides for hygiene purposes. This is purely
+ // precautionary, so InitMessage/placement-new is unnecessary.
+ mExtra.mMessage = aRHS.mExtra.mMessage = nullptr;
+ }
+
+#ifdef DEBUG
+ mUnionState = aRHS.mUnionState;
+ aRHS.mUnionState = HasNothing;
+#endif // DEBUG
+
+ // Note: It's important to do this last, since this affects the condition
+ // checks above!
+ mResult = aRHS.mResult;
+ aRHS.mResult = NS_OK;
+ return *this;
+}
+
+template <typename CleanupPolicy>
+bool TErrorResult<CleanupPolicy>::operator==(const ErrorResult& aRight) const {
+ auto right = reinterpret_cast<const TErrorResult<CleanupPolicy>*>(&aRight);
+
+ if (mResult != right->mResult) {
+ return false;
+ }
+
+ if (IsJSException()) {
+ // js exceptions are always non-equal
+ return false;
+ }
+
+ if (IsErrorWithMessage()) {
+ return *mExtra.mMessage == *right->mExtra.mMessage;
+ }
+
+ if (IsDOMException()) {
+ return *mExtra.mDOMExceptionInfo == *right->mExtra.mDOMExceptionInfo;
+ }
+
+ return true;
+}
+
+template <typename CleanupPolicy>
+void TErrorResult<CleanupPolicy>::CloneTo(TErrorResult& aRv) const {
+ AssertInOwningThread();
+ aRv.AssertInOwningThread();
+ aRv.ClearUnionData();
+ aRv.mResult = mResult;
+#ifdef DEBUG
+ aRv.mMightHaveUnreportedJSException = mMightHaveUnreportedJSException;
+#endif
+
+ if (IsErrorWithMessage()) {
+#ifdef DEBUG
+ aRv.mUnionState = HasMessage;
+#endif
+ Message* message = aRv.InitMessage(new Message());
+ message->mArgs = mExtra.mMessage->mArgs.Clone();
+ message->mErrorNumber = mExtra.mMessage->mErrorNumber;
+ } else if (IsDOMException()) {
+#ifdef DEBUG
+ aRv.mUnionState = HasDOMExceptionInfo;
+#endif
+ auto* exnInfo = new DOMExceptionInfo(mExtra.mDOMExceptionInfo->mRv,
+ mExtra.mDOMExceptionInfo->mMessage);
+ aRv.InitDOMExceptionInfo(exnInfo);
+ } else if (IsJSException()) {
+#ifdef DEBUG
+ aRv.mUnionState = HasJSException;
+#endif
+ JSContext* cx = dom::danger::GetJSContext();
+ JS::Rooted<JS::Value> exception(cx, mExtra.mJSException);
+ aRv.ThrowJSException(cx, exception);
+ }
+}
+
+template <typename CleanupPolicy>
+void TErrorResult<CleanupPolicy>::SuppressException() {
+ AssertInOwningThread();
+ WouldReportJSException();
+ ClearUnionData();
+ // We don't use AssignErrorCode, because we want to override existing error
+ // states, which AssignErrorCode is not allowed to do.
+ mResult = NS_OK;
+}
+
+template <typename CleanupPolicy>
+void TErrorResult<CleanupPolicy>::SetPendingException(JSContext* cx,
+ const char* context) {
+ AssertInOwningThread();
+ if (IsUncatchableException()) {
+ // Nuke any existing exception on cx, to make sure we're uncatchable.
+ JS_ClearPendingException(cx);
+ // Don't do any reporting. Just return, to create an
+ // uncatchable exception.
+ mResult = NS_OK;
+ return;
+ }
+ if (IsJSContextException()) {
+ // Whatever we need to throw is on the JSContext already.
+ MOZ_ASSERT(JS_IsExceptionPending(cx));
+ mResult = NS_OK;
+ return;
+ }
+ if (IsErrorWithMessage()) {
+ SetPendingExceptionWithMessage(cx, context);
+ return;
+ }
+ if (IsJSException()) {
+ SetPendingJSException(cx);
+ return;
+ }
+ if (IsDOMException()) {
+ SetPendingDOMException(cx, context);
+ return;
+ }
+ SetPendingGenericErrorException(cx);
+}
+
+template <typename CleanupPolicy>
+void TErrorResult<CleanupPolicy>::StealExceptionFromJSContext(JSContext* cx) {
+ AssertInOwningThread();
+ MOZ_ASSERT(mMightHaveUnreportedJSException,
+ "Why didn't you tell us you planned to throw a JS exception?");
+
+ JS::Rooted<JS::Value> exn(cx);
+ if (!JS_GetPendingException(cx, &exn)) {
+ ThrowUncatchableException();
+ return;
+ }
+
+ ThrowJSException(cx, exn);
+ JS_ClearPendingException(cx);
+}
+
+template <typename CleanupPolicy>
+void TErrorResult<CleanupPolicy>::NoteJSContextException(JSContext* aCx) {
+ AssertInOwningThread();
+ if (JS_IsExceptionPending(aCx)) {
+ mResult = NS_ERROR_INTERNAL_ERRORRESULT_EXCEPTION_ON_JSCONTEXT;
+ } else {
+ mResult = NS_ERROR_UNCATCHABLE_EXCEPTION;
+ }
+}
+
+/* static */
+template <typename CleanupPolicy>
+void TErrorResult<CleanupPolicy>::EnsureUTF8Validity(nsCString& aValue,
+ size_t aValidUpTo) {
+ nsCString valid;
+ if (NS_SUCCEEDED(UTF_8_ENCODING->DecodeWithoutBOMHandling(aValue, valid,
+ aValidUpTo))) {
+ aValue = valid;
+ } else {
+ aValue.SetLength(aValidUpTo);
+ }
+}
+
+template class TErrorResult<JustAssertCleanupPolicy>;
+template class TErrorResult<AssertAndSuppressCleanupPolicy>;
+template class TErrorResult<JustSuppressCleanupPolicy>;
+template class TErrorResult<ThreadSafeJustSuppressCleanupPolicy>;
+
+} // namespace binding_danger
+
+namespace dom {
+
+bool DefineConstants(JSContext* cx, JS::Handle<JSObject*> obj,
+ const ConstantSpec* cs) {
+ JS::Rooted<JS::Value> value(cx);
+ for (; cs->name; ++cs) {
+ value = cs->value;
+ bool ok = JS_DefineProperty(
+ cx, obj, cs->name, value,
+ JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
+ if (!ok) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static inline bool Define(JSContext* cx, JS::Handle<JSObject*> obj,
+ const JSFunctionSpec* spec) {
+ return JS_DefineFunctions(cx, obj, spec);
+}
+static inline bool Define(JSContext* cx, JS::Handle<JSObject*> obj,
+ const JSPropertySpec* spec) {
+ return JS_DefineProperties(cx, obj, spec);
+}
+static inline bool Define(JSContext* cx, JS::Handle<JSObject*> obj,
+ const ConstantSpec* spec) {
+ return DefineConstants(cx, obj, spec);
+}
+
+template <typename T>
+bool DefinePrefable(JSContext* cx, JS::Handle<JSObject*> obj,
+ const Prefable<T>* props) {
+ MOZ_ASSERT(props);
+ MOZ_ASSERT(props->specs);
+ do {
+ // Define if enabled
+ if (props->isEnabled(cx, obj)) {
+ if (!Define(cx, obj, props->specs)) {
+ return false;
+ }
+ }
+ } while ((++props)->specs);
+ return true;
+}
+
+bool DefineLegacyUnforgeableMethods(
+ JSContext* cx, JS::Handle<JSObject*> obj,
+ const Prefable<const JSFunctionSpec>* props) {
+ return DefinePrefable(cx, obj, props);
+}
+
+bool DefineLegacyUnforgeableAttributes(
+ JSContext* cx, JS::Handle<JSObject*> obj,
+ const Prefable<const JSPropertySpec>* props) {
+ return DefinePrefable(cx, obj, props);
+}
+
+// We should use JSFunction objects for interface objects, but we need a custom
+// hasInstance hook because we have new interface objects on prototype chains of
+// old (XPConnect-based) bindings. We also need Xrays and arbitrary numbers of
+// reserved slots (e.g. for named constructors). So we define a custom
+// funToString ObjectOps member for interface objects.
+JSString* InterfaceObjectToString(JSContext* aCx, JS::Handle<JSObject*> aObject,
+ bool /* isToSource */) {
+ const JSClass* clasp = JS::GetClass(aObject);
+ MOZ_ASSERT(IsDOMIfaceAndProtoClass(clasp));
+
+ const DOMIfaceAndProtoJSClass* ifaceAndProtoJSClass =
+ DOMIfaceAndProtoJSClass::FromJSClass(clasp);
+ return JS_NewStringCopyZ(aCx, ifaceAndProtoJSClass->mFunToString);
+}
+
+bool Constructor(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ const JS::Value& v = js::GetFunctionNativeReserved(
+ &args.callee(), CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT);
+ const JSNativeHolder* nativeHolder =
+ static_cast<const JSNativeHolder*>(v.toPrivate());
+ return (nativeHolder->mNative)(cx, argc, vp);
+}
+
+static JSObject* CreateConstructor(JSContext* cx, JS::Handle<JSObject*> global,
+ const char* name,
+ const JSNativeHolder* nativeHolder,
+ unsigned ctorNargs) {
+ JSFunction* fun = js::NewFunctionWithReserved(cx, Constructor, ctorNargs,
+ JSFUN_CONSTRUCTOR, name);
+ if (!fun) {
+ return nullptr;
+ }
+
+ JSObject* constructor = JS_GetFunctionObject(fun);
+ js::SetFunctionNativeReserved(
+ constructor, CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT,
+ JS::PrivateValue(const_cast<JSNativeHolder*>(nativeHolder)));
+ return constructor;
+}
+
+static bool DefineConstructor(JSContext* cx, JS::Handle<JSObject*> global,
+ JS::Handle<jsid> name,
+ JS::Handle<JSObject*> constructor) {
+ bool alreadyDefined;
+ if (!JS_AlreadyHasOwnPropertyById(cx, global, name, &alreadyDefined)) {
+ return false;
+ }
+
+ // This is Enumerable: False per spec.
+ return alreadyDefined ||
+ JS_DefinePropertyById(cx, global, name, constructor, JSPROP_RESOLVING);
+}
+
+static bool DefineConstructor(JSContext* cx, JS::Handle<JSObject*> global,
+ const char* name,
+ JS::Handle<JSObject*> constructor) {
+ JSString* nameStr = JS_AtomizeString(cx, name);
+ if (!nameStr) {
+ return false;
+ }
+ JS::Rooted<JS::PropertyKey> nameKey(cx, JS::PropertyKey::NonIntAtom(nameStr));
+ return DefineConstructor(cx, global, nameKey, constructor);
+}
+
+static bool DefineToStringTag(JSContext* cx, JS::Handle<JSObject*> obj,
+ JS::Handle<JSString*> class_name) {
+ JS::Rooted<jsid> toStringTagId(
+ cx, JS::GetWellKnownSymbolKey(cx, JS::SymbolCode::toStringTag));
+ return JS_DefinePropertyById(cx, obj, toStringTagId, class_name,
+ JSPROP_READONLY);
+}
+
+// name must be an atom (or JS::PropertyKey::NonIntAtom will assert).
+static JSObject* CreateInterfaceObject(
+ JSContext* cx, JS::Handle<JSObject*> global,
+ JS::Handle<JSObject*> constructorProto, const JSClass* constructorClass,
+ unsigned ctorNargs, const LegacyFactoryFunction* namedConstructors,
+ JS::Handle<JSObject*> proto, const NativeProperties* properties,
+ const NativeProperties* chromeOnlyProperties, JS::Handle<JSString*> name,
+ bool isChrome, bool defineOnGlobal, const char* const* legacyWindowAliases,
+ bool isNamespace) {
+ JS::Rooted<JSObject*> constructor(cx);
+ MOZ_ASSERT(constructorProto);
+ MOZ_ASSERT(constructorClass);
+ constructor =
+ JS_NewObjectWithGivenProto(cx, constructorClass, constructorProto);
+ if (!constructor) {
+ return nullptr;
+ }
+
+ if (!isNamespace) {
+ if (!JS_DefineProperty(cx, constructor, "length", ctorNargs,
+ JSPROP_READONLY)) {
+ return nullptr;
+ }
+
+ if (!JS_DefineProperty(cx, constructor, "name", name, JSPROP_READONLY)) {
+ return nullptr;
+ }
+ }
+
+ if (DOMIfaceAndProtoJSClass::FromJSClass(constructorClass)
+ ->wantsInterfaceHasInstance) {
+ if (StaticPrefs::dom_webidl_crosscontext_hasinstance_enabled()) {
+ JS::Rooted<jsid> hasInstanceId(
+ cx, JS::GetWellKnownSymbolKey(cx, JS::SymbolCode::hasInstance));
+ if (!JS_DefineFunctionById(
+ cx, constructor, hasInstanceId, InterfaceHasInstance, 1,
+ // Flags match those of Function[Symbol.hasInstance]
+ JSPROP_READONLY | JSPROP_PERMANENT)) {
+ return nullptr;
+ }
+ }
+
+ if (isChrome && !JS_DefineFunction(cx, constructor, "isInstance",
+ InterfaceIsInstance, 1,
+ // Don't bother making it enumerable
+ 0)) {
+ return nullptr;
+ }
+ }
+
+ if (properties) {
+ if (properties->HasStaticMethods() &&
+ !DefinePrefable(cx, constructor, properties->StaticMethods())) {
+ return nullptr;
+ }
+
+ if (properties->HasStaticAttributes() &&
+ !DefinePrefable(cx, constructor, properties->StaticAttributes())) {
+ return nullptr;
+ }
+
+ if (properties->HasConstants() &&
+ !DefinePrefable(cx, constructor, properties->Constants())) {
+ return nullptr;
+ }
+ }
+
+ if (chromeOnlyProperties && isChrome) {
+ if (chromeOnlyProperties->HasStaticMethods() &&
+ !DefinePrefable(cx, constructor,
+ chromeOnlyProperties->StaticMethods())) {
+ return nullptr;
+ }
+
+ if (chromeOnlyProperties->HasStaticAttributes() &&
+ !DefinePrefable(cx, constructor,
+ chromeOnlyProperties->StaticAttributes())) {
+ return nullptr;
+ }
+
+ if (chromeOnlyProperties->HasConstants() &&
+ !DefinePrefable(cx, constructor, chromeOnlyProperties->Constants())) {
+ return nullptr;
+ }
+ }
+
+ if (isNamespace && !DefineToStringTag(cx, constructor, name)) {
+ return nullptr;
+ }
+
+ if (proto && !JS_LinkConstructorAndPrototype(cx, constructor, proto)) {
+ return nullptr;
+ }
+
+ JS::Rooted<jsid> nameStr(cx, JS::PropertyKey::NonIntAtom(name));
+ if (defineOnGlobal && !DefineConstructor(cx, global, nameStr, constructor)) {
+ return nullptr;
+ }
+
+ if (legacyWindowAliases && NS_IsMainThread()) {
+ for (; *legacyWindowAliases; ++legacyWindowAliases) {
+ if (!DefineConstructor(cx, global, *legacyWindowAliases, constructor)) {
+ return nullptr;
+ }
+ }
+ }
+
+ if (namedConstructors) {
+ int namedConstructorSlot = DOM_INTERFACE_SLOTS_BASE;
+ while (namedConstructors->mName) {
+ JS::Rooted<JSObject*> namedConstructor(
+ cx, CreateConstructor(cx, global, namedConstructors->mName,
+ &namedConstructors->mHolder,
+ namedConstructors->mNargs));
+ if (!namedConstructor ||
+ !JS_DefineProperty(cx, namedConstructor, "prototype", proto,
+ JSPROP_PERMANENT | JSPROP_READONLY) ||
+ (defineOnGlobal &&
+ !DefineConstructor(cx, global, namedConstructors->mName,
+ namedConstructor))) {
+ return nullptr;
+ }
+ JS::SetReservedSlot(constructor, namedConstructorSlot++,
+ JS::ObjectValue(*namedConstructor));
+ ++namedConstructors;
+ }
+ }
+
+ return constructor;
+}
+
+static JSObject* CreateInterfacePrototypeObject(
+ JSContext* cx, JS::Handle<JSObject*> global,
+ JS::Handle<JSObject*> parentProto, const JSClass* protoClass,
+ const NativeProperties* properties,
+ const NativeProperties* chromeOnlyProperties,
+ const char* const* unscopableNames, JS::Handle<JSString*> name,
+ bool isGlobal) {
+ JS::Rooted<JSObject*> ourProto(
+ cx, JS_NewObjectWithGivenProto(cx, protoClass, parentProto));
+ if (!ourProto ||
+ // We don't try to define properties on the global's prototype; those
+ // properties go on the global itself.
+ (!isGlobal &&
+ !DefineProperties(cx, ourProto, properties, chromeOnlyProperties))) {
+ return nullptr;
+ }
+
+ if (unscopableNames) {
+ JS::Rooted<JSObject*> unscopableObj(
+ cx, JS_NewObjectWithGivenProto(cx, nullptr, nullptr));
+ if (!unscopableObj) {
+ return nullptr;
+ }
+
+ for (; *unscopableNames; ++unscopableNames) {
+ if (!JS_DefineProperty(cx, unscopableObj, *unscopableNames,
+ JS::TrueHandleValue, JSPROP_ENUMERATE)) {
+ return nullptr;
+ }
+ }
+
+ JS::Rooted<jsid> unscopableId(
+ cx, JS::GetWellKnownSymbolKey(cx, JS::SymbolCode::unscopables));
+ // Readonly and non-enumerable to match Array.prototype.
+ if (!JS_DefinePropertyById(cx, ourProto, unscopableId, unscopableObj,
+ JSPROP_READONLY)) {
+ return nullptr;
+ }
+ }
+
+ if (!DefineToStringTag(cx, ourProto, name)) {
+ return nullptr;
+ }
+
+ return ourProto;
+}
+
+bool DefineProperties(JSContext* cx, JS::Handle<JSObject*> obj,
+ const NativeProperties* properties,
+ const NativeProperties* chromeOnlyProperties) {
+ if (properties) {
+ if (properties->HasMethods() &&
+ !DefinePrefable(cx, obj, properties->Methods())) {
+ return false;
+ }
+
+ if (properties->HasAttributes() &&
+ !DefinePrefable(cx, obj, properties->Attributes())) {
+ return false;
+ }
+
+ if (properties->HasConstants() &&
+ !DefinePrefable(cx, obj, properties->Constants())) {
+ return false;
+ }
+ }
+
+ if (chromeOnlyProperties) {
+ if (chromeOnlyProperties->HasMethods() &&
+ !DefinePrefable(cx, obj, chromeOnlyProperties->Methods())) {
+ return false;
+ }
+
+ if (chromeOnlyProperties->HasAttributes() &&
+ !DefinePrefable(cx, obj, chromeOnlyProperties->Attributes())) {
+ return false;
+ }
+
+ if (chromeOnlyProperties->HasConstants() &&
+ !DefinePrefable(cx, obj, chromeOnlyProperties->Constants())) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void CreateInterfaceObjects(
+ JSContext* cx, JS::Handle<JSObject*> global,
+ JS::Handle<JSObject*> protoProto, const JSClass* protoClass,
+ JS::Heap<JSObject*>* protoCache, JS::Handle<JSObject*> constructorProto,
+ const JSClass* constructorClass, unsigned ctorNargs,
+ bool isConstructorChromeOnly,
+ const LegacyFactoryFunction* namedConstructors,
+ JS::Heap<JSObject*>* constructorCache, const NativeProperties* properties,
+ const NativeProperties* chromeOnlyProperties, const char* name,
+ bool defineOnGlobal, const char* const* unscopableNames, bool isGlobal,
+ const char* const* legacyWindowAliases, bool isNamespace) {
+ MOZ_ASSERT(protoClass || constructorClass, "Need at least one class!");
+ MOZ_ASSERT(
+ !((properties &&
+ (properties->HasMethods() || properties->HasAttributes())) ||
+ (chromeOnlyProperties && (chromeOnlyProperties->HasMethods() ||
+ chromeOnlyProperties->HasAttributes()))) ||
+ protoClass,
+ "Methods or properties but no protoClass!");
+ MOZ_ASSERT(!((properties && (properties->HasStaticMethods() ||
+ properties->HasStaticAttributes())) ||
+ (chromeOnlyProperties &&
+ (chromeOnlyProperties->HasStaticMethods() ||
+ chromeOnlyProperties->HasStaticAttributes()))) ||
+ constructorClass,
+ "Static methods but no constructorClass!");
+ MOZ_ASSERT(!protoClass == !protoCache,
+ "If, and only if, there is an interface prototype object we need "
+ "to cache it");
+ MOZ_ASSERT(bool(constructorClass) == bool(constructorCache),
+ "If, and only if, there is an interface object we need to cache "
+ "it");
+ MOZ_ASSERT(constructorProto || !constructorClass,
+ "Must have a constructor proto if we plan to create a constructor "
+ "object");
+
+ bool isChrome = nsContentUtils::ThreadsafeIsSystemCaller(cx);
+
+ JS::Rooted<JSString*> nameStr(cx, JS_AtomizeString(cx, name));
+ if (!nameStr) {
+ return;
+ }
+
+ JS::Rooted<JSObject*> proto(cx);
+ if (protoClass) {
+ proto = CreateInterfacePrototypeObject(
+ cx, global, protoProto, protoClass, properties,
+ isChrome ? chromeOnlyProperties : nullptr, unscopableNames, nameStr,
+ isGlobal);
+ if (!proto) {
+ return;
+ }
+
+ *protoCache = proto;
+ } else {
+ MOZ_ASSERT(!proto);
+ }
+
+ JSObject* interface;
+ if (constructorClass) {
+ interface = CreateInterfaceObject(
+ cx, global, constructorProto, constructorClass,
+ (isChrome || !isConstructorChromeOnly) ? ctorNargs : 0,
+ namedConstructors, proto, properties, chromeOnlyProperties, nameStr,
+ isChrome, defineOnGlobal, legacyWindowAliases, isNamespace);
+ if (!interface) {
+ if (protoCache) {
+ // If we fail we need to make sure to clear the value of protoCache we
+ // set above.
+ *protoCache = nullptr;
+ }
+ return;
+ }
+ *constructorCache = interface;
+ }
+}
+
+// Only set aAllowNativeWrapper to false if you really know you need it; if in
+// doubt use true. Setting it to false disables security wrappers.
+static bool NativeInterface2JSObjectAndThrowIfFailed(
+ JSContext* aCx, JS::Handle<JSObject*> aScope,
+ JS::MutableHandle<JS::Value> aRetval, xpcObjectHelper& aHelper,
+ const nsIID* aIID, bool aAllowNativeWrapper) {
+ js::AssertSameCompartment(aCx, aScope);
+ nsresult rv;
+ // Inline some logic from XPCConvert::NativeInterfaceToJSObject that we need
+ // on all threads.
+ nsWrapperCache* cache = aHelper.GetWrapperCache();
+
+ if (cache) {
+ JS::Rooted<JSObject*> obj(aCx, cache->GetWrapper());
+ if (!obj) {
+ obj = cache->WrapObject(aCx, nullptr);
+ if (!obj) {
+ return Throw(aCx, NS_ERROR_UNEXPECTED);
+ }
+ }
+
+ if (aAllowNativeWrapper && !JS_WrapObject(aCx, &obj)) {
+ return false;
+ }
+
+ aRetval.setObject(*obj);
+ return true;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!XPCConvert::NativeInterface2JSObject(aCx, aRetval, aHelper, aIID,
+ aAllowNativeWrapper, &rv)) {
+ // I can't tell if NativeInterface2JSObject throws JS exceptions
+ // or not. This is a sloppy stab at the right semantics; the
+ // method really ought to be fixed to behave consistently.
+ if (!JS_IsExceptionPending(aCx)) {
+ Throw(aCx, NS_FAILED(rv) ? rv : NS_ERROR_UNEXPECTED);
+ }
+ return false;
+ }
+ return true;
+}
+
+bool TryPreserveWrapper(JS::Handle<JSObject*> obj) {
+ MOZ_ASSERT(IsDOMObject(obj));
+
+ // nsISupports objects are special cased because DOM proxies are nsISupports
+ // and have addProperty hooks that do more than wrapper preservation (so we
+ // don't want to call them).
+ if (nsISupports* native = UnwrapDOMObjectToISupports(obj)) {
+ nsWrapperCache* cache = nullptr;
+ CallQueryInterface(native, &cache);
+ if (cache) {
+ cache->PreserveWrapper(native);
+ }
+ return true;
+ }
+
+ // The addProperty hook for WebIDL classes does wrapper preservation, and
+ // nothing else, so call it, if present.
+
+ const JSClass* clasp = JS::GetClass(obj);
+ const DOMJSClass* domClass = GetDOMClass(clasp);
+
+ // We expect all proxies to be nsISupports.
+ MOZ_RELEASE_ASSERT(clasp->isNativeObject(),
+ "Should not call addProperty for proxies.");
+
+ JSAddPropertyOp addProperty = clasp->getAddProperty();
+ if (!addProperty) {
+ return true;
+ }
+
+ // The class should have an addProperty hook iff it is a CC participant.
+ MOZ_RELEASE_ASSERT(domClass->mParticipant);
+
+ JS::Rooted<jsid> dummyId(RootingCx());
+ JS::Rooted<JS::Value> dummyValue(RootingCx());
+ return addProperty(nullptr, obj, dummyId, dummyValue);
+}
+
+bool HasReleasedWrapper(JS::Handle<JSObject*> obj) {
+ MOZ_ASSERT(obj);
+ MOZ_ASSERT(IsDOMObject(obj));
+
+ nsWrapperCache* cache = nullptr;
+ if (nsISupports* native = UnwrapDOMObjectToISupports(obj)) {
+ CallQueryInterface(native, &cache);
+ } else {
+ const JSClass* clasp = JS::GetClass(obj);
+ const DOMJSClass* domClass = GetDOMClass(clasp);
+
+ // We expect all proxies to be nsISupports.
+ MOZ_RELEASE_ASSERT(clasp->isNativeObject(),
+ "Should not call getWrapperCache for proxies.");
+
+ WrapperCacheGetter getter = domClass->mWrapperCacheGetter;
+
+ if (getter) {
+ // If the class has a wrapper cache getter it must be a CC participant.
+ MOZ_RELEASE_ASSERT(domClass->mParticipant);
+
+ cache = getter(obj);
+ }
+ }
+
+ return cache && !cache->PreservingWrapper();
+}
+
+// Can only be called with a DOM JSClass.
+bool InstanceClassHasProtoAtDepth(const JSClass* clasp, uint32_t protoID,
+ uint32_t depth) {
+ const DOMJSClass* domClass = DOMJSClass::FromJSClass(clasp);
+ return static_cast<uint32_t>(domClass->mInterfaceChain[depth]) == protoID;
+}
+
+// Only set allowNativeWrapper to false if you really know you need it; if in
+// doubt use true. Setting it to false disables security wrappers.
+bool XPCOMObjectToJsval(JSContext* cx, JS::Handle<JSObject*> scope,
+ xpcObjectHelper& helper, const nsIID* iid,
+ bool allowNativeWrapper,
+ JS::MutableHandle<JS::Value> rval) {
+ return NativeInterface2JSObjectAndThrowIfFailed(cx, scope, rval, helper, iid,
+ allowNativeWrapper);
+}
+
+bool VariantToJsval(JSContext* aCx, nsIVariant* aVariant,
+ JS::MutableHandle<JS::Value> aRetval) {
+ nsresult rv;
+ if (!XPCVariant::VariantDataToJS(aCx, aVariant, &rv, aRetval)) {
+ // Does it throw? Who knows
+ if (!JS_IsExceptionPending(aCx)) {
+ Throw(aCx, NS_FAILED(rv) ? rv : NS_ERROR_UNEXPECTED);
+ }
+ return false;
+ }
+
+ return true;
+}
+
+bool WrapObject(JSContext* cx, const WindowProxyHolder& p,
+ JS::MutableHandle<JS::Value> rval) {
+ return ToJSValue(cx, p, rval);
+}
+
+static int ComparePropertyInfosAtIndices(const void* aElement1,
+ const void* aElement2,
+ void* aClosure) {
+ const uint16_t index1 = *static_cast<const uint16_t*>(aElement1);
+ const uint16_t index2 = *static_cast<const uint16_t*>(aElement2);
+ const PropertyInfo* infos = static_cast<PropertyInfo*>(aClosure);
+
+ return PropertyInfo::Compare(infos[index1], infos[index2]);
+}
+
+// {JSPropertySpec,JSFunctionSpec} use {JSPropertySpec,JSFunctionSpec}::Name
+// and ConstantSpec uses `const char*` for name field.
+static inline JSPropertySpec::Name ToPropertySpecName(
+ JSPropertySpec::Name name) {
+ return name;
+}
+
+static inline JSPropertySpec::Name ToPropertySpecName(const char* name) {
+ return JSPropertySpec::Name(name);
+}
+
+template <typename SpecT>
+static bool InitPropertyInfos(JSContext* cx, const Prefable<SpecT>* pref,
+ PropertyInfo* infos, PropertyType type) {
+ MOZ_ASSERT(pref);
+ MOZ_ASSERT(pref->specs);
+
+ // Index of the Prefable that contains the id for the current PropertyInfo.
+ uint32_t prefIndex = 0;
+
+ do {
+ // We ignore whether the set of ids is enabled and just intern all the IDs,
+ // because this is only done once per application runtime.
+ const SpecT* spec = pref->specs;
+ // Index of the property/function/constant spec for our current PropertyInfo
+ // in the "specs" array of the relevant Prefable.
+ uint32_t specIndex = 0;
+ do {
+ jsid id;
+ if (!JS::PropertySpecNameToPermanentId(cx, ToPropertySpecName(spec->name),
+ &id)) {
+ return false;
+ }
+ infos->SetId(id);
+ infos->type = type;
+ infos->prefIndex = prefIndex;
+ infos->specIndex = specIndex++;
+ ++infos;
+ } while ((++spec)->name);
+ ++prefIndex;
+ } while ((++pref)->specs);
+
+ return true;
+}
+
+#define INIT_PROPERTY_INFOS_IF_DEFINED(TypeName) \
+ { \
+ if (nativeProperties->Has##TypeName##s() && \
+ !InitPropertyInfos(cx, nativeProperties->TypeName##s(), \
+ nativeProperties->TypeName##PropertyInfos(), \
+ e##TypeName)) { \
+ return false; \
+ } \
+ }
+
+static bool InitPropertyInfos(JSContext* cx,
+ const NativeProperties* nativeProperties) {
+ INIT_PROPERTY_INFOS_IF_DEFINED(StaticMethod);
+ INIT_PROPERTY_INFOS_IF_DEFINED(StaticAttribute);
+ INIT_PROPERTY_INFOS_IF_DEFINED(Method);
+ INIT_PROPERTY_INFOS_IF_DEFINED(Attribute);
+ INIT_PROPERTY_INFOS_IF_DEFINED(UnforgeableMethod);
+ INIT_PROPERTY_INFOS_IF_DEFINED(UnforgeableAttribute);
+ INIT_PROPERTY_INFOS_IF_DEFINED(Constant);
+
+ // Initialize and sort the index array.
+ uint16_t* indices = nativeProperties->sortedPropertyIndices;
+ for (unsigned int i = 0; i < nativeProperties->propertyInfoCount; ++i) {
+ indices[i] = i;
+ }
+ // ComparePropertyInfosAtIndices() doesn't actually modify the PropertyInfo
+ // array, so the const_cast here is OK in spite of the signature of
+ // NS_QuickSort().
+ NS_QuickSort(indices, nativeProperties->propertyInfoCount, sizeof(uint16_t),
+ ComparePropertyInfosAtIndices,
+ const_cast<PropertyInfo*>(nativeProperties->PropertyInfos()));
+
+ return true;
+}
+
+#undef INIT_PROPERTY_INFOS_IF_DEFINED
+
+static inline bool InitPropertyInfos(
+ JSContext* aCx, const NativePropertiesHolder& nativeProperties) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!*nativeProperties.inited) {
+ if (nativeProperties.regular &&
+ !InitPropertyInfos(aCx, nativeProperties.regular)) {
+ return false;
+ }
+ if (nativeProperties.chromeOnly &&
+ !InitPropertyInfos(aCx, nativeProperties.chromeOnly)) {
+ return false;
+ }
+ *nativeProperties.inited = true;
+ }
+
+ return true;
+}
+
+void GetInterfaceImpl(JSContext* aCx, nsIInterfaceRequestor* aRequestor,
+ nsWrapperCache* aCache, JS::Handle<JS::Value> aIID,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aError) {
+ Maybe<nsIID> iid = xpc::JSValue2ID(aCx, aIID);
+ if (!iid) {
+ aError.Throw(NS_ERROR_XPC_BAD_CONVERT_JS);
+ return;
+ }
+
+ RefPtr<nsISupports> result;
+ aError = aRequestor->GetInterface(*iid, getter_AddRefs(result));
+ if (aError.Failed()) {
+ return;
+ }
+
+ if (!WrapObject(aCx, result, iid.ptr(), aRetval)) {
+ aError.Throw(NS_ERROR_FAILURE);
+ }
+}
+
+bool ThrowingConstructor(JSContext* cx, unsigned argc, JS::Value* vp) {
+ // Cast nullptr to void* to work around
+ // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100666
+ return ThrowErrorMessage<MSG_ILLEGAL_CONSTRUCTOR>(cx, (void*)nullptr);
+}
+
+bool ThrowConstructorWithoutNew(JSContext* cx, const char* name) {
+ return ThrowErrorMessage<MSG_CONSTRUCTOR_WITHOUT_NEW>(cx, name);
+}
+
+inline const NativePropertyHooks* GetNativePropertyHooksFromConstructorFunction(
+ JS::Handle<JSObject*> obj) {
+ MOZ_ASSERT(JS_IsNativeFunction(obj, Constructor));
+ const JS::Value& v = js::GetFunctionNativeReserved(
+ obj, CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT);
+ const JSNativeHolder* nativeHolder =
+ static_cast<const JSNativeHolder*>(v.toPrivate());
+ return nativeHolder->mPropertyHooks;
+}
+
+inline const NativePropertyHooks* GetNativePropertyHooks(
+ JSContext* cx, JS::Handle<JSObject*> obj, DOMObjectType& type) {
+ const JSClass* clasp = JS::GetClass(obj);
+
+ const DOMJSClass* domClass = GetDOMClass(clasp);
+ if (domClass) {
+ bool isGlobal = (clasp->flags & JSCLASS_DOM_GLOBAL) != 0;
+ type = isGlobal ? eGlobalInstance : eInstance;
+ return domClass->mNativeHooks;
+ }
+
+ if (JS_ObjectIsFunction(obj)) {
+ type = eInterface;
+ return GetNativePropertyHooksFromConstructorFunction(obj);
+ }
+
+ MOZ_ASSERT(IsDOMIfaceAndProtoClass(JS::GetClass(obj)));
+ const DOMIfaceAndProtoJSClass* ifaceAndProtoJSClass =
+ DOMIfaceAndProtoJSClass::FromJSClass(JS::GetClass(obj));
+ type = ifaceAndProtoJSClass->mType;
+ return ifaceAndProtoJSClass->mNativeHooks;
+}
+
+static JSObject* XrayCreateFunction(JSContext* cx,
+ JS::Handle<JSObject*> wrapper,
+ JSNativeWrapper native, unsigned nargs,
+ JS::Handle<jsid> id) {
+ JSFunction* fun;
+ if (id.isString()) {
+ fun = js::NewFunctionByIdWithReserved(cx, native.op, nargs, 0, id);
+ } else {
+ // Can't pass this id (probably a symbol) to NewFunctionByIdWithReserved;
+ // just use an empty name for lack of anything better.
+ fun = js::NewFunctionWithReserved(cx, native.op, nargs, 0, nullptr);
+ }
+
+ if (!fun) {
+ return nullptr;
+ }
+
+ SET_JITINFO(fun, native.info);
+ JSObject* obj = JS_GetFunctionObject(fun);
+ js::SetFunctionNativeReserved(obj, XRAY_DOM_FUNCTION_PARENT_WRAPPER_SLOT,
+ JS::ObjectValue(*wrapper));
+#ifdef DEBUG
+ js::SetFunctionNativeReserved(obj, XRAY_DOM_FUNCTION_NATIVE_SLOT_FOR_SELF,
+ JS::ObjectValue(*obj));
+#endif
+ return obj;
+}
+
+struct IdToIndexComparator {
+ // The id we're searching for.
+ const jsid& mId;
+ // Whether we're searching for static operations.
+ const bool mStatic;
+ // The list of ids we're searching in.
+ const PropertyInfo* mInfos;
+
+ IdToIndexComparator(const jsid& aId, DOMObjectType aType,
+ const PropertyInfo* aInfos)
+ : mId(aId),
+ mStatic(aType == eInterface || aType == eNamespace),
+ mInfos(aInfos) {}
+ int operator()(const uint16_t aIndex) const {
+ const PropertyInfo& info = mInfos[aIndex];
+ if (mId.asRawBits() == info.Id().asRawBits()) {
+ if (info.type != eMethod && info.type != eStaticMethod) {
+ return 0;
+ }
+
+ if (mStatic == info.IsStaticMethod()) {
+ // We're looking for static properties and we've found a static one for
+ // the right name.
+ return 0;
+ }
+
+ // Static operations are sorted before others by PropertyInfo::Compare.
+ return mStatic ? -1 : 1;
+ }
+
+ return mId.asRawBits() < info.Id().asRawBits() ? -1 : 1;
+ }
+};
+
+static const PropertyInfo* XrayFindOwnPropertyInfo(
+ JSContext* cx, DOMObjectType type, JS::Handle<jsid> id,
+ const NativeProperties* nativeProperties) {
+ if ((type == eInterfacePrototype || type == eGlobalInstance) &&
+ MOZ_UNLIKELY(nativeProperties->iteratorAliasMethodIndex >= 0) &&
+ id.isWellKnownSymbol(JS::SymbolCode::iterator)) {
+ return nativeProperties->MethodPropertyInfos() +
+ nativeProperties->iteratorAliasMethodIndex;
+ }
+
+ size_t idx;
+ const uint16_t* sortedPropertyIndices =
+ nativeProperties->sortedPropertyIndices;
+ const PropertyInfo* propertyInfos = nativeProperties->PropertyInfos();
+
+ if (BinarySearchIf(sortedPropertyIndices, 0,
+ nativeProperties->propertyInfoCount,
+ IdToIndexComparator(id, type, propertyInfos), &idx)) {
+ return propertyInfos + sortedPropertyIndices[idx];
+ }
+
+ return nullptr;
+}
+
+static bool XrayResolveAttribute(
+ JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> obj,
+ JS::Handle<jsid> id, const Prefable<const JSPropertySpec>& pref,
+ const JSPropertySpec& attrSpec,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc,
+ bool& cacheOnHolder) {
+ if (!pref.isEnabled(cx, obj)) {
+ return true;
+ }
+
+ MOZ_ASSERT(attrSpec.isAccessor());
+
+ MOZ_ASSERT(
+ !attrSpec.isSelfHosted(),
+ "Bad JSPropertySpec declaration: unsupported self-hosted accessor");
+
+ cacheOnHolder = true;
+
+ JS::Rooted<jsid> getterId(cx);
+ if (!JS::ToGetterId(cx, id, &getterId)) {
+ return false;
+ }
+
+ // Because of centralization, we need to make sure we fault in the JitInfos as
+ // well. At present, until the JSAPI changes, the easiest way to do this is
+ // wrap them up as functions ourselves.
+
+ // They all have getters, so we can just make it.
+ JS::Rooted<JSObject*> getter(
+ cx, XrayCreateFunction(cx, wrapper, attrSpec.u.accessors.getter.native, 0,
+ getterId));
+ if (!getter) {
+ return false;
+ }
+
+ JS::Rooted<JSObject*> setter(cx);
+ if (attrSpec.u.accessors.setter.native.op) {
+ JS::Rooted<jsid> setterId(cx);
+ if (!JS::ToSetterId(cx, id, &setterId)) {
+ return false;
+ }
+
+ // We have a setter! Make it.
+ setter = XrayCreateFunction(cx, wrapper, attrSpec.u.accessors.setter.native,
+ 1, setterId);
+ if (!setter) {
+ return false;
+ }
+ }
+
+ desc.set(Some(
+ JS::PropertyDescriptor::Accessor(getter, setter, attrSpec.attributes())));
+ return true;
+}
+
+static bool XrayResolveMethod(
+ JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> obj,
+ JS::Handle<jsid> id, const Prefable<const JSFunctionSpec>& pref,
+ const JSFunctionSpec& methodSpec,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc,
+ bool& cacheOnHolder) {
+ if (!pref.isEnabled(cx, obj)) {
+ return true;
+ }
+
+ cacheOnHolder = true;
+
+ JSObject* funobj;
+ if (methodSpec.selfHostedName) {
+ JSFunction* fun = JS::GetSelfHostedFunction(cx, methodSpec.selfHostedName,
+ id, methodSpec.nargs);
+ if (!fun) {
+ return false;
+ }
+ MOZ_ASSERT(!methodSpec.call.op,
+ "Bad FunctionSpec declaration: non-null native");
+ MOZ_ASSERT(!methodSpec.call.info,
+ "Bad FunctionSpec declaration: non-null jitinfo");
+ funobj = JS_GetFunctionObject(fun);
+ } else {
+ funobj =
+ XrayCreateFunction(cx, wrapper, methodSpec.call, methodSpec.nargs, id);
+ if (!funobj) {
+ return false;
+ }
+ }
+
+ desc.set(Some(JS::PropertyDescriptor::Data(JS::ObjectValue(*funobj),
+ methodSpec.flags)));
+ return true;
+}
+
+static bool XrayResolveConstant(
+ JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> obj,
+ JS::Handle<jsid>, const Prefable<const ConstantSpec>& pref,
+ const ConstantSpec& constantSpec,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc,
+ bool& cacheOnHolder) {
+ if (!pref.isEnabled(cx, obj)) {
+ return true;
+ }
+
+ cacheOnHolder = true;
+
+ desc.set(Some(JS::PropertyDescriptor::Data(
+ constantSpec.value, {JS::PropertyAttribute::Enumerable})));
+ return true;
+}
+
+#define RESOLVE_CASE(PropType, SpecType, Resolver) \
+ case e##PropType: { \
+ MOZ_ASSERT(nativeProperties->Has##PropType##s()); \
+ const Prefable<const SpecType>& pref = \
+ nativeProperties->PropType##s()[propertyInfo.prefIndex]; \
+ return Resolver(cx, wrapper, obj, id, pref, \
+ pref.specs[propertyInfo.specIndex], desc, cacheOnHolder); \
+ }
+
+static bool XrayResolveProperty(
+ JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> obj,
+ JS::Handle<jsid> id, JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc,
+ bool& cacheOnHolder, DOMObjectType type,
+ const NativeProperties* nativeProperties,
+ const PropertyInfo& propertyInfo) {
+ MOZ_ASSERT(type != eGlobalInterfacePrototype);
+
+ // Make sure we resolve for matched object type.
+ switch (propertyInfo.type) {
+ case eStaticMethod:
+ case eStaticAttribute:
+ if (type != eInterface && type != eNamespace) {
+ return true;
+ }
+ break;
+ case eMethod:
+ case eAttribute:
+ if (type != eGlobalInstance && type != eInterfacePrototype) {
+ return true;
+ }
+ break;
+ case eUnforgeableMethod:
+ case eUnforgeableAttribute:
+ if (!IsInstance(type)) {
+ return true;
+ }
+ break;
+ case eConstant:
+ if (IsInstance(type)) {
+ return true;
+ }
+ break;
+ }
+
+ switch (propertyInfo.type) {
+ RESOLVE_CASE(StaticMethod, JSFunctionSpec, XrayResolveMethod)
+ RESOLVE_CASE(StaticAttribute, JSPropertySpec, XrayResolveAttribute)
+ RESOLVE_CASE(Method, JSFunctionSpec, XrayResolveMethod)
+ RESOLVE_CASE(Attribute, JSPropertySpec, XrayResolveAttribute)
+ RESOLVE_CASE(UnforgeableMethod, JSFunctionSpec, XrayResolveMethod)
+ RESOLVE_CASE(UnforgeableAttribute, JSPropertySpec, XrayResolveAttribute)
+ RESOLVE_CASE(Constant, ConstantSpec, XrayResolveConstant)
+ }
+
+ return true;
+}
+
+#undef RESOLVE_CASE
+
+static bool ResolvePrototypeOrConstructor(
+ JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> obj,
+ size_t protoAndIfaceCacheIndex, unsigned attrs,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc,
+ bool& cacheOnHolder) {
+ JS::Rooted<JSObject*> global(cx, JS::GetNonCCWObjectGlobal(obj));
+ {
+ JSAutoRealm ar(cx, global);
+ ProtoAndIfaceCache& protoAndIfaceCache = *GetProtoAndIfaceCache(global);
+ // This function is called when resolving the "constructor" and "prototype"
+ // properties of Xrays for DOM prototypes and constructors respectively.
+ // This means the relevant Xray exists, which means its _target_ exists.
+ // And that means we managed to successfullly create the prototype or
+ // constructor, respectively, and hence must have managed to create the
+ // thing it's pointing to as well. So our entry slot must exist.
+ JSObject* protoOrIface =
+ protoAndIfaceCache.EntrySlotMustExist(protoAndIfaceCacheIndex);
+ MOZ_RELEASE_ASSERT(protoOrIface, "How can this object not exist?");
+
+ cacheOnHolder = true;
+
+ desc.set(Some(
+ JS::PropertyDescriptor::Data(JS::ObjectValue(*protoOrIface), attrs)));
+ }
+ return JS_WrapPropertyDescriptor(cx, desc);
+}
+
+/* static */ bool XrayResolveOwnProperty(
+ JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> obj,
+ JS::Handle<jsid> id, JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc,
+ bool& cacheOnHolder) {
+ MOZ_ASSERT(desc.isNothing());
+ cacheOnHolder = false;
+
+ DOMObjectType type;
+ const NativePropertyHooks* nativePropertyHooks =
+ GetNativePropertyHooks(cx, obj, type);
+ ResolveOwnProperty resolveOwnProperty =
+ nativePropertyHooks->mResolveOwnProperty;
+
+ if (type == eNamedPropertiesObject) {
+ MOZ_ASSERT(!resolveOwnProperty,
+ "Shouldn't have any Xray-visible properties");
+ return true;
+ }
+
+ const NativePropertiesHolder& nativePropertiesHolder =
+ nativePropertyHooks->mNativeProperties;
+
+ if (!InitPropertyInfos(cx, nativePropertiesHolder)) {
+ return false;
+ }
+
+ const NativeProperties* nativeProperties = nullptr;
+ const PropertyInfo* found = nullptr;
+
+ if ((nativeProperties = nativePropertiesHolder.regular)) {
+ found = XrayFindOwnPropertyInfo(cx, type, id, nativeProperties);
+ }
+ if (!found && (nativeProperties = nativePropertiesHolder.chromeOnly) &&
+ xpc::AccessCheck::isChrome(JS::GetCompartment(wrapper))) {
+ found = XrayFindOwnPropertyInfo(cx, type, id, nativeProperties);
+ }
+
+ if (IsInstance(type)) {
+ // Check for unforgeable properties first to prevent names provided by
+ // resolveOwnProperty callback from shadowing them.
+ if (found && (found->type == eUnforgeableMethod ||
+ found->type == eUnforgeableAttribute)) {
+ if (!XrayResolveProperty(cx, wrapper, obj, id, desc, cacheOnHolder, type,
+ nativeProperties, *found)) {
+ return false;
+ }
+
+ if (desc.isSome()) {
+ return true;
+ }
+ }
+
+ if (resolveOwnProperty) {
+ if (!resolveOwnProperty(cx, wrapper, obj, id, desc)) {
+ return false;
+ }
+
+ if (desc.isSome()) {
+ // None of these should be cached on the holder, since they're dynamic.
+ return true;
+ }
+ }
+
+ // For non-global instance Xrays there are no other properties, so return
+ // here for them.
+ if (type != eGlobalInstance) {
+ return true;
+ }
+ } else if (type == eInterface) {
+ if (id.get() == GetJSIDByIndex(cx, XPCJSContext::IDX_PROTOTYPE)) {
+ return nativePropertyHooks->mPrototypeID == prototypes::id::_ID_Count ||
+ ResolvePrototypeOrConstructor(
+ cx, wrapper, obj, nativePropertyHooks->mPrototypeID,
+ JSPROP_PERMANENT | JSPROP_READONLY, desc, cacheOnHolder);
+ }
+
+ if (id.get() == GetJSIDByIndex(cx, XPCJSContext::IDX_ISINSTANCE)) {
+ const JSClass* objClass = JS::GetClass(obj);
+ if (IsDOMIfaceAndProtoClass(objClass) &&
+ DOMIfaceAndProtoJSClass::FromJSClass(objClass)
+ ->wantsInterfaceHasInstance) {
+ cacheOnHolder = true;
+ JSNativeWrapper interfaceIsInstanceWrapper = {InterfaceIsInstance,
+ nullptr};
+ JSObject* funObj =
+ XrayCreateFunction(cx, wrapper, interfaceIsInstanceWrapper, 1, id);
+ if (!funObj) {
+ return false;
+ }
+
+ desc.set(Some(JS::PropertyDescriptor::Data(
+ JS::ObjectValue(*funObj), {JS::PropertyAttribute::Configurable,
+ JS::PropertyAttribute::Writable})));
+ return true;
+ }
+ }
+
+ if (StaticPrefs::dom_webidl_crosscontext_hasinstance_enabled() &&
+ id.isWellKnownSymbol(JS::SymbolCode::hasInstance)) {
+ const JSClass* objClass = JS::GetClass(obj);
+ if (IsDOMIfaceAndProtoClass(objClass) &&
+ DOMIfaceAndProtoJSClass::FromJSClass(objClass)
+ ->wantsInterfaceHasInstance) {
+ cacheOnHolder = true;
+ JSNativeWrapper interfaceHasInstanceWrapper = {InterfaceHasInstance,
+ nullptr};
+ JSObject* funObj =
+ XrayCreateFunction(cx, wrapper, interfaceHasInstanceWrapper, 1, id);
+ if (!funObj) {
+ return false;
+ }
+
+ desc.set(
+ Some(JS::PropertyDescriptor::Data(JS::ObjectValue(*funObj), {})));
+ return true;
+ }
+ }
+ } else if (type == eNamespace) {
+ if (id.isWellKnownSymbol(JS::SymbolCode::toStringTag)) {
+ JS::Rooted<JSString*> nameStr(
+ cx, JS_AtomizeString(cx, JS::GetClass(obj)->name));
+ if (!nameStr) {
+ return false;
+ }
+
+ desc.set(Some(JS::PropertyDescriptor::Data(
+ JS::StringValue(nameStr), {JS::PropertyAttribute::Configurable})));
+ return true;
+ }
+ } else {
+ MOZ_ASSERT(IsInterfacePrototype(type));
+
+ if (id.get() == GetJSIDByIndex(cx, XPCJSContext::IDX_CONSTRUCTOR)) {
+ return nativePropertyHooks->mConstructorID ==
+ constructors::id::_ID_Count ||
+ ResolvePrototypeOrConstructor(cx, wrapper, obj,
+ nativePropertyHooks->mConstructorID,
+ 0, desc, cacheOnHolder);
+ }
+
+ if (id.isWellKnownSymbol(JS::SymbolCode::toStringTag)) {
+ const JSClass* objClass = JS::GetClass(obj);
+ prototypes::ID prototypeID =
+ DOMIfaceAndProtoJSClass::FromJSClass(objClass)->mPrototypeID;
+ JS::Rooted<JSString*> nameStr(
+ cx, JS_AtomizeString(cx, NamesOfInterfacesWithProtos(prototypeID)));
+ if (!nameStr) {
+ return false;
+ }
+
+ desc.set(Some(JS::PropertyDescriptor::Data(
+ JS::StringValue(nameStr), {JS::PropertyAttribute::Configurable})));
+ return true;
+ }
+
+ // The properties for globals live on the instance, so return here as there
+ // are no properties on their interface prototype object.
+ if (type == eGlobalInterfacePrototype) {
+ return true;
+ }
+ }
+
+ if (found && !XrayResolveProperty(cx, wrapper, obj, id, desc, cacheOnHolder,
+ type, nativeProperties, *found)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool XrayDefineProperty(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
+ JS::Handle<JS::PropertyDescriptor> desc,
+ JS::ObjectOpResult& result, bool* done) {
+ if (!js::IsProxy(obj)) return true;
+
+ const DOMProxyHandler* handler = GetDOMProxyHandler(obj);
+ return handler->defineProperty(cx, wrapper, id, desc, result, done);
+}
+
+template <typename SpecType>
+bool XrayAppendPropertyKeys(JSContext* cx, JS::Handle<JSObject*> obj,
+ const Prefable<const SpecType>* pref,
+ const PropertyInfo* infos, unsigned flags,
+ JS::MutableHandleVector<jsid> props) {
+ do {
+ bool prefIsEnabled = pref->isEnabled(cx, obj);
+ if (prefIsEnabled) {
+ const SpecType* spec = pref->specs;
+ do {
+ const jsid id = infos++->Id();
+ if (((flags & JSITER_HIDDEN) ||
+ (spec->attributes() & JSPROP_ENUMERATE)) &&
+ ((flags & JSITER_SYMBOLS) || !id.isSymbol()) && !props.append(id)) {
+ return false;
+ }
+ } while ((++spec)->name);
+ }
+ // Break if we have reached the end of pref.
+ if (!(++pref)->specs) {
+ break;
+ }
+ // Advance infos if the previous pref is disabled. The -1 is required
+ // because there is an end-of-list terminator between pref->specs and
+ // (pref - 1)->specs.
+ if (!prefIsEnabled) {
+ infos += pref->specs - (pref - 1)->specs - 1;
+ }
+ } while (1);
+
+ return true;
+}
+
+template <>
+bool XrayAppendPropertyKeys<ConstantSpec>(
+ JSContext* cx, JS::Handle<JSObject*> obj,
+ const Prefable<const ConstantSpec>* pref, const PropertyInfo* infos,
+ unsigned flags, JS::MutableHandleVector<jsid> props) {
+ do {
+ bool prefIsEnabled = pref->isEnabled(cx, obj);
+ if (prefIsEnabled) {
+ const ConstantSpec* spec = pref->specs;
+ do {
+ if (!props.append(infos++->Id())) {
+ return false;
+ }
+ } while ((++spec)->name);
+ }
+ // Break if we have reached the end of pref.
+ if (!(++pref)->specs) {
+ break;
+ }
+ // Advance infos if the previous pref is disabled. The -1 is required
+ // because there is an end-of-list terminator between pref->specs and
+ // (pref - 1)->specs.
+ if (!prefIsEnabled) {
+ infos += pref->specs - (pref - 1)->specs - 1;
+ }
+ } while (1);
+
+ return true;
+}
+
+#define ADD_KEYS_IF_DEFINED(FieldName) \
+ { \
+ if (nativeProperties->Has##FieldName##s() && \
+ !XrayAppendPropertyKeys(cx, obj, nativeProperties->FieldName##s(), \
+ nativeProperties->FieldName##PropertyInfos(), \
+ flags, props)) { \
+ return false; \
+ } \
+ }
+
+bool XrayOwnPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ JS::Handle<JSObject*> obj, unsigned flags,
+ JS::MutableHandleVector<jsid> props,
+ DOMObjectType type,
+ const NativeProperties* nativeProperties) {
+ MOZ_ASSERT(type != eNamedPropertiesObject);
+
+ if (IsInstance(type)) {
+ ADD_KEYS_IF_DEFINED(UnforgeableMethod);
+ ADD_KEYS_IF_DEFINED(UnforgeableAttribute);
+ if (type == eGlobalInstance) {
+ ADD_KEYS_IF_DEFINED(Method);
+ ADD_KEYS_IF_DEFINED(Attribute);
+ }
+ } else {
+ MOZ_ASSERT(type != eGlobalInterfacePrototype);
+ if (type == eInterface || type == eNamespace) {
+ ADD_KEYS_IF_DEFINED(StaticMethod);
+ ADD_KEYS_IF_DEFINED(StaticAttribute);
+ } else {
+ MOZ_ASSERT(type == eInterfacePrototype);
+ ADD_KEYS_IF_DEFINED(Method);
+ ADD_KEYS_IF_DEFINED(Attribute);
+ }
+ ADD_KEYS_IF_DEFINED(Constant);
+ }
+
+ return true;
+}
+
+#undef ADD_KEYS_IF_DEFINED
+
+bool XrayOwnNativePropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ const NativePropertyHooks* nativePropertyHooks,
+ DOMObjectType type, JS::Handle<JSObject*> obj,
+ unsigned flags,
+ JS::MutableHandleVector<jsid> props) {
+ MOZ_ASSERT(type != eNamedPropertiesObject);
+
+ if (type == eInterface &&
+ nativePropertyHooks->mPrototypeID != prototypes::id::_ID_Count &&
+ !AddStringToIDVector(cx, props, "prototype")) {
+ return false;
+ }
+
+ if (IsInterfacePrototype(type) &&
+ nativePropertyHooks->mConstructorID != constructors::id::_ID_Count &&
+ (flags & JSITER_HIDDEN) &&
+ !AddStringToIDVector(cx, props, "constructor")) {
+ return false;
+ }
+
+ const NativePropertiesHolder& nativeProperties =
+ nativePropertyHooks->mNativeProperties;
+
+ if (!InitPropertyInfos(cx, nativeProperties)) {
+ return false;
+ }
+
+ if (nativeProperties.regular &&
+ !XrayOwnPropertyKeys(cx, wrapper, obj, flags, props, type,
+ nativeProperties.regular)) {
+ return false;
+ }
+
+ if (nativeProperties.chromeOnly &&
+ xpc::AccessCheck::isChrome(JS::GetCompartment(wrapper)) &&
+ !XrayOwnPropertyKeys(cx, wrapper, obj, flags, props, type,
+ nativeProperties.chromeOnly)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool XrayOwnPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ JS::Handle<JSObject*> obj, unsigned flags,
+ JS::MutableHandleVector<jsid> props) {
+ DOMObjectType type;
+ const NativePropertyHooks* nativePropertyHooks =
+ GetNativePropertyHooks(cx, obj, type);
+ EnumerateOwnProperties enumerateOwnProperties =
+ nativePropertyHooks->mEnumerateOwnProperties;
+
+ if (type == eNamedPropertiesObject) {
+ MOZ_ASSERT(!enumerateOwnProperties,
+ "Shouldn't have any Xray-visible properties");
+ return true;
+ }
+
+ if (IsInstance(type)) {
+ // FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=1071189
+ // Should do something about XBL properties too.
+ if (enumerateOwnProperties &&
+ !enumerateOwnProperties(cx, wrapper, obj, props)) {
+ return false;
+ }
+ }
+
+ return type == eGlobalInterfacePrototype ||
+ XrayOwnNativePropertyKeys(cx, wrapper, nativePropertyHooks, type, obj,
+ flags, props);
+}
+
+const JSClass* XrayGetExpandoClass(JSContext* cx, JS::Handle<JSObject*> obj) {
+ DOMObjectType type;
+ const NativePropertyHooks* nativePropertyHooks =
+ GetNativePropertyHooks(cx, obj, type);
+ if (!IsInstance(type)) {
+ // Non-instances don't need any special expando classes.
+ return &DefaultXrayExpandoObjectClass;
+ }
+
+ return nativePropertyHooks->mXrayExpandoClass;
+}
+
+bool XrayDeleteNamedProperty(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
+ JS::ObjectOpResult& opresult) {
+ DOMObjectType type;
+ const NativePropertyHooks* nativePropertyHooks =
+ GetNativePropertyHooks(cx, obj, type);
+ if (!IsInstance(type) || !nativePropertyHooks->mDeleteNamedProperty) {
+ return opresult.succeed();
+ }
+ return nativePropertyHooks->mDeleteNamedProperty(cx, wrapper, obj, id,
+ opresult);
+}
+
+namespace binding_detail {
+
+bool ResolveOwnProperty(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) {
+ return js::GetProxyHandler(obj)->getOwnPropertyDescriptor(cx, wrapper, id,
+ desc);
+}
+
+bool EnumerateOwnProperties(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ JS::Handle<JSObject*> obj,
+ JS::MutableHandleVector<jsid> props) {
+ return js::GetProxyHandler(obj)->ownPropertyKeys(cx, wrapper, props);
+}
+
+} // namespace binding_detail
+
+JSObject* GetCachedSlotStorageObjectSlow(JSContext* cx,
+ JS::Handle<JSObject*> obj,
+ bool* isXray) {
+ if (!xpc::WrapperFactory::IsXrayWrapper(obj)) {
+ JSObject* retval =
+ js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
+ MOZ_ASSERT(IsDOMObject(retval));
+ *isXray = false;
+ return retval;
+ }
+
+ *isXray = true;
+ return xpc::EnsureXrayExpandoObject(cx, obj);
+}
+
+DEFINE_XRAY_EXPANDO_CLASS(, DefaultXrayExpandoObjectClass, 0);
+
+bool sEmptyNativePropertiesInited = true;
+NativePropertyHooks sEmptyNativePropertyHooks = {
+ nullptr,
+ nullptr,
+ nullptr,
+ {nullptr, nullptr, &sEmptyNativePropertiesInited},
+ prototypes::id::_ID_Count,
+ constructors::id::_ID_Count,
+ nullptr};
+
+const JSClassOps sBoringInterfaceObjectClassClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* enumerate */
+ nullptr, /* newEnumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ nullptr, /* finalize */
+ ThrowingConstructor, /* call */
+ ThrowingConstructor, /* construct */
+ nullptr, /* trace */
+};
+
+const js::ObjectOps sInterfaceObjectClassObjectOps = {
+ nullptr, /* lookupProperty */
+ nullptr, /* defineProperty */
+ nullptr, /* hasProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* getOwnPropertyDescriptor */
+ nullptr, /* deleteProperty */
+ nullptr, /* getElements */
+ InterfaceObjectToString, /* funToString */
+};
+
+bool GetPropertyOnPrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<JS::Value> receiver, JS::Handle<jsid> id,
+ bool* found, JS::MutableHandle<JS::Value> vp) {
+ JS::Rooted<JSObject*> proto(cx);
+ if (!js::GetObjectProto(cx, proxy, &proto)) {
+ return false;
+ }
+ if (!proto) {
+ *found = false;
+ return true;
+ }
+
+ if (!JS_HasPropertyById(cx, proto, id, found)) {
+ return false;
+ }
+
+ if (!*found) {
+ return true;
+ }
+
+ return JS_ForwardGetPropertyTo(cx, proto, id, receiver, vp);
+}
+
+bool HasPropertyOnPrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id, bool* has) {
+ JS::Rooted<JSObject*> proto(cx);
+ if (!js::GetObjectProto(cx, proxy, &proto)) {
+ return false;
+ }
+ if (!proto) {
+ *has = false;
+ return true;
+ }
+
+ return JS_HasPropertyById(cx, proto, id, has);
+}
+
+bool AppendNamedPropertyIds(JSContext* cx, JS::Handle<JSObject*> proxy,
+ nsTArray<nsString>& names,
+ bool shadowPrototypeProperties,
+ JS::MutableHandleVector<jsid> props) {
+ for (uint32_t i = 0; i < names.Length(); ++i) {
+ JS::Rooted<JS::Value> v(cx);
+ if (!xpc::NonVoidStringToJsval(cx, names[i], &v)) {
+ return false;
+ }
+
+ JS::Rooted<jsid> id(cx);
+ if (!JS_ValueToId(cx, v, &id)) {
+ return false;
+ }
+
+ bool shouldAppend = shadowPrototypeProperties;
+ if (!shouldAppend) {
+ bool has;
+ if (!HasPropertyOnPrototype(cx, proxy, id, &has)) {
+ return false;
+ }
+ shouldAppend = !has;
+ }
+
+ if (shouldAppend) {
+ if (!props.append(id)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool DictionaryBase::ParseJSON(JSContext* aCx, const nsAString& aJSON,
+ JS::MutableHandle<JS::Value> aVal) {
+ if (aJSON.IsEmpty()) {
+ return true;
+ }
+ return JS_ParseJSON(aCx, aJSON.BeginReading(), aJSON.Length(), aVal);
+}
+
+bool DictionaryBase::StringifyToJSON(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ nsAString& aJSON) const {
+ return JS::ToJSONMaybeSafely(aCx, aObj, AppendJSONToString, &aJSON);
+}
+
+/* static */
+bool DictionaryBase::AppendJSONToString(const char16_t* aJSONData,
+ uint32_t aDataLength, void* aString) {
+ nsAString* string = static_cast<nsAString*>(aString);
+ string->Append(aJSONData, aDataLength);
+ return true;
+}
+
+void UpdateReflectorGlobal(JSContext* aCx, JS::Handle<JSObject*> aObjArg,
+ ErrorResult& aError) {
+ js::AssertSameCompartment(aCx, aObjArg);
+
+ aError.MightThrowJSException();
+
+ // Check if we're anywhere near the stack limit before we reach the
+ // transplanting code, since it has no good way to handle errors. This uses
+ // the untrusted script limit, which is not strictly necessary since no
+ // actual script should run.
+ js::AutoCheckRecursionLimit recursion(aCx);
+ if (!recursion.checkConservative(aCx)) {
+ aError.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ JS::Rooted<JSObject*> aObj(aCx, aObjArg);
+ MOZ_ASSERT(IsDOMObject(aObj));
+
+ const DOMJSClass* domClass = GetDOMClass(aObj);
+
+ JS::Rooted<JSObject*> oldGlobal(aCx, JS::GetNonCCWObjectGlobal(aObj));
+ MOZ_ASSERT(JS_IsGlobalObject(oldGlobal));
+
+ JS::Rooted<JSObject*> newGlobal(aCx,
+ domClass->mGetAssociatedGlobal(aCx, aObj));
+ MOZ_ASSERT(JS_IsGlobalObject(newGlobal));
+
+ JSAutoRealm oldAr(aCx, oldGlobal);
+
+ if (oldGlobal == newGlobal) {
+ return;
+ }
+
+ nsISupports* native = UnwrapDOMObjectToISupports(aObj);
+ if (!native) {
+ return;
+ }
+
+ bool isProxy = js::IsProxy(aObj);
+ JS::Rooted<JSObject*> expandoObject(aCx);
+ if (isProxy) {
+ expandoObject = DOMProxyHandler::GetAndClearExpandoObject(aObj);
+ }
+
+ JSAutoRealm newAr(aCx, newGlobal);
+
+ // First we clone the reflector. We get a copy of its properties and clone its
+ // expando chain.
+
+ JS::Handle<JSObject*> proto = (domClass->mGetProto)(aCx);
+ if (!proto) {
+ aError.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ JS::Rooted<JSObject*> newobj(aCx, JS_CloneObject(aCx, aObj, proto));
+ if (!newobj) {
+ aError.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ // Assert it's possible to create wrappers when |aObj| and |newobj| are in
+ // different compartments.
+ MOZ_ASSERT_IF(JS::GetCompartment(aObj) != JS::GetCompartment(newobj),
+ js::AllowNewWrapper(JS::GetCompartment(aObj), newobj));
+
+ JS::Rooted<JSObject*> propertyHolder(aCx);
+ JS::Rooted<JSObject*> copyFrom(aCx, isProxy ? expandoObject : aObj);
+ if (copyFrom) {
+ propertyHolder = JS_NewObjectWithGivenProto(aCx, nullptr, nullptr);
+ if (!propertyHolder) {
+ aError.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ if (!JS_CopyOwnPropertiesAndPrivateFields(aCx, propertyHolder, copyFrom)) {
+ aError.StealExceptionFromJSContext(aCx);
+ return;
+ }
+ } else {
+ propertyHolder = nullptr;
+ }
+
+ // We've set up |newobj|, so we make it own the native by setting its reserved
+ // slot and nulling out the reserved slot of |obj|. Update the wrapper cache
+ // to keep everything consistent in case GC moves newobj.
+ //
+ // NB: It's important to do this _after_ copying the properties to
+ // propertyHolder. Otherwise, an object with |foo.x === foo| will
+ // crash when JS_CopyOwnPropertiesAndPrivateFields tries to call wrap() on
+ // foo.x.
+ JS::SetReservedSlot(newobj, DOM_OBJECT_SLOT,
+ JS::GetReservedSlot(aObj, DOM_OBJECT_SLOT));
+ JS::SetReservedSlot(aObj, DOM_OBJECT_SLOT, JS::PrivateValue(nullptr));
+ nsWrapperCache* cache = nullptr;
+ CallQueryInterface(native, &cache);
+ cache->UpdateWrapperForNewGlobal(native, newobj);
+
+ aObj = xpc::TransplantObjectRetainingXrayExpandos(aCx, aObj, newobj);
+ if (!aObj) {
+ MOZ_CRASH();
+ }
+
+ // Update the wrapper cache again if transplanting didn't use newobj but
+ // returned some other object.
+ if (aObj != newobj) {
+ MOZ_ASSERT(UnwrapDOMObjectToISupports(aObj) == native);
+ cache->UpdateWrapperForNewGlobal(native, aObj);
+ }
+
+ if (propertyHolder) {
+ JS::Rooted<JSObject*> copyTo(aCx);
+ if (isProxy) {
+ copyTo = DOMProxyHandler::EnsureExpandoObject(aCx, aObj);
+ } else {
+ copyTo = aObj;
+ }
+
+ if (!copyTo ||
+ !JS_CopyOwnPropertiesAndPrivateFields(aCx, copyTo, propertyHolder)) {
+ MOZ_CRASH();
+ }
+ }
+}
+
+GlobalObject::GlobalObject(JSContext* aCx, JSObject* aObject)
+ : mGlobalJSObject(aCx), mCx(aCx), mGlobalObject(nullptr) {
+ MOZ_ASSERT(mCx);
+ JS::Rooted<JSObject*> obj(aCx, aObject);
+ if (js::IsWrapper(obj)) {
+ // aCx correctly represents the current global here.
+ obj = js::CheckedUnwrapDynamic(obj, aCx, /* stopAtWindowProxy = */ false);
+ if (!obj) {
+ // We should never end up here on a worker thread, since there shouldn't
+ // be any security wrappers to worry about.
+ if (!MOZ_LIKELY(NS_IsMainThread())) {
+ MOZ_CRASH();
+ }
+
+ Throw(aCx, NS_ERROR_XPC_SECURITY_MANAGER_VETO);
+ return;
+ }
+ }
+
+ mGlobalJSObject = JS::GetNonCCWObjectGlobal(obj);
+}
+
+nsISupports* GlobalObject::GetAsSupports() const {
+ if (mGlobalObject) {
+ return mGlobalObject;
+ }
+
+ MOZ_ASSERT(!js::IsWrapper(mGlobalJSObject));
+
+ // Most of our globals are DOM objects. Try that first. Note that this
+ // assumes that either the first nsISupports in the object is the canonical
+ // one or that we don't care about the canonical nsISupports here.
+ mGlobalObject = UnwrapDOMObjectToISupports(mGlobalJSObject);
+ if (mGlobalObject) {
+ return mGlobalObject;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread(), "All our worker globals are DOM objects");
+
+ // Remove everything below here once all our global objects are using new
+ // bindings. If that ever happens; it would need to include Sandbox and
+ // BackstagePass.
+
+ // See whether mGlobalJSObject is an XPCWrappedNative. This will redo the
+ // IsWrapper bit above and the UnwrapDOMObjectToISupports in the case when
+ // we're not actually an XPCWrappedNative, but this should be a rare-ish case
+ // anyway.
+ //
+ // It's OK to use ReflectorToISupportsStatic, because we know we don't have a
+ // cross-compartment wrapper.
+ nsCOMPtr<nsISupports> supp = xpc::ReflectorToISupportsStatic(mGlobalJSObject);
+ if (supp) {
+ // See documentation for mGlobalJSObject for why this assignment is OK.
+ mGlobalObject = supp;
+ return mGlobalObject;
+ }
+
+ // And now a final hack. Sandbox is not a reflector, but it does have an
+ // nsIGlobalObject hanging out in its private slot. Handle that case here,
+ // (though again, this will do the useless UnwrapDOMObjectToISupports if we
+ // got here for something that is somehow not a DOM object, not an
+ // XPCWrappedNative _and_ not a Sandbox).
+ if (XPCConvert::GetISupportsFromJSObject(mGlobalJSObject, &mGlobalObject)) {
+ return mGlobalObject;
+ }
+
+ MOZ_ASSERT(!mGlobalObject);
+
+ Throw(mCx, NS_ERROR_XPC_BAD_CONVERT_JS);
+ return nullptr;
+}
+
+nsIPrincipal* GlobalObject::GetSubjectPrincipal() const {
+ if (!NS_IsMainThread()) {
+ return nullptr;
+ }
+
+ JS::Realm* realm = js::GetContextRealm(mCx);
+ MOZ_ASSERT(realm);
+ JSPrincipals* principals = JS::GetRealmPrincipals(realm);
+ return nsJSPrincipals::get(principals);
+}
+
+CallerType GlobalObject::CallerType() const {
+ return nsContentUtils::ThreadsafeIsSystemCaller(mCx)
+ ? dom::CallerType::System
+ : dom::CallerType::NonSystem;
+}
+
+static bool CallOrdinaryHasInstance(JSContext* cx, JS::CallArgs& args) {
+ JS::Rooted<JSObject*> thisObj(cx, &args.thisv().toObject());
+ bool isInstance;
+ if (!JS::OrdinaryHasInstance(cx, thisObj, args.get(0), &isInstance)) {
+ return false;
+ }
+ args.rval().setBoolean(isInstance);
+ return true;
+}
+
+using CheckInstanceFallback = bool (*)(JSContext* cx, JS::CallArgs& args);
+
+static bool InterfaceCheckInstance(JSContext* cx, unsigned argc, JS::Value* vp,
+ CheckInstanceFallback fallback) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ // If the thing we were passed is not an object, return false like
+ // OrdinaryHasInstance does.
+ if (!args.get(0).isObject()) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+
+ // If "this" is not an object, likewise return false (again, like
+ // OrdinaryHasInstance).
+ if (!args.thisv().isObject()) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+
+ // If "this" doesn't have a DOMIfaceAndProtoJSClass, it's not a DOM
+ // constructor, so just fall back. But note that we should
+ // CheckedUnwrapStatic here, because otherwise we won't get the right answers.
+ // The static version is OK, because we're looking for DOM constructors, which
+ // are not cross-origin objects.
+ JS::Rooted<JSObject*> thisObj(
+ cx, js::CheckedUnwrapStatic(&args.thisv().toObject()));
+ if (!thisObj) {
+ // Just fall back on the normal thing, in case it still happens to work.
+ return fallback(cx, args);
+ }
+
+ const JSClass* thisClass = JS::GetClass(thisObj);
+
+ if (!IsDOMIfaceAndProtoClass(thisClass)) {
+ return fallback(cx, args);
+ }
+
+ const DOMIfaceAndProtoJSClass* clasp =
+ DOMIfaceAndProtoJSClass::FromJSClass(thisClass);
+
+ // If "this" isn't a DOM constructor or is a constructor for an interface
+ // without a prototype, just fall back.
+ if (clasp->mType != eInterface ||
+ clasp->mPrototypeID == prototypes::id::_ID_Count) {
+ return fallback(cx, args);
+ }
+
+ JS::Rooted<JSObject*> instance(cx, &args[0].toObject());
+ const DOMJSClass* domClass = GetDOMClass(
+ js::UncheckedUnwrap(instance, /* stopAtWindowProxy = */ false));
+
+ if (domClass &&
+ domClass->mInterfaceChain[clasp->mDepth] == clasp->mPrototypeID) {
+ args.rval().setBoolean(true);
+ return true;
+ }
+
+ if (IsRemoteObjectProxy(instance, clasp->mPrototypeID)) {
+ args.rval().setBoolean(true);
+ return true;
+ }
+
+ return fallback(cx, args);
+}
+
+bool InterfaceHasInstance(JSContext* cx, unsigned argc, JS::Value* vp) {
+ return InterfaceCheckInstance(cx, argc, vp,
+ [](JSContext* cx, JS::CallArgs& args) {
+ return CallOrdinaryHasInstance(cx, args);
+ });
+}
+
+bool InterfaceHasInstance(JSContext* cx, int prototypeID, int depth,
+ JS::Handle<JSObject*> instance, bool* bp) {
+ const DOMJSClass* domClass = GetDOMClass(js::UncheckedUnwrap(instance));
+
+ MOZ_ASSERT(!domClass || prototypeID != prototypes::id::_ID_Count,
+ "Why do we have a hasInstance hook if we don't have a prototype "
+ "ID?");
+
+ *bp = (domClass && domClass->mInterfaceChain[depth] == prototypeID);
+ return true;
+}
+
+bool InterfaceIsInstance(JSContext* cx, unsigned argc, JS::Value* vp) {
+ return InterfaceCheckInstance(cx, argc, vp,
+ [](JSContext*, JS::CallArgs& args) {
+ args.rval().setBoolean(false);
+ return true;
+ });
+}
+
+bool ReportLenientThisUnwrappingFailure(JSContext* cx, JSObject* obj) {
+ JS::Rooted<JSObject*> rootedObj(cx, obj);
+ GlobalObject global(cx, rootedObj);
+ if (global.Failed()) {
+ return false;
+ }
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(global.GetAsSupports());
+ if (window && window->GetDoc()) {
+ window->GetDoc()->WarnOnceAbout(DeprecatedOperations::eLenientThis);
+ }
+ return true;
+}
+
+bool GetContentGlobalForJSImplementedObject(BindingCallContext& cx,
+ JS::Handle<JSObject*> obj,
+ nsIGlobalObject** globalObj) {
+ // Be very careful to not get tricked here.
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!xpc::AccessCheck::isChrome(JS::GetCompartment(obj))) {
+ MOZ_CRASH("Should have a chrome object here");
+ }
+
+ // Look up the content-side object.
+ JS::Rooted<JS::Value> domImplVal(cx);
+ if (!JS_GetProperty(cx, obj, "__DOM_IMPL__", &domImplVal)) {
+ return false;
+ }
+
+ if (!domImplVal.isObject()) {
+ cx.ThrowErrorMessage<MSG_NOT_OBJECT>("Value");
+ return false;
+ }
+
+ // Go ahead and get the global from it. GlobalObject will handle
+ // doing unwrapping as needed.
+ GlobalObject global(cx, &domImplVal.toObject());
+ if (global.Failed()) {
+ return false;
+ }
+
+ DebugOnly<nsresult> rv =
+ CallQueryInterface(global.GetAsSupports(), globalObj);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(*globalObj);
+ return true;
+}
+
+void ConstructJSImplementation(const char* aContractId,
+ nsIGlobalObject* aGlobal,
+ JS::MutableHandle<JSObject*> aObject,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Make sure to divorce ourselves from the calling JS while creating and
+ // initializing the object, so exceptions from that will get reported
+ // properly, since those are never exceptions that a spec wants to be thrown.
+ {
+ AutoNoJSAPI nojsapi;
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
+ if (!window) {
+ aRv.ThrowInvalidStateError("Global is not a Window");
+ return;
+ }
+ if (!window->IsCurrentInnerWindow()) {
+ aRv.ThrowInvalidStateError("Window no longer active");
+ return;
+ }
+
+ // Get the XPCOM component containing the JS implementation.
+ nsresult rv;
+ nsCOMPtr<nsISupports> implISupports = do_CreateInstance(aContractId, &rv);
+ if (!implISupports) {
+ nsPrintfCString msg("Failed to get JS implementation for contract \"%s\"",
+ aContractId);
+ NS_WARNING(msg.get());
+ aRv.Throw(rv);
+ return;
+ }
+ // Initialize the object, if it implements nsIDOMGlobalPropertyInitializer
+ // and our global is a window.
+ nsCOMPtr<nsIDOMGlobalPropertyInitializer> gpi =
+ do_QueryInterface(implISupports);
+ if (gpi) {
+ JS::Rooted<JS::Value> initReturn(RootingCx());
+ rv = gpi->Init(window, &initReturn);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+ // With JS-implemented WebIDL, the return value of init() is not used to
+ // determine if init() failed, so init() should only return undefined. Any
+ // kind of permission or pref checking must happen by adding an attribute
+ // to the WebIDL interface.
+ if (!initReturn.isUndefined()) {
+ MOZ_ASSERT(false,
+ "The init() method for JS-implemented WebIDL should not "
+ "return anything");
+ MOZ_CRASH();
+ }
+ }
+ // Extract the JS implementation from the XPCOM object.
+ nsCOMPtr<nsIXPConnectWrappedJS> implWrapped =
+ do_QueryInterface(implISupports, &rv);
+ MOZ_ASSERT(implWrapped, "Failed to get wrapped JS from XPCOM component.");
+ if (!implWrapped) {
+ aRv.Throw(rv);
+ return;
+ }
+ aObject.set(implWrapped->GetJSObject());
+ if (!aObject) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ }
+ }
+}
+
+bool NonVoidByteStringToJsval(JSContext* cx, const nsACString& str,
+ JS::MutableHandle<JS::Value> rval) {
+ // ByteStrings are not UTF-8 encoded.
+ JSString* jsStr = JS_NewStringCopyN(cx, str.Data(), str.Length());
+ if (!jsStr) {
+ return false;
+ }
+ rval.setString(jsStr);
+ return true;
+}
+
+bool NormalizeUSVString(nsAString& aString) {
+ return EnsureUTF16Validity(aString);
+}
+
+bool NormalizeUSVString(binding_detail::FakeString<char16_t>& aString) {
+ uint32_t upTo = Utf16ValidUpTo(aString);
+ uint32_t len = aString.Length();
+ if (upTo == len) {
+ return true;
+ }
+ // This is the part that's different from EnsureUTF16Validity with an
+ // nsAString& argument, because we don't want to ensure mutability in our
+ // BeginWriting() in the common case and nsAString's EnsureMutable is not
+ // public. This is a little annoying; I wish we could just share the more or
+ // less identical code!
+ if (!aString.EnsureMutable()) {
+ return false;
+ }
+
+ char16_t* ptr = aString.BeginWriting();
+ auto span = Span(ptr, len);
+ span[upTo] = 0xFFFD;
+ EnsureUtf16ValiditySpan(span.From(upTo + 1));
+ return true;
+}
+
+bool ConvertJSValueToByteString(BindingCallContext& cx, JS::Handle<JS::Value> v,
+ bool nullable, const char* sourceDescription,
+ nsACString& result) {
+ JS::Rooted<JSString*> s(cx);
+ if (v.isString()) {
+ s = v.toString();
+ } else {
+ if (nullable && v.isNullOrUndefined()) {
+ result.SetIsVoid(true);
+ return true;
+ }
+
+ s = JS::ToString(cx, v);
+ if (!s) {
+ return false;
+ }
+ }
+
+ // Conversion from Javascript string to ByteString is only valid if all
+ // characters < 256. This is always the case for Latin1 strings.
+ size_t length;
+ if (!JS::StringHasLatin1Chars(s)) {
+ // ThrowErrorMessage can GC, so we first scan the string for bad chars
+ // and report the error outside the AutoCheckCannotGC scope.
+ bool foundBadChar = false;
+ size_t badCharIndex;
+ char16_t badChar;
+ {
+ JS::AutoCheckCannotGC nogc;
+ const char16_t* chars =
+ JS_GetTwoByteStringCharsAndLength(cx, nogc, s, &length);
+ if (!chars) {
+ return false;
+ }
+
+ for (size_t i = 0; i < length; i++) {
+ if (chars[i] > 255) {
+ badCharIndex = i;
+ badChar = chars[i];
+ foundBadChar = true;
+ break;
+ }
+ }
+ }
+
+ if (foundBadChar) {
+ MOZ_ASSERT(badCharIndex < length);
+ MOZ_ASSERT(badChar > 255);
+ // The largest unsigned 64 bit number (18,446,744,073,709,551,615) has
+ // 20 digits, plus one more for the null terminator.
+ char index[21];
+ static_assert(sizeof(size_t) <= 8, "index array too small");
+ SprintfLiteral(index, "%zu", badCharIndex);
+ // A char16_t is 16 bits long. The biggest unsigned 16 bit
+ // number (65,535) has 5 digits, plus one more for the null
+ // terminator.
+ char badCharArray[6];
+ static_assert(sizeof(char16_t) <= 2, "badCharArray too small");
+ SprintfLiteral(badCharArray, "%d", badChar);
+ cx.ThrowErrorMessage<MSG_INVALID_BYTESTRING>(sourceDescription, index,
+ badCharArray);
+ return false;
+ }
+ } else {
+ length = JS::GetStringLength(s);
+ }
+
+ static_assert(JS::MaxStringLength < UINT32_MAX,
+ "length+1 shouldn't overflow");
+
+ if (!result.SetLength(length, fallible)) {
+ return false;
+ }
+
+ if (!JS_EncodeStringToBuffer(cx, s, result.BeginWriting(), length)) {
+ return false;
+ }
+
+ return true;
+}
+
+void FinalizeGlobal(JS::GCContext* aGcx, JSObject* aObj) {
+ MOZ_ASSERT(JS::GetClass(aObj)->flags & JSCLASS_DOM_GLOBAL);
+ mozilla::dom::DestroyProtoAndIfaceCache(aObj);
+}
+
+bool ResolveGlobal(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::Handle<jsid> aId, bool* aResolvedp) {
+ MOZ_ASSERT(JS_IsGlobalObject(aObj),
+ "Should have a global here, since we plan to resolve standard "
+ "classes!");
+
+ return JS_ResolveStandardClass(aCx, aObj, aId, aResolvedp);
+}
+
+bool MayResolveGlobal(const JSAtomState& aNames, jsid aId,
+ JSObject* aMaybeObj) {
+ return JS_MayResolveStandardClass(aNames, aId, aMaybeObj);
+}
+
+bool EnumerateGlobal(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::MutableHandleVector<jsid> aProperties,
+ bool aEnumerableOnly) {
+ MOZ_ASSERT(JS_IsGlobalObject(aObj),
+ "Should have a global here, since we plan to enumerate standard "
+ "classes!");
+
+ return JS_NewEnumerateStandardClasses(aCx, aObj, aProperties,
+ aEnumerableOnly);
+}
+
+bool IsNonExposedGlobal(JSContext* aCx, JSObject* aGlobal,
+ uint32_t aNonExposedGlobals) {
+ MOZ_ASSERT(aNonExposedGlobals, "Why did we get called?");
+ MOZ_ASSERT((aNonExposedGlobals &
+ ~(GlobalNames::Window | GlobalNames::DedicatedWorkerGlobalScope |
+ GlobalNames::SharedWorkerGlobalScope |
+ GlobalNames::ServiceWorkerGlobalScope |
+ GlobalNames::WorkerDebuggerGlobalScope |
+ GlobalNames::AudioWorkletGlobalScope |
+ GlobalNames::PaintWorkletGlobalScope |
+ GlobalNames::ShadowRealmGlobalScope)) == 0,
+ "Unknown non-exposed global type");
+
+ const char* name = JS::GetClass(aGlobal)->name;
+
+ if ((aNonExposedGlobals & GlobalNames::Window) &&
+ (!strcmp(name, "Window") || !strcmp(name, "BackstagePass"))) {
+ return true;
+ }
+
+ if ((aNonExposedGlobals & GlobalNames::DedicatedWorkerGlobalScope) &&
+ !strcmp(name, "DedicatedWorkerGlobalScope")) {
+ return true;
+ }
+
+ if ((aNonExposedGlobals & GlobalNames::SharedWorkerGlobalScope) &&
+ !strcmp(name, "SharedWorkerGlobalScope")) {
+ return true;
+ }
+
+ if ((aNonExposedGlobals & GlobalNames::ServiceWorkerGlobalScope) &&
+ !strcmp(name, "ServiceWorkerGlobalScope")) {
+ return true;
+ }
+
+ if ((aNonExposedGlobals & GlobalNames::WorkerDebuggerGlobalScope) &&
+ !strcmp(name, "WorkerDebuggerGlobalScopex")) {
+ return true;
+ }
+
+ if ((aNonExposedGlobals & GlobalNames::AudioWorkletGlobalScope) &&
+ !strcmp(name, "AudioWorkletGlobalScope")) {
+ return true;
+ }
+
+ if ((aNonExposedGlobals & GlobalNames::PaintWorkletGlobalScope) &&
+ !strcmp(name, "PaintWorkletGlobalScope")) {
+ return true;
+ }
+
+ if ((aNonExposedGlobals & GlobalNames::ShadowRealmGlobalScope) &&
+ !strcmp(name, "ShadowRealmGlobalScope")) {
+ return true;
+ }
+
+ return false;
+}
+
+namespace binding_detail {
+
+/**
+ * A ThisPolicy struct needs to provide the following methods:
+ *
+ * HasValidThisValue: Takes a CallArgs and returns a boolean indicating whether
+ * the thisv() is valid in the sense of being the right type
+ * of Value. It does not check whether it's the right sort
+ * of object if the Value is a JSObject*.
+ *
+ * ExtractThisObject: Takes a CallArgs for which HasValidThisValue was true and
+ * returns the JSObject* to use for getting |this|.
+ *
+ * MaybeUnwrapThisObject: If our |this| is a JSObject* that this policy wants to
+ * allow unchecked access to for this
+ * getter/setter/method, unwrap it. Otherwise just
+ * return the given object.
+ *
+ * UnwrapThisObject: Takes a MutableHandle for a JSObject which contains the
+ * this object (which the caller probably got from
+ * MaybeUnwrapThisObject). It will try to get the right native
+ * out of aObj. In some cases there are 2 possible types for
+ * the native (which is why aSelf is a reference to a void*).
+ * The ThisPolicy user should use the this JSObject* to
+ * determine what C++ class aSelf contains. aObj is used to
+ * keep the reflector object alive while self is being used,
+ * so its value before and after the UnwrapThisObject call
+ * could be different (if aObj was wrapped). The return value
+ * is an nsresult, which will signal if an error occurred.
+ *
+ * This is passed a JSContext for dynamic unwrapping purposes,
+ * but should not throw exceptions on that JSContext.
+ *
+ * HandleInvalidThis: If the |this| is not valid (wrong type of value, wrong
+ * object, etc), decide what to do about it. Returns a
+ * boolean to return from the JSNative (false for failure,
+ * true for succcess).
+ */
+struct NormalThisPolicy {
+ // This needs to be inlined because it's called on no-exceptions fast-paths.
+ static MOZ_ALWAYS_INLINE bool HasValidThisValue(const JS::CallArgs& aArgs) {
+ // Per WebIDL spec, all getters/setters/methods allow null/undefined "this"
+ // and coerce it to the global. Then the "is this the right interface?"
+ // check fails if the interface involved is not one that the global
+ // implements.
+ //
+ // As an optimization, we skip doing the null/undefined stuff if we know our
+ // interface is not implemented by the global.
+ return aArgs.thisv().isObject();
+ }
+
+ static MOZ_ALWAYS_INLINE JSObject* ExtractThisObject(
+ const JS::CallArgs& aArgs) {
+ return &aArgs.thisv().toObject();
+ }
+
+ static MOZ_ALWAYS_INLINE JSObject* MaybeUnwrapThisObject(JSObject* aObj) {
+ return aObj;
+ }
+
+ static MOZ_ALWAYS_INLINE nsresult UnwrapThisObject(
+ JS::MutableHandle<JSObject*> aObj, JSContext* aCx, void*& aSelf,
+ prototypes::ID aProtoID, uint32_t aProtoDepth) {
+ binding_detail::MutableObjectHandleWrapper wrapper(aObj);
+ return binding_detail::UnwrapObjectInternal<void, true>(
+ wrapper, aSelf, aProtoID, aProtoDepth, aCx);
+ }
+
+ static bool HandleInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs,
+ bool aSecurityError, prototypes::ID aProtoId) {
+ return ThrowInvalidThis(aCx, aArgs, aSecurityError, aProtoId);
+ }
+};
+
+struct MaybeGlobalThisPolicy : public NormalThisPolicy {
+ static MOZ_ALWAYS_INLINE bool HasValidThisValue(const JS::CallArgs& aArgs) {
+ // Here we have to allow null/undefined.
+ return aArgs.thisv().isObject() || aArgs.thisv().isNullOrUndefined();
+ }
+
+ static MOZ_ALWAYS_INLINE JSObject* ExtractThisObject(
+ const JS::CallArgs& aArgs) {
+ return aArgs.thisv().isObject()
+ ? &aArgs.thisv().toObject()
+ : JS::GetNonCCWObjectGlobal(&aArgs.callee());
+ }
+
+ // We want the MaybeUnwrapThisObject of NormalThisPolicy.
+
+ // We want the HandleInvalidThis of NormalThisPolicy.
+};
+
+// Shared LenientThis behavior for our two different LenientThis policies.
+struct LenientThisPolicyMixin {
+ static bool HandleInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs,
+ bool aSecurityError, prototypes::ID aProtoId) {
+ if (aSecurityError) {
+ return NormalThisPolicy::HandleInvalidThis(aCx, aArgs, aSecurityError,
+ aProtoId);
+ }
+
+ MOZ_ASSERT(!JS_IsExceptionPending(aCx));
+ if (!ReportLenientThisUnwrappingFailure(aCx, &aArgs.callee())) {
+ return false;
+ }
+ aArgs.rval().set(JS::UndefinedValue());
+ return true;
+ }
+};
+
+// There are some LenientThis things on globals, so we inherit from
+// MaybeGlobalThisPolicy.
+struct LenientThisPolicy : public MaybeGlobalThisPolicy,
+ public LenientThisPolicyMixin {
+ // We want the HasValidThisValue of MaybeGlobalThisPolicy.
+
+ // We want the ExtractThisObject of MaybeGlobalThisPolicy.
+
+ // We want the MaybeUnwrapThisObject of MaybeGlobalThisPolicy.
+
+ // We want HandleInvalidThis from LenientThisPolicyMixin
+ using LenientThisPolicyMixin::HandleInvalidThis;
+};
+
+// There are some cross-origin things on globals, so we inherit from
+// MaybeGlobalThisPolicy.
+struct CrossOriginThisPolicy : public MaybeGlobalThisPolicy {
+ // We want the HasValidThisValue of MaybeGlobalThisPolicy.
+
+ // We want the ExtractThisObject of MaybeGlobalThisPolicy.
+
+ static MOZ_ALWAYS_INLINE JSObject* MaybeUnwrapThisObject(JSObject* aObj) {
+ if (xpc::WrapperFactory::IsCrossOriginWrapper(aObj)) {
+ return js::UncheckedUnwrap(aObj);
+ }
+
+ // Else just return aObj; our UnwrapThisObject call will try to
+ // CheckedUnwrap it, and either succeed or get a security error as needed.
+ return aObj;
+ }
+
+ // After calling UnwrapThisObject aSelf can contain one of 2 types, depending
+ // on whether aObj is a proxy with a RemoteObjectProxy handler or a (maybe
+ // wrapped) normal WebIDL reflector. The generated binding code relies on this
+ // and uses IsRemoteObjectProxy to determine what type aSelf points to.
+ static MOZ_ALWAYS_INLINE nsresult UnwrapThisObject(
+ JS::MutableHandle<JSObject*> aObj, JSContext* aCx, void*& aSelf,
+ prototypes::ID aProtoID, uint32_t aProtoDepth) {
+ binding_detail::MutableObjectHandleWrapper wrapper(aObj);
+ // We need to pass false here, because if aObj doesn't have a DOMJSClass
+ // it might be a remote proxy object, and we don't want to throw in that
+ // case (even though unwrapping would fail).
+ nsresult rv = binding_detail::UnwrapObjectInternal<void, false>(
+ wrapper, aSelf, aProtoID, aProtoDepth, nullptr);
+ if (NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ if (js::IsWrapper(wrapper)) {
+ // We want CheckedUnwrapDynamic here: aCx represents the Realm we are in
+ // right now, so we want to check whether that Realm should be able to
+ // access the object. And this object can definitely be a WindowProxy, so
+ // we need he dynamic check.
+ JSObject* unwrappedObj = js::CheckedUnwrapDynamic(
+ wrapper, aCx, /* stopAtWindowProxy = */ false);
+ if (!unwrappedObj) {
+ return NS_ERROR_XPC_SECURITY_MANAGER_VETO;
+ }
+
+ // At this point we want to keep "unwrappedObj" alive, because we don't
+ // hold a strong reference in "aSelf".
+ wrapper = unwrappedObj;
+
+ return binding_detail::UnwrapObjectInternal<void, false>(
+ wrapper, aSelf, aProtoID, aProtoDepth, nullptr);
+ }
+
+ if (!IsRemoteObjectProxy(wrapper, aProtoID)) {
+ return NS_ERROR_XPC_BAD_CONVERT_JS;
+ }
+ aSelf = RemoteObjectProxyBase::GetNative(wrapper);
+ return NS_OK;
+ }
+
+ // We want the HandleInvalidThis of MaybeGlobalThisPolicy.
+};
+
+// Some objects that can be cross-origin objects are globals, so we inherit
+// from MaybeGlobalThisPolicy.
+struct MaybeCrossOriginObjectThisPolicy : public MaybeGlobalThisPolicy {
+ // We want the HasValidThisValue of MaybeGlobalThisPolicy.
+
+ // We want the ExtractThisObject of MaybeGlobalThisPolicy.
+
+ // We want the MaybeUnwrapThisObject of MaybeGlobalThisPolicy
+
+ static MOZ_ALWAYS_INLINE nsresult UnwrapThisObject(
+ JS::MutableHandle<JSObject*> aObj, JSContext* aCx, void*& aSelf,
+ prototypes::ID aProtoID, uint32_t aProtoDepth) {
+ // There are two cases at this point: either aObj is a cross-compartment
+ // wrapper (CCW) or it's not. If it is, we don't need to do anything
+ // special compared to MaybeGlobalThisPolicy: the CCW will do the relevant
+ // security checks. Which is good, because if we tried to do the
+ // cross-origin object check _before_ unwrapping it would always come back
+ // as "same-origin" and if we tried to do it after unwrapping it would be
+ // completely wrong: the checks rely on the two sides of the comparison
+ // being symmetric (can access each other or cannot access each other), but
+ // if we have a CCW we could have an Xray, which is asymmetric. And then
+ // we'd think we should deny access, whereas we should actually allow
+ // access.
+ //
+ // If we do _not_ have a CCW here, then we need to check whether it's a
+ // cross-origin-accessible object, and if it is check whether it's
+ // same-origin-domain with our current callee.
+ if (!js::IsCrossCompartmentWrapper(aObj) &&
+ xpc::IsCrossOriginAccessibleObject(aObj) &&
+ !MaybeCrossOriginObjectMixins::IsPlatformObjectSameOrigin(aCx, aObj)) {
+ return NS_ERROR_XPC_SECURITY_MANAGER_VETO;
+ }
+
+ return MaybeGlobalThisPolicy::UnwrapThisObject(aObj, aCx, aSelf, aProtoID,
+ aProtoDepth);
+ }
+
+ // We want the HandleInvalidThis of MaybeGlobalThisPolicy.
+};
+
+// And in some cases we are dealing with a maybe-cross-origin object _and_ need
+// [LenientThis] behavior.
+struct MaybeCrossOriginObjectLenientThisPolicy
+ : public MaybeCrossOriginObjectThisPolicy,
+ public LenientThisPolicyMixin {
+ // We want to get all of our behavior from
+ // MaybeCrossOriginObjectLenientThisPolicy, except for HandleInvalidThis,
+ // which should come from LenientThisPolicyMixin.
+ using LenientThisPolicyMixin::HandleInvalidThis;
+};
+
+/**
+ * An ExceptionPolicy struct provides a single HandleException method which is
+ * used to handle an exception, if any. The method is given the current
+ * success/failure boolean so it can decide whether there is in fact an
+ * exception involved.
+ */
+struct ThrowExceptions {
+ // This needs to be inlined because it's called even on no-exceptions
+ // fast-paths.
+ static MOZ_ALWAYS_INLINE bool HandleException(JSContext* aCx,
+ JS::CallArgs& aArgs,
+ const JSJitInfo* aInfo,
+ bool aOK) {
+ return aOK;
+ }
+};
+
+struct ConvertExceptionsToPromises {
+ // This needs to be inlined because it's called even on no-exceptions
+ // fast-paths.
+ static MOZ_ALWAYS_INLINE bool HandleException(JSContext* aCx,
+ JS::CallArgs& aArgs,
+ const JSJitInfo* aInfo,
+ bool aOK) {
+ // Promise-returning getters/methods always return objects.
+ MOZ_ASSERT(aInfo->returnType() == JSVAL_TYPE_OBJECT);
+
+ if (aOK) {
+ return true;
+ }
+
+ return ConvertExceptionToPromise(aCx, aArgs.rval());
+ }
+};
+
+template <typename ThisPolicy, typename ExceptionPolicy>
+bool GenericGetter(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ const JSJitInfo* info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
+ prototypes::ID protoID = static_cast<prototypes::ID>(info->protoID);
+ if (!ThisPolicy::HasValidThisValue(args)) {
+ bool ok = ThisPolicy::HandleInvalidThis(cx, args, false, protoID);
+ return ExceptionPolicy::HandleException(cx, args, info, ok);
+ }
+ JS::Rooted<JSObject*> obj(cx, ThisPolicy::ExtractThisObject(args));
+
+ // NOTE: we want to leave obj in its initial compartment, so don't want to
+ // pass it to UnwrapObjectInternal. Also, the thing we pass to
+ // UnwrapObjectInternal may be affected by our ThisPolicy.
+ JS::Rooted<JSObject*> rootSelf(cx, ThisPolicy::MaybeUnwrapThisObject(obj));
+ void* self;
+ {
+ nsresult rv =
+ ThisPolicy::UnwrapThisObject(&rootSelf, cx, self, protoID, info->depth);
+ if (NS_FAILED(rv)) {
+ bool ok = ThisPolicy::HandleInvalidThis(
+ cx, args, rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO, protoID);
+ return ExceptionPolicy::HandleException(cx, args, info, ok);
+ }
+ }
+
+ MOZ_ASSERT(info->type() == JSJitInfo::Getter);
+ JSJitGetterOp getter = info->getter;
+ bool ok = getter(cx, obj, self, JSJitGetterCallArgs(args));
+#ifdef DEBUG
+ if (ok) {
+ AssertReturnTypeMatchesJitinfo(info, args.rval());
+ }
+#endif
+ return ExceptionPolicy::HandleException(cx, args, info, ok);
+}
+
+// Force instantiation of the specializations of GenericGetter we need here.
+template bool GenericGetter<NormalThisPolicy, ThrowExceptions>(JSContext* cx,
+ unsigned argc,
+ JS::Value* vp);
+template bool GenericGetter<NormalThisPolicy, ConvertExceptionsToPromises>(
+ JSContext* cx, unsigned argc, JS::Value* vp);
+template bool GenericGetter<MaybeGlobalThisPolicy, ThrowExceptions>(
+ JSContext* cx, unsigned argc, JS::Value* vp);
+template bool GenericGetter<MaybeGlobalThisPolicy, ConvertExceptionsToPromises>(
+ JSContext* cx, unsigned argc, JS::Value* vp);
+template bool GenericGetter<LenientThisPolicy, ThrowExceptions>(JSContext* cx,
+ unsigned argc,
+ JS::Value* vp);
+// There aren't any [LenientThis] Promise-returning getters, so don't
+// bother instantiating that specialization.
+template bool GenericGetter<CrossOriginThisPolicy, ThrowExceptions>(
+ JSContext* cx, unsigned argc, JS::Value* vp);
+// There aren't any cross-origin Promise-returning getters, so don't
+// bother instantiating that specialization.
+template bool GenericGetter<MaybeCrossOriginObjectThisPolicy, ThrowExceptions>(
+ JSContext* cx, unsigned argc, JS::Value* vp);
+// There aren't any maybe-cross-origin-object Promise-returning getters, so
+// don't bother instantiating that specialization.
+template bool GenericGetter<MaybeCrossOriginObjectLenientThisPolicy,
+ ThrowExceptions>(JSContext* cx, unsigned argc,
+ JS::Value* vp);
+// There aren't any maybe-cross-origin-object Promise-returning lenient-this
+// getters, so don't bother instantiating that specialization.
+
+template <typename ThisPolicy>
+bool GenericSetter(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ const JSJitInfo* info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
+ prototypes::ID protoID = static_cast<prototypes::ID>(info->protoID);
+ if (!ThisPolicy::HasValidThisValue(args)) {
+ return ThisPolicy::HandleInvalidThis(cx, args, false, protoID);
+ }
+ JS::Rooted<JSObject*> obj(cx, ThisPolicy::ExtractThisObject(args));
+
+ // NOTE: we want to leave obj in its initial compartment, so don't want to
+ // pass it to UnwrapObject. Also the thing we pass to UnwrapObjectInternal
+ // may be affected by our ThisPolicy.
+ JS::Rooted<JSObject*> rootSelf(cx, ThisPolicy::MaybeUnwrapThisObject(obj));
+ void* self;
+ {
+ nsresult rv =
+ ThisPolicy::UnwrapThisObject(&rootSelf, cx, self, protoID, info->depth);
+ if (NS_FAILED(rv)) {
+ return ThisPolicy::HandleInvalidThis(
+ cx, args, rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO, protoID);
+ }
+ }
+ if (args.length() == 0) {
+ return ThrowNoSetterArg(cx, args, protoID);
+ }
+ MOZ_ASSERT(info->type() == JSJitInfo::Setter);
+ JSJitSetterOp setter = info->setter;
+ if (!setter(cx, obj, self, JSJitSetterCallArgs(args))) {
+ return false;
+ }
+ args.rval().setUndefined();
+#ifdef DEBUG
+ AssertReturnTypeMatchesJitinfo(info, args.rval());
+#endif
+ return true;
+}
+
+// Force instantiation of the specializations of GenericSetter we need here.
+template bool GenericSetter<NormalThisPolicy>(JSContext* cx, unsigned argc,
+ JS::Value* vp);
+template bool GenericSetter<MaybeGlobalThisPolicy>(JSContext* cx, unsigned argc,
+ JS::Value* vp);
+template bool GenericSetter<LenientThisPolicy>(JSContext* cx, unsigned argc,
+ JS::Value* vp);
+template bool GenericSetter<CrossOriginThisPolicy>(JSContext* cx, unsigned argc,
+ JS::Value* vp);
+template bool GenericSetter<MaybeCrossOriginObjectThisPolicy>(JSContext* cx,
+ unsigned argc,
+ JS::Value* vp);
+template bool GenericSetter<MaybeCrossOriginObjectLenientThisPolicy>(
+ JSContext* cx, unsigned argc, JS::Value* vp);
+
+template <typename ThisPolicy, typename ExceptionPolicy>
+bool GenericMethod(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ const JSJitInfo* info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
+ prototypes::ID protoID = static_cast<prototypes::ID>(info->protoID);
+ if (!ThisPolicy::HasValidThisValue(args)) {
+ bool ok = ThisPolicy::HandleInvalidThis(cx, args, false, protoID);
+ return ExceptionPolicy::HandleException(cx, args, info, ok);
+ }
+ JS::Rooted<JSObject*> obj(cx, ThisPolicy::ExtractThisObject(args));
+
+ // NOTE: we want to leave obj in its initial compartment, so don't want to
+ // pass it to UnwrapObjectInternal. Also, the thing we pass to
+ // UnwrapObjectInternal may be affected by our ThisPolicy.
+ JS::Rooted<JSObject*> rootSelf(cx, ThisPolicy::MaybeUnwrapThisObject(obj));
+ void* self;
+ {
+ nsresult rv =
+ ThisPolicy::UnwrapThisObject(&rootSelf, cx, self, protoID, info->depth);
+ if (NS_FAILED(rv)) {
+ bool ok = ThisPolicy::HandleInvalidThis(
+ cx, args, rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO, protoID);
+ return ExceptionPolicy::HandleException(cx, args, info, ok);
+ }
+ }
+ MOZ_ASSERT(info->type() == JSJitInfo::Method);
+ JSJitMethodOp method = info->method;
+ bool ok = method(cx, obj, self, JSJitMethodCallArgs(args));
+#ifdef DEBUG
+ if (ok) {
+ AssertReturnTypeMatchesJitinfo(info, args.rval());
+ }
+#endif
+ return ExceptionPolicy::HandleException(cx, args, info, ok);
+}
+
+// Force instantiation of the specializations of GenericMethod we need here.
+template bool GenericMethod<NormalThisPolicy, ThrowExceptions>(JSContext* cx,
+ unsigned argc,
+ JS::Value* vp);
+template bool GenericMethod<NormalThisPolicy, ConvertExceptionsToPromises>(
+ JSContext* cx, unsigned argc, JS::Value* vp);
+template bool GenericMethod<MaybeGlobalThisPolicy, ThrowExceptions>(
+ JSContext* cx, unsigned argc, JS::Value* vp);
+template bool GenericMethod<MaybeGlobalThisPolicy, ConvertExceptionsToPromises>(
+ JSContext* cx, unsigned argc, JS::Value* vp);
+template bool GenericMethod<CrossOriginThisPolicy, ThrowExceptions>(
+ JSContext* cx, unsigned argc, JS::Value* vp);
+// There aren't any cross-origin Promise-returning methods, so don't
+// bother instantiating that specialization.
+template bool GenericMethod<MaybeCrossOriginObjectThisPolicy, ThrowExceptions>(
+ JSContext* cx, unsigned argc, JS::Value* vp);
+template bool GenericMethod<MaybeCrossOriginObjectThisPolicy,
+ ConvertExceptionsToPromises>(JSContext* cx,
+ unsigned argc,
+ JS::Value* vp);
+
+} // namespace binding_detail
+
+bool StaticMethodPromiseWrapper(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+ const JSJitInfo* info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
+ MOZ_ASSERT(info);
+ MOZ_ASSERT(info->type() == JSJitInfo::StaticMethod);
+
+ bool ok = info->staticMethod(cx, argc, vp);
+ if (ok) {
+ return true;
+ }
+
+ return ConvertExceptionToPromise(cx, args.rval());
+}
+
+bool ConvertExceptionToPromise(JSContext* cx,
+ JS::MutableHandle<JS::Value> rval) {
+ JS::Rooted<JS::Value> exn(cx);
+ if (!JS_GetPendingException(cx, &exn)) {
+ // This is very important: if there is no pending exception here but we're
+ // ending up in this code, that means the callee threw an uncatchable
+ // exception. Just propagate that out as-is.
+ return false;
+ }
+
+ JS_ClearPendingException(cx);
+
+ JSObject* promise = JS::CallOriginalPromiseReject(cx, exn);
+ if (!promise) {
+ // We just give up. Put the exception back.
+ JS_SetPendingException(cx, exn);
+ return false;
+ }
+
+ rval.setObject(*promise);
+ return true;
+}
+
+/* static */
+void CreateGlobalOptionsWithXPConnect::TraceGlobal(JSTracer* aTrc,
+ JSObject* aObj) {
+ xpc::TraceXPCGlobal(aTrc, aObj);
+}
+
+/* static */
+bool CreateGlobalOptionsWithXPConnect::PostCreateGlobal(
+ JSContext* aCx, JS::Handle<JSObject*> aGlobal) {
+ JSPrincipals* principals =
+ JS::GetRealmPrincipals(js::GetNonCCWObjectRealm(aGlobal));
+ nsIPrincipal* principal = nsJSPrincipals::get(principals);
+
+ SiteIdentifier site;
+ nsresult rv = BasePrincipal::Cast(principal)->GetSiteIdentifier(site);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ xpc::RealmPrivate::Init(aGlobal, site);
+ return true;
+}
+
+uint64_t GetWindowID(void* aGlobal) { return 0; }
+
+uint64_t GetWindowID(nsGlobalWindowInner* aGlobal) {
+ return aGlobal->WindowID();
+}
+
+uint64_t GetWindowID(DedicatedWorkerGlobalScope* aGlobal) {
+ return aGlobal->WindowID();
+}
+
+#ifdef DEBUG
+void AssertReturnTypeMatchesJitinfo(const JSJitInfo* aJitInfo,
+ JS::Handle<JS::Value> aValue) {
+ switch (aJitInfo->returnType()) {
+ case JSVAL_TYPE_UNKNOWN:
+ // Any value is good.
+ break;
+ case JSVAL_TYPE_DOUBLE:
+ // The value could actually be an int32 value as well.
+ MOZ_ASSERT(aValue.isNumber());
+ break;
+ case JSVAL_TYPE_INT32:
+ MOZ_ASSERT(aValue.isInt32());
+ break;
+ case JSVAL_TYPE_UNDEFINED:
+ MOZ_ASSERT(aValue.isUndefined());
+ break;
+ case JSVAL_TYPE_BOOLEAN:
+ MOZ_ASSERT(aValue.isBoolean());
+ break;
+ case JSVAL_TYPE_STRING:
+ MOZ_ASSERT(aValue.isString());
+ break;
+ case JSVAL_TYPE_NULL:
+ MOZ_ASSERT(aValue.isNull());
+ break;
+ case JSVAL_TYPE_OBJECT:
+ MOZ_ASSERT(aValue.isObject());
+ break;
+ default:
+ // Someone messed up their jitinfo type.
+ MOZ_ASSERT(false, "Unexpected JSValueType stored in jitinfo");
+ break;
+ }
+}
+#endif
+
+bool CallerSubsumes(JSObject* aObject) {
+ // Remote object proxies are not CCWs, so unwrapping them does not get you
+ // their "real" principal, but we want to treat them like cross-origin objects
+ // when considering them as WebIDL arguments, for consistency.
+ if (IsRemoteObjectProxy(aObject)) {
+ return false;
+ }
+ nsIPrincipal* objPrin =
+ nsContentUtils::ObjectPrincipal(js::UncheckedUnwrap(aObject));
+ return nsContentUtils::SubjectPrincipal()->Subsumes(objPrin);
+}
+
+nsresult UnwrapArgImpl(JSContext* cx, JS::Handle<JSObject*> src,
+ const nsIID& iid, void** ppArg) {
+ if (!NS_IsMainThread()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // The JSContext represents the "who is unwrapping" realm, so we want to use
+ // it for ReflectorToISupportsDynamic here.
+ nsCOMPtr<nsISupports> iface = xpc::ReflectorToISupportsDynamic(src, cx);
+ if (iface) {
+ if (NS_FAILED(iface->QueryInterface(iid, ppArg))) {
+ return NS_ERROR_XPC_BAD_CONVERT_JS;
+ }
+
+ return NS_OK;
+ }
+
+ // Only allow XPCWrappedJS stuff in system code. Ideally we would remove this
+ // even there, but that involves converting some things to WebIDL callback
+ // interfaces and making some other things builtinclass...
+ if (!nsContentUtils::IsSystemCaller(cx)) {
+ return NS_ERROR_XPC_BAD_CONVERT_JS;
+ }
+
+ RefPtr<nsXPCWrappedJS> wrappedJS;
+ nsresult rv =
+ nsXPCWrappedJS::GetNewOrUsed(cx, src, iid, getter_AddRefs(wrappedJS));
+ if (NS_FAILED(rv) || !wrappedJS) {
+ return rv;
+ }
+
+ // We need to go through the QueryInterface logic to make this return
+ // the right thing for the various 'special' interfaces; e.g.
+ // nsIPropertyBag. We must use AggregatedQueryInterface in cases where
+ // there is an outer to avoid nasty recursion.
+ return wrappedJS->QueryInterface(iid, ppArg);
+}
+
+nsresult UnwrapWindowProxyArg(JSContext* cx, JS::Handle<JSObject*> src,
+ WindowProxyHolder& ppArg) {
+ if (IsRemoteObjectProxy(src, prototypes::id::Window)) {
+ ppArg =
+ static_cast<BrowsingContext*>(RemoteObjectProxyBase::GetNative(src));
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> inner;
+ nsresult rv = UnwrapArg<nsPIDOMWindowInner>(cx, src, getter_AddRefs(inner));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsPIDOMWindowOuter> outer = inner->GetOuterWindow();
+ RefPtr<BrowsingContext> bc = outer ? outer->GetBrowsingContext() : nullptr;
+ ppArg = std::move(bc);
+ return NS_OK;
+}
+
+template <auto Method, typename... Args>
+static bool GetBackingObject(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ size_t aSlotIndex,
+ JS::MutableHandle<JSObject*> aBackingObj,
+ bool* aBackingObjCreated, Args... aArgs) {
+ JS::Rooted<JSObject*> reflector(aCx);
+ reflector = IsDOMObject(aObj)
+ ? aObj
+ : js::UncheckedUnwrap(aObj,
+ /* stopAtWindowProxy = */ 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 realms,
+ // make sure to create the backing object in reflector realm.
+ {
+ JSAutoRealm ar(aCx, reflector);
+ JS::Rooted<JSObject*> newBackingObj(aCx);
+ newBackingObj.set(Method(aCx, aArgs...));
+ 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 GetBackingObject<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 GetBackingObject<JS::NewSetObject>(aCx, aObj, aSlotIndex, aBackingObj,
+ aBackingObjCreated);
+}
+
+static inline JSObject* NewObservableArrayProxyObject(
+ JSContext* aCx, const ObservableArrayProxyHandler* aHandler, void* aOwner) {
+ JS::Rooted<JSObject*> target(aCx, JS::NewArrayObject(aCx, 0));
+ if (NS_WARN_IF(!target)) {
+ return nullptr;
+ }
+
+ JS::Rooted<JS::Value> targetValue(aCx, JS::ObjectValue(*target));
+ JS::Rooted<JSObject*> proxy(
+ aCx, js::NewProxyObject(aCx, aHandler, targetValue, nullptr));
+ if (!proxy) {
+ return nullptr;
+ }
+ js::SetProxyReservedSlot(proxy, OBSERVABLE_ARRAY_DOM_INTERFACE_SLOT,
+ JS::PrivateValue(aOwner));
+ return proxy;
+}
+
+bool GetObservableArrayBackingObject(
+ JSContext* aCx, JS::Handle<JSObject*> aObj, size_t aSlotIndex,
+ JS::MutableHandle<JSObject*> aBackingObj, bool* aBackingObjCreated,
+ const ObservableArrayProxyHandler* aHandler, void* aOwner) {
+ return GetBackingObject<NewObservableArrayProxyObject>(
+ aCx, aObj, aSlotIndex, aBackingObj, aBackingObjCreated, aHandler, aOwner);
+}
+
+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::RootedVector<JS::Value> newArgs(aCx);
+ // Arguments are passed in as value, key, object. Keep value and key, replace
+ // object with the maplike/setlike object.
+ if (!newArgs.append(args.get(0))) {
+ return false;
+ }
+ if (!newArgs.append(args.get(1))) {
+ return false;
+ }
+ if (!newArgs.append(maplikeOrSetlikeObj)) {
+ return false;
+ }
+ JS::Rooted<JS::Value> rval(aCx, JS::UndefinedValue());
+ // Now actually call the user specified callback
+ return JS::Call(aCx, args.thisv(), callbackFn, newArgs, &rval);
+}
+
+static inline prototypes::ID GetProtoIdForNewtarget(
+ JS::Handle<JSObject*> aNewTarget) {
+ const JSClass* newTargetClass = JS::GetClass(aNewTarget);
+ if (IsDOMIfaceAndProtoClass(newTargetClass)) {
+ const DOMIfaceAndProtoJSClass* newTargetIfaceClass =
+ DOMIfaceAndProtoJSClass::FromJSClass(newTargetClass);
+ if (newTargetIfaceClass->mType == eInterface) {
+ return newTargetIfaceClass->mPrototypeID;
+ }
+ } else if (JS_IsNativeFunction(aNewTarget, Constructor)) {
+ return GetNativePropertyHooksFromConstructorFunction(aNewTarget)
+ ->mPrototypeID;
+ }
+
+ return prototypes::id::_ID_Count;
+}
+
+bool GetDesiredProto(JSContext* aCx, const JS::CallArgs& aCallArgs,
+ prototypes::id::ID aProtoId,
+ CreateInterfaceObjectsMethod aCreator,
+ JS::MutableHandle<JSObject*> aDesiredProto) {
+ // This basically implements
+ // https://heycam.github.io/webidl/#internally-create-a-new-object-implementing-the-interface
+ // step 3.
+ MOZ_ASSERT(aCallArgs.isConstructing(), "How did we end up here?");
+
+ // The desired prototype depends on the actual constructor that was invoked,
+ // which is passed to us as the newTarget in the callargs. We want to do
+ // something akin to the ES6 specification's GetProtototypeFromConstructor (so
+ // get .prototype on the newTarget, with a fallback to some sort of default).
+
+ // First, a fast path for the case when the the constructor is in fact one of
+ // our DOM constructors. This is safe because on those the "constructor"
+ // property is non-configurable and non-writable, so we don't have to do the
+ // slow JS_GetProperty call.
+ JS::Rooted<JSObject*> newTarget(aCx, &aCallArgs.newTarget().toObject());
+ MOZ_ASSERT(JS::IsCallable(newTarget));
+ JS::Rooted<JSObject*> originalNewTarget(aCx, newTarget);
+ // See whether we have a known DOM constructor here, such that we can take a
+ // fast path.
+ prototypes::ID protoID = GetProtoIdForNewtarget(newTarget);
+ if (protoID == prototypes::id::_ID_Count) {
+ // We might still have a cross-compartment wrapper for a known DOM
+ // constructor. CheckedUnwrapStatic is fine here, because we're looking for
+ // DOM constructors and those can't be cross-origin objects.
+ newTarget = js::CheckedUnwrapStatic(newTarget);
+ if (newTarget && newTarget != originalNewTarget) {
+ protoID = GetProtoIdForNewtarget(newTarget);
+ }
+ }
+
+ if (protoID != prototypes::id::_ID_Count) {
+ ProtoAndIfaceCache& protoAndIfaceCache =
+ *GetProtoAndIfaceCache(JS::GetNonCCWObjectGlobal(newTarget));
+ aDesiredProto.set(protoAndIfaceCache.EntrySlotMustExist(protoID));
+ if (newTarget != originalNewTarget) {
+ return JS_WrapObject(aCx, aDesiredProto);
+ }
+ return true;
+ }
+
+ // Slow path. This basically duplicates the ES6 spec's
+ // GetPrototypeFromConstructor except that instead of taking a string naming
+ // the fallback prototype we determine the fallback based on the proto id we
+ // were handed.
+ //
+ // Note that it's very important to do this property get on originalNewTarget,
+ // not our unwrapped newTarget, since we want to get Xray behavior here as
+ // needed.
+ // XXXbz for speed purposes, using a preinterned id here sure would be nice.
+ // We can't use GetJSIDByIndex, because that only works on the main thread,
+ // not workers.
+ JS::Rooted<JS::Value> protoVal(aCx);
+ if (!JS_GetProperty(aCx, originalNewTarget, "prototype", &protoVal)) {
+ return false;
+ }
+
+ if (protoVal.isObject()) {
+ aDesiredProto.set(&protoVal.toObject());
+ return true;
+ }
+
+ // Fall back to getting the proto for our given proto id in the realm that
+ // GetFunctionRealm(newTarget) returns.
+ JS::Rooted<JS::Realm*> realm(aCx, JS::GetFunctionRealm(aCx, newTarget));
+ if (!realm) {
+ return false;
+ }
+
+ {
+ // JS::GetRealmGlobalOrNull should not be returning null here, because we
+ // have live objects in the Realm.
+ JSAutoRealm ar(aCx, JS::GetRealmGlobalOrNull(realm));
+ aDesiredProto.set(
+ GetPerInterfaceObjectHandle(aCx, aProtoId, aCreator, true));
+ if (!aDesiredProto) {
+ return false;
+ }
+ }
+
+ return MaybeWrapObject(aCx, aDesiredProto);
+}
+
+namespace {
+
+class MOZ_RAII AutoConstructionDepth final {
+ public:
+ MOZ_IMPLICIT AutoConstructionDepth(CustomElementDefinition* aDefinition)
+ : mDefinition(aDefinition) {
+ MOZ_ASSERT(mDefinition->mConstructionStack.IsEmpty());
+
+ mDefinition->mConstructionDepth++;
+ // If the mConstructionDepth isn't matched with the length of mPrefixStack,
+ // this means the constructor is called directly from JS, i.e.
+ // 'new CustomElementConstructor()', we have to push a dummy prefix into
+ // stack.
+ if (mDefinition->mConstructionDepth > mDefinition->mPrefixStack.Length()) {
+ mDidPush = true;
+ mDefinition->mPrefixStack.AppendElement(nullptr);
+ }
+
+ MOZ_ASSERT(mDefinition->mConstructionDepth ==
+ mDefinition->mPrefixStack.Length());
+ }
+
+ ~AutoConstructionDepth() {
+ MOZ_ASSERT(mDefinition->mConstructionDepth > 0);
+ MOZ_ASSERT(mDefinition->mConstructionDepth ==
+ mDefinition->mPrefixStack.Length());
+
+ if (mDidPush) {
+ MOZ_ASSERT(mDefinition->mPrefixStack.LastElement() == nullptr);
+ mDefinition->mPrefixStack.RemoveLastElement();
+ }
+ mDefinition->mConstructionDepth--;
+ }
+
+ private:
+ CustomElementDefinition* mDefinition;
+ bool mDidPush = false;
+};
+
+} // anonymous namespace
+
+// https://html.spec.whatwg.org/multipage/dom.html#htmlconstructor
+namespace binding_detail {
+bool HTMLConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp,
+ constructors::id::ID aConstructorId,
+ prototypes::id::ID aProtoId,
+ CreateInterfaceObjectsMethod aCreator) {
+ JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp);
+
+ // Per spec, this is technically part of step 3, but doing the check
+ // directly lets us provide a better error message. And then in
+ // step 2 we can work with newTarget in a simpler way because we
+ // know it's an object.
+ if (!args.isConstructing()) {
+ return ThrowConstructorWithoutNew(aCx,
+ NamesOfInterfacesWithProtos(aProtoId));
+ }
+
+ JS::Rooted<JSObject*> callee(aCx, &args.callee());
+ // 'callee' is not a function here; it's either an Xray for our interface
+ // object or the interface object itself. So caling XrayAwareCalleeGlobal on
+ // it is not safe. But since in the Xray case it's a wrapper for our
+ // interface object, we can just construct our GlobalObject from it and end
+ // up with the right thing.
+ GlobalObject global(aCx, callee);
+ if (global.Failed()) {
+ return false;
+ }
+
+ // Now we start the [HTMLConstructor] algorithm steps from
+ // https://html.spec.whatwg.org/multipage/dom.html#htmlconstructor
+
+ ErrorResult rv;
+ auto scopeExit =
+ MakeScopeExit([&]() { Unused << rv.MaybeSetPendingException(aCx); });
+
+ // Step 1.
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(global.GetAsSupports());
+ if (!window) {
+ // This means we ended up with an HTML Element interface object defined in
+ // a non-Window scope. That's ... pretty unexpected.
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return false;
+ }
+ RefPtr<mozilla::dom::CustomElementRegistry> registry(
+ window->CustomElements());
+
+ // Technically, per spec, a window always has a document. In Gecko, a
+ // sufficiently torn-down window might not, so check for that case. We're
+ // going to need a document to create an element.
+ Document* doc = window->GetExtantDoc();
+ if (!doc) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return false;
+ }
+
+ // Step 2.
+
+ // The newTarget might be a cross-compartment wrapper. Get the underlying
+ // object so we can do the spec's object-identity checks. If we ever stop
+ // unwrapping here, carefully audit uses of newTarget below!
+ //
+ // Note that the ES spec enforces that newTarget is always a constructor (in
+ // the sense of having a [[Construct]]), so it's not a cross-origin object and
+ // we can use CheckedUnwrapStatic.
+ JS::Rooted<JSObject*> newTarget(
+ aCx, js::CheckedUnwrapStatic(&args.newTarget().toObject()));
+ if (!newTarget) {
+ rv.ThrowTypeError<MSG_ILLEGAL_CONSTRUCTOR>();
+ return false;
+ }
+
+ // Enter the compartment of our underlying newTarget object, so we end
+ // up comparing to the constructor object for our interface from that global.
+ // XXXbz This is not what the spec says to do, and it's not super-clear to me
+ // at this point why we're doing it. Why not just compare |newTarget| and
+ // |callee| if the intent is just to prevent registration of HTML interface
+ // objects as constructors? Of course it's not clear that the spec check
+ // makes sense to start with: https://github.com/whatwg/html/issues/3575
+ {
+ JSAutoRealm ar(aCx, newTarget);
+ JS::Handle<JSObject*> constructor =
+ GetPerInterfaceObjectHandle(aCx, aConstructorId, aCreator, true);
+ if (!constructor) {
+ return false;
+ }
+ if (newTarget == constructor) {
+ rv.ThrowTypeError<MSG_ILLEGAL_CONSTRUCTOR>();
+ return false;
+ }
+ }
+
+ // Step 3.
+ CustomElementDefinition* definition =
+ registry->LookupCustomElementDefinition(aCx, newTarget);
+ if (!definition) {
+ rv.ThrowTypeError<MSG_ILLEGAL_CONSTRUCTOR>();
+ return false;
+ }
+
+ // Steps 4, 5, 6 do some sanity checks on our callee. We add to those a
+ // determination of what sort of element we're planning to construct.
+ // Technically, this should happen (implicitly) in step 8, but this
+ // determination is side-effect-free, so it's OK.
+ int32_t ns = definition->mNamespaceID;
+
+ constructorGetterCallback cb = nullptr;
+ if (ns == kNameSpaceID_XUL) {
+ if (definition->mLocalName == nsGkAtoms::description ||
+ definition->mLocalName == nsGkAtoms::label) {
+ cb = XULTextElement_Binding::GetConstructorObject;
+ } else if (definition->mLocalName == nsGkAtoms::resizer) {
+ cb = XULResizerElement_Binding::GetConstructorObject;
+ } else if (definition->mLocalName == nsGkAtoms::menupopup ||
+ definition->mLocalName == nsGkAtoms::popup ||
+ definition->mLocalName == nsGkAtoms::panel ||
+ definition->mLocalName == nsGkAtoms::tooltip) {
+ cb = XULPopupElement_Binding::GetConstructorObject;
+ } else if (definition->mLocalName == nsGkAtoms::iframe ||
+ definition->mLocalName == nsGkAtoms::browser ||
+ definition->mLocalName == nsGkAtoms::editor) {
+ cb = XULFrameElement_Binding::GetConstructorObject;
+ } else if (definition->mLocalName == nsGkAtoms::menu ||
+ definition->mLocalName == nsGkAtoms::menulist) {
+ cb = XULMenuElement_Binding::GetConstructorObject;
+ } else if (definition->mLocalName == nsGkAtoms::tree) {
+ cb = XULTreeElement_Binding::GetConstructorObject;
+ } else {
+ cb = XULElement_Binding::GetConstructorObject;
+ }
+ }
+
+ int32_t tag = eHTMLTag_userdefined;
+ if (!definition->IsCustomBuiltIn()) {
+ // Step 4.
+ // If the definition is for an autonomous custom element, the active
+ // function should be HTMLElement or extend from XULElement.
+ if (!cb) {
+ cb = HTMLElement_Binding::GetConstructorObject;
+ }
+
+ // We want to get the constructor from our global's realm, not the
+ // caller realm.
+ JSAutoRealm ar(aCx, global.Get());
+ JS::Rooted<JSObject*> constructor(aCx, cb(aCx));
+
+ // CheckedUnwrapStatic is OK here, since our callee is callable, hence not a
+ // cross-origin object.
+ if (constructor != js::CheckedUnwrapStatic(callee)) {
+ rv.ThrowTypeError<MSG_ILLEGAL_CONSTRUCTOR>();
+ return false;
+ }
+ } else {
+ if (ns == kNameSpaceID_XHTML) {
+ // Step 5.
+ // If the definition is for a customized built-in element, the localName
+ // should be one of the ones defined in the specification for this
+ // interface.
+ tag = nsHTMLTags::CaseSensitiveAtomTagToId(definition->mLocalName);
+ if (tag == eHTMLTag_userdefined) {
+ rv.ThrowTypeError<MSG_ILLEGAL_CONSTRUCTOR>();
+ return false;
+ }
+
+ MOZ_ASSERT(tag <= NS_HTML_TAG_MAX, "tag is out of bounds");
+
+ // If the definition is for a customized built-in element, the active
+ // function should be the localname's element interface.
+ cb = sConstructorGetterCallback[tag];
+ }
+
+ if (!cb) {
+ rv.ThrowTypeError<MSG_ILLEGAL_CONSTRUCTOR>();
+ return false;
+ }
+
+ // We want to get the constructor from our global's realm, not the
+ // caller realm.
+ JSAutoRealm ar(aCx, global.Get());
+ JS::Rooted<JSObject*> constructor(aCx, cb(aCx));
+ if (!constructor) {
+ return false;
+ }
+
+ // CheckedUnwrapStatic is OK here, since our callee is callable, hence not a
+ // cross-origin object.
+ if (constructor != js::CheckedUnwrapStatic(callee)) {
+ rv.ThrowTypeError<MSG_ILLEGAL_CONSTRUCTOR>();
+ return false;
+ }
+ }
+
+ // Steps 7 and 8.
+ JS::Rooted<JSObject*> desiredProto(aCx);
+ if (!GetDesiredProto(aCx, args, aProtoId, aCreator, &desiredProto)) {
+ return false;
+ }
+
+ MOZ_ASSERT(desiredProto, "How could we not have a prototype by now?");
+
+ // We need to do some work to actually return an Element, so we do step 8 on
+ // one branch and steps 9-12 on another branch, then common up the "return
+ // element" work.
+ RefPtr<Element> element;
+ nsTArray<RefPtr<Element>>& constructionStack = definition->mConstructionStack;
+ if (constructionStack.IsEmpty()) {
+ // Step 8.
+ // Now we go to construct an element. We want to do this in global's
+ // realm, not caller realm (the normal constructor behavior),
+ // just in case those elements create JS things.
+ JSAutoRealm ar(aCx, global.Get());
+ AutoConstructionDepth acd(definition);
+
+ RefPtr<NodeInfo> nodeInfo = doc->NodeInfoManager()->GetNodeInfo(
+ definition->mLocalName, definition->mPrefixStack.LastElement(), ns,
+ nsINode::ELEMENT_NODE);
+ MOZ_ASSERT(nodeInfo);
+
+ if (ns == kNameSpaceID_XUL) {
+ element = nsXULElement::Construct(nodeInfo.forget());
+
+ } else {
+ if (tag == eHTMLTag_userdefined) {
+ // Autonomous custom element.
+ element = NS_NewHTMLElement(nodeInfo.forget());
+ } else {
+ // Customized built-in element.
+ element = CreateHTMLElement(tag, nodeInfo.forget(), NOT_FROM_PARSER);
+ }
+ }
+
+ element->SetCustomElementData(MakeUnique<CustomElementData>(
+ definition->mType, CustomElementData::State::eCustom));
+
+ element->SetCustomElementDefinition(definition);
+ } else {
+ // Step 9.
+ element = constructionStack.LastElement();
+
+ // Step 10.
+ if (element == ALREADY_CONSTRUCTED_MARKER) {
+ rv.ThrowTypeError(
+ "Cannot instantiate a custom element inside its own constructor "
+ "during upgrades");
+ return false;
+ }
+
+ // Step 11.
+ // Do prototype swizzling for upgrading a custom element here, for cases
+ // when we have a reflector already. If we don't have one yet, we will
+ // create it with the right proto (by calling GetOrCreateDOMReflector with
+ // that proto), and will preserve it by means of the proto != canonicalProto
+ // check).
+ JS::Rooted<JSObject*> reflector(aCx, element->GetWrapper());
+ if (reflector) {
+ // reflector might be in different realm.
+ JSAutoRealm ar(aCx, reflector);
+ JS::Rooted<JSObject*> givenProto(aCx, desiredProto);
+ if (!JS_WrapObject(aCx, &givenProto) ||
+ !JS_SetPrototype(aCx, reflector, givenProto)) {
+ return false;
+ }
+ PreserveWrapper(element.get());
+ }
+
+ // Step 12.
+ constructionStack.LastElement() = ALREADY_CONSTRUCTED_MARKER;
+ }
+
+ // Tail end of step 8 and step 13: returning the element. We want to do this
+ // part in the global's realm, though in practice it won't matter much
+ // because Element always knows which realm it should be created in.
+ JSAutoRealm ar(aCx, global.Get());
+ if (!js::IsObjectInContextCompartment(desiredProto, aCx) &&
+ !JS_WrapObject(aCx, &desiredProto)) {
+ return false;
+ }
+
+ return GetOrCreateDOMReflector(aCx, element, args.rval(), desiredProto);
+}
+} // namespace binding_detail
+
+#ifdef DEBUG
+namespace binding_detail {
+void AssertReflectorHasGivenProto(JSContext* aCx, JSObject* aReflector,
+ JS::Handle<JSObject*> aGivenProto) {
+ if (!aGivenProto) {
+ // Nothing to assert here
+ return;
+ }
+
+ JS::Rooted<JSObject*> reflector(aCx, aReflector);
+ JSAutoRealm ar(aCx, reflector);
+ JS::Rooted<JSObject*> reflectorProto(aCx);
+ bool ok = JS_GetPrototype(aCx, reflector, &reflectorProto);
+ MOZ_ASSERT(ok);
+ // aGivenProto may not be in the right realm here, so we
+ // have to wrap it to compare.
+ JS::Rooted<JSObject*> givenProto(aCx, aGivenProto);
+ ok = JS_WrapObject(aCx, &givenProto);
+ MOZ_ASSERT(ok);
+ MOZ_ASSERT(givenProto == reflectorProto,
+ "How are we supposed to change the proto now?");
+}
+} // namespace binding_detail
+#endif // DEBUG
+
+void SetUseCounter(JSObject* aObject, UseCounter aUseCounter) {
+ nsGlobalWindowInner* win =
+ xpc::WindowGlobalOrNull(js::UncheckedUnwrap(aObject));
+ if (win && win->GetDocument()) {
+ win->GetDocument()->SetUseCounter(aUseCounter);
+ }
+}
+
+void SetUseCounter(UseCounterWorker aUseCounter) {
+ // If this is called from Worklet thread, workerPrivate will be null.
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ if (workerPrivate) {
+ workerPrivate->SetUseCounter(aUseCounter);
+ }
+}
+
+namespace {
+
+#define DEPRECATED_OPERATION(_op) #_op,
+static const char* kDeprecatedOperations[] = {
+#include "nsDeprecatedOperationList.h"
+ nullptr};
+#undef DEPRECATED_OPERATION
+
+void ReportDeprecation(nsIGlobalObject* aGlobal, nsIURI* aURI,
+ DeprecatedOperations aOperation,
+ const nsAString& aFileName,
+ const Nullable<uint32_t>& aLineNumber,
+ const Nullable<uint32_t>& aColumnNumber) {
+ MOZ_ASSERT(aURI);
+
+ // If the URI has the data scheme, report that instead of the spec,
+ // as the spec may be arbitrarily long and we would like to avoid
+ // copying it.
+ nsAutoCString specOrScheme;
+ nsresult rv = nsContentUtils::AnonymizeURI(aURI, specOrScheme);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsAutoString type;
+ type.AssignASCII(kDeprecatedOperations[static_cast<size_t>(aOperation)]);
+
+ nsAutoCString key;
+ key.AssignASCII(kDeprecatedOperations[static_cast<size_t>(aOperation)]);
+ key.AppendASCII("Warning");
+
+ nsAutoString msg;
+ rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ key.get(), msg);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ RefPtr<DeprecationReportBody> body =
+ new DeprecationReportBody(aGlobal, type, nullptr /* date */, msg,
+ aFileName, aLineNumber, aColumnNumber);
+
+ ReportingUtils::Report(aGlobal, nsGkAtoms::deprecation, u"default"_ns,
+ NS_ConvertUTF8toUTF16(specOrScheme), body);
+}
+
+// This runnable is used to write a deprecation message from a worker to the
+// console running on the main-thread.
+class DeprecationWarningRunnable final
+ : public WorkerProxyToMainThreadRunnable {
+ const DeprecatedOperations mOperation;
+
+ public:
+ explicit DeprecationWarningRunnable(DeprecatedOperations aOperation)
+ : mOperation(aOperation) {}
+
+ private:
+ void RunOnMainThread(WorkerPrivate* aWorkerPrivate) override {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aWorkerPrivate);
+
+ // Walk up to our containing page
+ WorkerPrivate* wp = aWorkerPrivate;
+ while (wp->GetParent()) {
+ wp = wp->GetParent();
+ }
+
+ nsPIDOMWindowInner* window = wp->GetWindow();
+ if (window && window->GetExtantDoc()) {
+ window->GetExtantDoc()->WarnOnceAbout(mOperation);
+ }
+ }
+
+ void RunBackOnWorkerThreadForCleanup(WorkerPrivate* aWorkerPrivate) override {
+ }
+};
+
+void MaybeShowDeprecationWarning(const GlobalObject& aGlobal,
+ DeprecatedOperations aOperation) {
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (window && window->GetExtantDoc()) {
+ window->GetExtantDoc()->WarnOnceAbout(aOperation);
+ }
+ return;
+ }
+
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aGlobal.Context());
+ if (!workerPrivate) {
+ return;
+ }
+
+ RefPtr<DeprecationWarningRunnable> runnable =
+ new DeprecationWarningRunnable(aOperation);
+ runnable->Dispatch(workerPrivate);
+}
+
+void MaybeReportDeprecation(const GlobalObject& aGlobal,
+ DeprecatedOperations aOperation) {
+ nsCOMPtr<nsIURI> uri;
+
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window || !window->GetExtantDoc()) {
+ return;
+ }
+
+ uri = window->GetExtantDoc()->GetDocumentURI();
+ } else {
+ WorkerPrivate* workerPrivate =
+ GetWorkerPrivateFromContext(aGlobal.Context());
+ if (!workerPrivate) {
+ return;
+ }
+
+ uri = workerPrivate->GetResolvedScriptURI();
+ }
+
+ if (NS_WARN_IF(!uri)) {
+ return;
+ }
+
+ nsAutoString fileName;
+ Nullable<uint32_t> lineNumber;
+ Nullable<uint32_t> columnNumber;
+ uint32_t line = 0;
+ uint32_t column = 0;
+ if (nsJSUtils::GetCallingLocation(aGlobal.Context(), fileName, &line,
+ &column)) {
+ lineNumber.SetValue(line);
+ columnNumber.SetValue(column);
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ MOZ_ASSERT(global);
+
+ ReportDeprecation(global, uri, aOperation, fileName, lineNumber,
+ columnNumber);
+}
+
+} // anonymous namespace
+
+void DeprecationWarning(JSContext* aCx, JSObject* aObject,
+ DeprecatedOperations aOperation) {
+ GlobalObject global(aCx, aObject);
+ if (global.Failed()) {
+ NS_ERROR("Could not create global for DeprecationWarning");
+ return;
+ }
+
+ DeprecationWarning(global, aOperation);
+}
+
+void DeprecationWarning(const GlobalObject& aGlobal,
+ DeprecatedOperations aOperation) {
+ MaybeShowDeprecationWarning(aGlobal, aOperation);
+ MaybeReportDeprecation(aGlobal, aOperation);
+}
+
+namespace binding_detail {
+JSObject* UnprivilegedJunkScopeOrWorkerGlobal(const fallible_t&) {
+ if (NS_IsMainThread()) {
+ return xpc::UnprivilegedJunkScope(fallible);
+ }
+
+ return GetCurrentThreadWorkerGlobal();
+}
+} // namespace binding_detail
+
+JS::Handle<JSObject*> GetPerInterfaceObjectHandle(
+ JSContext* aCx, size_t aSlotId, CreateInterfaceObjectsMethod aCreator,
+ bool aDefineOnGlobal) {
+ /* Make sure our global is sane. Hopefully we can remove this sometime */
+ JSObject* global = JS::CurrentGlobalOrNull(aCx);
+ if (!(JS::GetClass(global)->flags & JSCLASS_DOM_GLOBAL)) {
+ return nullptr;
+ }
+
+ /* Check to see whether the interface objects are already installed */
+ ProtoAndIfaceCache& protoAndIfaceCache = *GetProtoAndIfaceCache(global);
+ if (!protoAndIfaceCache.HasEntryInSlot(aSlotId)) {
+ JS::Rooted<JSObject*> rootedGlobal(aCx, global);
+ aCreator(aCx, rootedGlobal, protoAndIfaceCache, aDefineOnGlobal);
+ }
+
+ /*
+ * The object might _still_ be null, but that's OK.
+ *
+ * Calling fromMarkedLocation() is safe because protoAndIfaceCache is
+ * traced by TraceProtoAndIfaceCache() and its contents are never
+ * changed after they have been set.
+ *
+ * Calling address() avoids the read barrier that does gray unmarking, but
+ * it's not possible for the object to be gray here.
+ */
+
+ const JS::Heap<JSObject*>& entrySlot =
+ protoAndIfaceCache.EntrySlotMustExist(aSlotId);
+ JS::AssertObjectIsNotGray(entrySlot);
+ return JS::Handle<JSObject*>::fromMarkedLocation(entrySlot.address());
+}
+
+namespace binding_detail {
+bool IsGetterEnabled(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JSJitGetterOp aGetter,
+ const Prefable<const JSPropertySpec>* aAttributes) {
+ MOZ_ASSERT(aAttributes);
+ MOZ_ASSERT(aAttributes->specs);
+ do {
+ if (aAttributes->isEnabled(aCx, aObj)) {
+ const JSPropertySpec* specs = aAttributes->specs;
+ do {
+ if (!specs->isAccessor() || specs->isSelfHosted()) {
+ // It won't have a JSJitGetterOp.
+ continue;
+ }
+ const JSJitInfo* info = specs->u.accessors.getter.native.info;
+ if (!info) {
+ continue;
+ }
+ MOZ_ASSERT(info->type() == JSJitInfo::OpType::Getter);
+ if (info->getter == aGetter) {
+ return true;
+ }
+ } while ((++specs)->name);
+ }
+ } while ((++aAttributes)->specs);
+
+ // Didn't find it.
+ return false;
+}
+
+already_AddRefed<Promise> CreateRejectedPromiseFromThrownException(
+ JSContext* aCx, ErrorResult& aError) {
+ if (!JS_IsExceptionPending(aCx)) {
+ // If there is no pending exception here but we're ending up in this code,
+ // that means the callee threw an uncatchable exception. Just propagate that
+ // out as-is. Promise::RejectWithExceptionFromContext also checks this, but
+ // we want to bail out here before trying to get the globals.
+ aError.ThrowUncatchableException();
+ return nullptr;
+ }
+
+ GlobalObject promiseGlobal(aCx, GetEntryGlobal()->GetGlobalJSObject());
+ if (promiseGlobal.Failed()) {
+ aError.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global =
+ do_QueryInterface(promiseGlobal.GetAsSupports());
+ if (!global) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ return Promise::RejectWithExceptionFromContext(global, aCx, aError);
+}
+
+} // namespace binding_detail
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h
new file mode 100644
index 0000000000..fa1002af1c
--- /dev/null
+++ b/dom/bindings/BindingUtils.h
@@ -0,0 +1,3267 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_BindingUtils_h__
+#define mozilla_dom_BindingUtils_h__
+
+#include <type_traits>
+
+#include "jsfriendapi.h"
+#include "js/CharacterEncoding.h"
+#include "js/Conversions.h"
+#include "js/experimental/JitInfo.h" // JSJitGetterOp, JSJitInfo
+#include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy, js::ToWindowProxyIfWindow
+#include "js/MemoryFunctions.h"
+#include "js/Object.h" // JS::GetClass, JS::GetCompartment, JS::GetReservedSlot, JS::SetReservedSlot
+#include "js/RealmOptions.h"
+#include "js/String.h" // JS::GetLatin1LinearStringChars, JS::GetTwoByteLinearStringChars, JS::GetLinearStringLength, JS::LinearStringHasLatin1Chars, JS::StringHasLatin1Chars
+#include "js/Wrapper.h"
+#include "js/Zone.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Array.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DeferredFinalize.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/BindingCallContext.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/DOMJSClass.h"
+#include "mozilla/dom/DOMJSProxyHandler.h"
+#include "mozilla/dom/JSSlots.h"
+#include "mozilla/dom/NonRefcountedDOMObject.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/PrototypeList.h"
+#include "mozilla/dom/RemoteObjectProxy.h"
+#include "mozilla/SegmentedVector.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsIGlobalObject.h"
+#include "nsJSUtils.h"
+#include "nsISupportsImpl.h"
+#include "xpcObjectHelper.h"
+#include "xpcpublic.h"
+#include "nsIVariant.h"
+#include "mozilla/dom/FakeString.h"
+
+#include "nsWrapperCacheInlines.h"
+
+class nsGlobalWindowInner;
+class nsGlobalWindowOuter;
+class nsIInterfaceRequestor;
+
+namespace mozilla {
+
+enum UseCounter : int16_t;
+enum class UseCounterWorker : int16_t;
+
+namespace dom {
+class CustomElementReactionsStack;
+class Document;
+class EventTarget;
+class MessageManagerGlobal;
+class ObservableArrayProxyHandler;
+class DedicatedWorkerGlobalScope;
+template <typename KeyType, typename ValueType>
+class Record;
+class WindowProxyHolder;
+
+enum class DeprecatedOperations : uint16_t;
+
+nsresult UnwrapArgImpl(JSContext* cx, JS::Handle<JSObject*> src,
+ const nsIID& iid, void** ppArg);
+
+/** Convert a jsval to an XPCOM pointer. Caller must not assume that src will
+ keep the XPCOM pointer rooted. */
+template <class Interface>
+inline nsresult UnwrapArg(JSContext* cx, JS::Handle<JSObject*> src,
+ Interface** ppArg) {
+ return UnwrapArgImpl(cx, src, NS_GET_TEMPLATE_IID(Interface),
+ reinterpret_cast<void**>(ppArg));
+}
+
+nsresult UnwrapWindowProxyArg(JSContext* cx, JS::Handle<JSObject*> src,
+ WindowProxyHolder& ppArg);
+
+// Returns true if the JSClass is used for DOM objects.
+inline bool IsDOMClass(const JSClass* clasp) {
+ return clasp->flags & JSCLASS_IS_DOMJSCLASS;
+}
+
+// Return true if the JSClass is used for non-proxy DOM objects.
+inline bool IsNonProxyDOMClass(const JSClass* clasp) {
+ return IsDOMClass(clasp) && clasp->isNativeObject();
+}
+
+// Returns true if the JSClass is used for DOM interface and interface
+// prototype objects.
+inline bool IsDOMIfaceAndProtoClass(const JSClass* clasp) {
+ return clasp->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS;
+}
+
+static_assert(DOM_OBJECT_SLOT == 0,
+ "DOM_OBJECT_SLOT doesn't match the proxy private slot. "
+ "Expect bad things");
+template <class T>
+inline T* UnwrapDOMObject(JSObject* obj) {
+ MOZ_ASSERT(IsDOMClass(JS::GetClass(obj)),
+ "Don't pass non-DOM objects to this function");
+
+ JS::Value val = JS::GetReservedSlot(obj, DOM_OBJECT_SLOT);
+ return static_cast<T*>(val.toPrivate());
+}
+
+template <class T>
+inline T* UnwrapPossiblyNotInitializedDOMObject(JSObject* obj) {
+ // This is used by the OjectMoved JSClass hook which can be called before
+ // JS_NewObject has returned and so before we have a chance to set
+ // DOM_OBJECT_SLOT to anything useful.
+
+ MOZ_ASSERT(IsDOMClass(JS::GetClass(obj)),
+ "Don't pass non-DOM objects to this function");
+
+ JS::Value val = JS::GetReservedSlot(obj, DOM_OBJECT_SLOT);
+ if (val.isUndefined()) {
+ return nullptr;
+ }
+ return static_cast<T*>(val.toPrivate());
+}
+
+inline const DOMJSClass* GetDOMClass(const JSClass* clasp) {
+ return IsDOMClass(clasp) ? DOMJSClass::FromJSClass(clasp) : nullptr;
+}
+
+inline const DOMJSClass* GetDOMClass(JSObject* obj) {
+ return GetDOMClass(JS::GetClass(obj));
+}
+
+inline nsISupports* UnwrapDOMObjectToISupports(JSObject* aObject) {
+ const DOMJSClass* clasp = GetDOMClass(aObject);
+ if (!clasp || !clasp->mDOMObjectIsISupports) {
+ return nullptr;
+ }
+
+ return UnwrapPossiblyNotInitializedDOMObject<nsISupports>(aObject);
+}
+
+inline bool IsDOMObject(JSObject* obj) { return IsDOMClass(JS::GetClass(obj)); }
+
+// There are two valid ways to use UNWRAP_OBJECT: Either obj needs to
+// be a MutableHandle<JSObject*>, or value needs to be a strong-reference
+// smart pointer type (OwningNonNull or RefPtr or nsCOMPtr), in which case obj
+// can be anything that converts to JSObject*.
+//
+// This can't be used with Window, EventTarget, or Location as the "Interface"
+// argument (and will fail a static_assert if you try to do that). Use
+// UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT to unwrap to those interfaces.
+#define UNWRAP_OBJECT(Interface, obj, value) \
+ mozilla::dom::binding_detail::UnwrapObjectWithCrossOriginAsserts< \
+ mozilla::dom::prototypes::id::Interface, \
+ mozilla::dom::Interface##_Binding::NativeType>(obj, value)
+
+// UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT is just like UNWRAP_OBJECT but requires a
+// JSContext in a Realm that represents "who is doing the unwrapping?" to
+// properly unwrap the object.
+#define UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT(Interface, obj, value, cx) \
+ mozilla::dom::UnwrapObject<mozilla::dom::prototypes::id::Interface, \
+ mozilla::dom::Interface##_Binding::NativeType>( \
+ obj, value, cx)
+
+// Test whether the given object is an instance of the given interface.
+#define IS_INSTANCE_OF(Interface, obj) \
+ mozilla::dom::IsInstanceOf<mozilla::dom::prototypes::id::Interface, \
+ mozilla::dom::Interface##_Binding::NativeType>( \
+ obj)
+
+// Unwrap the given non-wrapper object. This can be used with any obj that
+// converts to JSObject*; as long as that JSObject* is live the return value
+// will be valid.
+#define UNWRAP_NON_WRAPPER_OBJECT(Interface, obj, value) \
+ mozilla::dom::UnwrapNonWrapperObject< \
+ mozilla::dom::prototypes::id::Interface, \
+ mozilla::dom::Interface##_Binding::NativeType>(obj, value)
+
+// Some callers don't want to set an exception when unwrapping fails
+// (for example, overload resolution uses unwrapping to tell what sort
+// of thing it's looking at).
+// U must be something that a T* can be assigned to (e.g. T* or an RefPtr<T>).
+//
+// The obj argument will be mutated to point to CheckedUnwrap of itself if the
+// passed-in value is not a DOM object and CheckedUnwrap succeeds.
+//
+// If mayBeWrapper is true, there are three valid ways to invoke
+// UnwrapObjectInternal: Either obj needs to be a class wrapping a
+// MutableHandle<JSObject*>, with an assignment operator that sets the handle to
+// the given object, or U needs to be a strong-reference smart pointer type
+// (OwningNonNull or RefPtr or nsCOMPtr), or the value being stored in "value"
+// must not escape past being tested for falsiness immediately after the
+// UnwrapObjectInternal call.
+//
+// If mayBeWrapper is false, obj can just be a JSObject*, and U anything that a
+// T* can be assigned to.
+//
+// The cx arg is in practice allowed to be either nullptr or JSContext* or a
+// BindingCallContext reference. If it's nullptr we will do a
+// CheckedUnwrapStatic and it's the caller's responsibility to make sure they're
+// not trying to work with Window or Location objects. Otherwise we'll do a
+// CheckedUnwrapDynamic. This all only matters if mayBeWrapper is true; if it's
+// false just pass nullptr for the cx arg.
+namespace binding_detail {
+template <class T, bool mayBeWrapper, typename U, typename V, typename CxType>
+MOZ_ALWAYS_INLINE nsresult UnwrapObjectInternal(V& obj, U& value,
+ prototypes::ID protoID,
+ uint32_t protoDepth,
+ const CxType& cx) {
+ static_assert(std::is_same_v<CxType, JSContext*> ||
+ std::is_same_v<CxType, BindingCallContext> ||
+ std::is_same_v<CxType, decltype(nullptr)>,
+ "Unexpected CxType");
+
+ /* First check to see whether we have a DOM object */
+ const DOMJSClass* domClass = GetDOMClass(obj);
+ if (domClass) {
+ /* This object is a DOM object. Double-check that it is safely
+ castable to T by checking whether it claims to inherit from the
+ class identified by protoID. */
+ if (domClass->mInterfaceChain[protoDepth] == protoID) {
+ value = UnwrapDOMObject<T>(obj);
+ return NS_OK;
+ }
+ }
+
+ /* Maybe we have a security wrapper or outer window? */
+ if (!mayBeWrapper || !js::IsWrapper(obj)) {
+ // For non-cross-origin-accessible methods and properties, remote object
+ // proxies should behave the same as opaque wrappers.
+ if (IsRemoteObjectProxy(obj)) {
+ return NS_ERROR_XPC_SECURITY_MANAGER_VETO;
+ }
+
+ /* Not a DOM object, not a wrapper, just bail */
+ return NS_ERROR_XPC_BAD_CONVERT_JS;
+ }
+
+ JSObject* unwrappedObj;
+ if (std::is_same_v<CxType, decltype(nullptr)>) {
+ unwrappedObj = js::CheckedUnwrapStatic(obj);
+ } else {
+ unwrappedObj =
+ js::CheckedUnwrapDynamic(obj, cx, /* stopAtWindowProxy = */ false);
+ }
+ if (!unwrappedObj) {
+ return NS_ERROR_XPC_SECURITY_MANAGER_VETO;
+ }
+
+ if (std::is_same_v<CxType, decltype(nullptr)>) {
+ // We might still have a windowproxy here. But it shouldn't matter, because
+ // that's not what the caller is looking for, so we're going to fail out
+ // anyway below once we do the recursive call to ourselves with wrapper
+ // unwrapping disabled.
+ MOZ_ASSERT(!js::IsWrapper(unwrappedObj) || js::IsWindowProxy(unwrappedObj));
+ } else {
+ // We shouldn't have a wrapper by now.
+ MOZ_ASSERT(!js::IsWrapper(unwrappedObj));
+ }
+
+ // Recursive call is OK, because now we're using false for mayBeWrapper and
+ // we never reach this code if that boolean is false, so can't keep calling
+ // ourselves.
+ //
+ // Unwrap into a temporary pointer, because in general unwrapping into
+ // something of type U might trigger GC (e.g. release the value currently
+ // stored in there, with arbitrary consequences) and invalidate the
+ // "unwrappedObj" pointer.
+ T* tempValue = nullptr;
+ nsresult rv = UnwrapObjectInternal<T, false>(unwrappedObj, tempValue, protoID,
+ protoDepth, nullptr);
+ if (NS_SUCCEEDED(rv)) {
+ // Suppress a hazard related to keeping tempValue alive across
+ // UnwrapObjectInternal, because the analysis can't tell that this function
+ // will not GC if maybeWrapped=False and we've already gone through a level
+ // of unwrapping so unwrappedObj will be !IsWrapper.
+ JS::AutoSuppressGCAnalysis suppress;
+
+ // It's very important to not update "obj" with the "unwrappedObj" value
+ // until we know the unwrap has succeeded. Otherwise, in a situation in
+ // which we have an overload of object and primitive we could end up
+ // converting to the primitive from the unwrappedObj, whereas we want to do
+ // it from the original object.
+ obj = unwrappedObj;
+ // And now assign to "value"; at this point we don't care if a GC happens
+ // and invalidates unwrappedObj.
+ value = tempValue;
+ return NS_OK;
+ }
+
+ /* It's the wrong sort of DOM object */
+ return NS_ERROR_XPC_BAD_CONVERT_JS;
+}
+
+struct MutableObjectHandleWrapper {
+ explicit MutableObjectHandleWrapper(JS::MutableHandle<JSObject*> aHandle)
+ : mHandle(aHandle) {}
+
+ void operator=(JSObject* aObject) {
+ MOZ_ASSERT(aObject);
+ mHandle.set(aObject);
+ }
+
+ operator JSObject*() const { return mHandle; }
+
+ private:
+ JS::MutableHandle<JSObject*> mHandle;
+};
+
+struct MutableValueHandleWrapper {
+ explicit MutableValueHandleWrapper(JS::MutableHandle<JS::Value> aHandle)
+ : mHandle(aHandle) {}
+
+ void operator=(JSObject* aObject) {
+ MOZ_ASSERT(aObject);
+#ifdef ENABLE_RECORD_TUPLE
+ MOZ_ASSERT(!js::gc::MaybeForwardedIsExtendedPrimitive(*aObject));
+#endif
+ mHandle.setObject(*aObject);
+ }
+
+ operator JSObject*() const { return &mHandle.toObject(); }
+
+ private:
+ JS::MutableHandle<JS::Value> mHandle;
+};
+
+} // namespace binding_detail
+
+// UnwrapObject overloads that ensure we have a MutableHandle to keep it alive.
+template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
+MOZ_ALWAYS_INLINE nsresult UnwrapObject(JS::MutableHandle<JSObject*> obj,
+ U& value, const CxType& cx) {
+ binding_detail::MutableObjectHandleWrapper wrapper(obj);
+ return binding_detail::UnwrapObjectInternal<T, true>(
+ wrapper, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
+}
+
+template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
+MOZ_ALWAYS_INLINE nsresult UnwrapObject(JS::MutableHandle<JS::Value> obj,
+ U& value, const CxType& cx) {
+ MOZ_ASSERT(obj.isObject());
+ binding_detail::MutableValueHandleWrapper wrapper(obj);
+ return binding_detail::UnwrapObjectInternal<T, true>(
+ wrapper, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
+}
+
+// UnwrapObject overloads that ensure we have a strong ref to keep it alive.
+template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
+MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj, RefPtr<U>& value,
+ const CxType& cx) {
+ return binding_detail::UnwrapObjectInternal<T, true>(
+ obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
+}
+
+template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
+MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj, nsCOMPtr<U>& value,
+ const CxType& cx) {
+ return binding_detail::UnwrapObjectInternal<T, true>(
+ obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
+}
+
+template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
+MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj, OwningNonNull<U>& value,
+ const CxType& cx) {
+ return binding_detail::UnwrapObjectInternal<T, true>(
+ obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
+}
+
+template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
+MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj, NonNull<U>& value,
+ const CxType& cx) {
+ return binding_detail::UnwrapObjectInternal<T, true>(
+ obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
+}
+
+// An UnwrapObject overload that just calls one of the JSObject* ones.
+template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
+MOZ_ALWAYS_INLINE nsresult UnwrapObject(JS::Handle<JS::Value> obj, U& value,
+ const CxType& cx) {
+ MOZ_ASSERT(obj.isObject());
+ return UnwrapObject<PrototypeID, T>(&obj.toObject(), value, cx);
+}
+
+template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
+MOZ_ALWAYS_INLINE nsresult UnwrapObject(JS::Handle<JS::Value> obj,
+ NonNull<U>& value, const CxType& cx) {
+ MOZ_ASSERT(obj.isObject());
+ return UnwrapObject<PrototypeID, T>(&obj.toObject(), value, cx);
+}
+
+template <prototypes::ID PrototypeID>
+MOZ_ALWAYS_INLINE void AssertStaticUnwrapOK() {
+ static_assert(PrototypeID != prototypes::id::Window,
+ "Can't do static unwrap of WindowProxy; use "
+ "UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT or a cross-origin-object "
+ "aware version of IS_INSTANCE_OF");
+ static_assert(PrototypeID != prototypes::id::EventTarget,
+ "Can't do static unwrap of WindowProxy (which an EventTarget "
+ "might be); use UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT or a "
+ "cross-origin-object aware version of IS_INSTANCE_OF");
+ static_assert(PrototypeID != prototypes::id::Location,
+ "Can't do static unwrap of Location; use "
+ "UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT or a cross-origin-object "
+ "aware version of IS_INSTANCE_OF");
+}
+
+namespace binding_detail {
+// This function is just here so we can do some static asserts in a centralized
+// place instead of putting them in every single UnwrapObject overload.
+template <prototypes::ID PrototypeID, class T, typename U, typename V>
+MOZ_ALWAYS_INLINE nsresult UnwrapObjectWithCrossOriginAsserts(V&& obj,
+ U& value) {
+ AssertStaticUnwrapOK<PrototypeID>();
+ return UnwrapObject<PrototypeID, T>(obj, value, nullptr);
+}
+} // namespace binding_detail
+
+template <prototypes::ID PrototypeID, class T>
+MOZ_ALWAYS_INLINE bool IsInstanceOf(JSObject* obj) {
+ AssertStaticUnwrapOK<PrototypeID>();
+ void* ignored;
+ nsresult unwrapped = binding_detail::UnwrapObjectInternal<T, true>(
+ obj, ignored, PrototypeID, PrototypeTraits<PrototypeID>::Depth, nullptr);
+ return NS_SUCCEEDED(unwrapped);
+}
+
+template <prototypes::ID PrototypeID, class T, typename U>
+MOZ_ALWAYS_INLINE nsresult UnwrapNonWrapperObject(JSObject* obj, U& value) {
+ MOZ_ASSERT(!js::IsWrapper(obj));
+ return binding_detail::UnwrapObjectInternal<T, false>(
+ obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, nullptr);
+}
+
+MOZ_ALWAYS_INLINE bool IsConvertibleToDictionary(JS::Handle<JS::Value> val) {
+ return val.isNullOrUndefined() || val.isObject();
+}
+
+// The items in the protoAndIfaceCache are indexed by the prototypes::id::ID,
+// constructors::id::ID and namedpropertiesobjects::id::ID enums, in that order.
+// The end of the prototype objects should be the start of the interface
+// objects, and the end of the interface objects should be the start of the
+// named properties objects.
+static_assert((size_t)constructors::id::_ID_Start ==
+ (size_t)prototypes::id::_ID_Count &&
+ (size_t)namedpropertiesobjects::id::_ID_Start ==
+ (size_t)constructors::id::_ID_Count,
+ "Overlapping or discontiguous indexes.");
+const size_t kProtoAndIfaceCacheCount = namedpropertiesobjects::id::_ID_Count;
+
+class ProtoAndIfaceCache {
+ // The caching strategy we use depends on what sort of global we're dealing
+ // with. For a window-like global, we want everything to be as fast as
+ // possible, so we use a flat array, indexed by prototype/constructor ID.
+ // For everything else (e.g. globals for JSMs), space is more important than
+ // speed, so we use a two-level lookup table.
+
+ class ArrayCache
+ : public Array<JS::Heap<JSObject*>, kProtoAndIfaceCacheCount> {
+ public:
+ bool HasEntryInSlot(size_t i) {
+ // Do an explicit call to the Heap<…> bool conversion operator. Because
+ // that operator is marked explicit we'd otherwise end up doing an
+ // implicit cast to JSObject* first, causing an unnecessary call to
+ // exposeToActiveJS().
+ return bool((*this)[i]);
+ }
+
+ JS::Heap<JSObject*>& EntrySlotOrCreate(size_t i) { return (*this)[i]; }
+
+ JS::Heap<JSObject*>& EntrySlotMustExist(size_t i) { return (*this)[i]; }
+
+ void Trace(JSTracer* aTracer) {
+ for (size_t i = 0; i < ArrayLength(*this); ++i) {
+ JS::TraceEdge(aTracer, &(*this)[i], "protoAndIfaceCache[i]");
+ }
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
+ return aMallocSizeOf(this);
+ }
+ };
+
+ class PageTableCache {
+ public:
+ PageTableCache() { memset(mPages.begin(), 0, sizeof(mPages)); }
+
+ ~PageTableCache() {
+ for (size_t i = 0; i < ArrayLength(mPages); ++i) {
+ delete mPages[i];
+ }
+ }
+
+ bool HasEntryInSlot(size_t i) {
+ MOZ_ASSERT(i < kProtoAndIfaceCacheCount);
+ size_t pageIndex = i / kPageSize;
+ size_t leafIndex = i % kPageSize;
+ Page* p = mPages[pageIndex];
+ if (!p) {
+ return false;
+ }
+ // Do an explicit call to the Heap<…> bool conversion operator. Because
+ // that operator is marked explicit we'd otherwise end up doing an
+ // implicit cast to JSObject* first, causing an unnecessary call to
+ // exposeToActiveJS().
+ return bool((*p)[leafIndex]);
+ }
+
+ JS::Heap<JSObject*>& EntrySlotOrCreate(size_t i) {
+ MOZ_ASSERT(i < kProtoAndIfaceCacheCount);
+ size_t pageIndex = i / kPageSize;
+ size_t leafIndex = i % kPageSize;
+ Page* p = mPages[pageIndex];
+ if (!p) {
+ p = new Page;
+ mPages[pageIndex] = p;
+ }
+ return (*p)[leafIndex];
+ }
+
+ JS::Heap<JSObject*>& EntrySlotMustExist(size_t i) {
+ MOZ_ASSERT(i < kProtoAndIfaceCacheCount);
+ size_t pageIndex = i / kPageSize;
+ size_t leafIndex = i % kPageSize;
+ Page* p = mPages[pageIndex];
+ MOZ_ASSERT(p);
+ return (*p)[leafIndex];
+ }
+
+ void Trace(JSTracer* trc) {
+ for (size_t i = 0; i < ArrayLength(mPages); ++i) {
+ Page* p = mPages[i];
+ if (p) {
+ for (size_t j = 0; j < ArrayLength(*p); ++j) {
+ JS::TraceEdge(trc, &(*p)[j], "protoAndIfaceCache[i]");
+ }
+ }
+ }
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
+ size_t n = aMallocSizeOf(this);
+ for (size_t i = 0; i < ArrayLength(mPages); ++i) {
+ n += aMallocSizeOf(mPages[i]);
+ }
+ return n;
+ }
+
+ private:
+ static const size_t kPageSize = 16;
+ typedef Array<JS::Heap<JSObject*>, kPageSize> Page;
+ static const size_t kNPages =
+ kProtoAndIfaceCacheCount / kPageSize +
+ size_t(bool(kProtoAndIfaceCacheCount % kPageSize));
+ Array<Page*, kNPages> mPages;
+ };
+
+ public:
+ enum Kind { WindowLike, NonWindowLike };
+
+ explicit ProtoAndIfaceCache(Kind aKind) : mKind(aKind) {
+ MOZ_COUNT_CTOR(ProtoAndIfaceCache);
+ if (aKind == WindowLike) {
+ mArrayCache = new ArrayCache();
+ } else {
+ mPageTableCache = new PageTableCache();
+ }
+ }
+
+ ~ProtoAndIfaceCache() {
+ if (mKind == WindowLike) {
+ delete mArrayCache;
+ } else {
+ delete mPageTableCache;
+ }
+ MOZ_COUNT_DTOR(ProtoAndIfaceCache);
+ }
+
+#define FORWARD_OPERATION(opName, args) \
+ do { \
+ if (mKind == WindowLike) { \
+ return mArrayCache->opName args; \
+ } else { \
+ return mPageTableCache->opName args; \
+ } \
+ } while (0)
+
+ // Return whether slot i contains an object. This doesn't return the object
+ // itself because in practice consumers just want to know whether it's there
+ // or not, and that doesn't require barriering, which returning the object
+ // pointer does.
+ bool HasEntryInSlot(size_t i) { FORWARD_OPERATION(HasEntryInSlot, (i)); }
+
+ // Return a reference to slot i, creating it if necessary. There
+ // may not be an object in the returned slot.
+ JS::Heap<JSObject*>& EntrySlotOrCreate(size_t i) {
+ FORWARD_OPERATION(EntrySlotOrCreate, (i));
+ }
+
+ // Return a reference to slot i, which is guaranteed to already
+ // exist. There may not be an object in the slot, if prototype and
+ // constructor initialization for one of our bindings failed.
+ JS::Heap<JSObject*>& EntrySlotMustExist(size_t i) {
+ FORWARD_OPERATION(EntrySlotMustExist, (i));
+ }
+
+ void Trace(JSTracer* aTracer) { FORWARD_OPERATION(Trace, (aTracer)); }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
+ size_t n = aMallocSizeOf(this);
+ n += (mKind == WindowLike
+ ? mArrayCache->SizeOfIncludingThis(aMallocSizeOf)
+ : mPageTableCache->SizeOfIncludingThis(aMallocSizeOf));
+ return n;
+ }
+#undef FORWARD_OPERATION
+
+ private:
+ union {
+ ArrayCache* mArrayCache;
+ PageTableCache* mPageTableCache;
+ };
+ Kind mKind;
+};
+
+inline void AllocateProtoAndIfaceCache(JSObject* obj,
+ ProtoAndIfaceCache::Kind aKind) {
+ MOZ_ASSERT(JS::GetClass(obj)->flags & JSCLASS_DOM_GLOBAL);
+ MOZ_ASSERT(JS::GetReservedSlot(obj, DOM_PROTOTYPE_SLOT).isUndefined());
+
+ ProtoAndIfaceCache* protoAndIfaceCache = new ProtoAndIfaceCache(aKind);
+
+ JS::SetReservedSlot(obj, DOM_PROTOTYPE_SLOT,
+ JS::PrivateValue(protoAndIfaceCache));
+}
+
+#ifdef DEBUG
+struct VerifyTraceProtoAndIfaceCacheCalledTracer : public JS::CallbackTracer {
+ bool ok;
+
+ explicit VerifyTraceProtoAndIfaceCacheCalledTracer(JSContext* cx)
+ : JS::CallbackTracer(cx, JS::TracerKind::VerifyTraceProtoAndIface),
+ ok(false) {}
+
+ void onChild(JS::GCCellPtr, const char* name) override {
+ // We don't do anything here, we only want to verify that
+ // TraceProtoAndIfaceCache was called.
+ }
+};
+#endif
+
+inline void TraceProtoAndIfaceCache(JSTracer* trc, JSObject* obj) {
+ MOZ_ASSERT(JS::GetClass(obj)->flags & JSCLASS_DOM_GLOBAL);
+
+#ifdef DEBUG
+ if (trc->kind() == JS::TracerKind::VerifyTraceProtoAndIface) {
+ // We don't do anything here, we only want to verify that
+ // TraceProtoAndIfaceCache was called.
+ static_cast<VerifyTraceProtoAndIfaceCacheCalledTracer*>(trc)->ok = true;
+ return;
+ }
+#endif
+
+ if (!DOMGlobalHasProtoAndIFaceCache(obj)) return;
+ ProtoAndIfaceCache* protoAndIfaceCache = GetProtoAndIfaceCache(obj);
+ protoAndIfaceCache->Trace(trc);
+}
+
+inline void DestroyProtoAndIfaceCache(JSObject* obj) {
+ MOZ_ASSERT(JS::GetClass(obj)->flags & JSCLASS_DOM_GLOBAL);
+
+ if (!DOMGlobalHasProtoAndIFaceCache(obj)) {
+ return;
+ }
+
+ ProtoAndIfaceCache* protoAndIfaceCache = GetProtoAndIfaceCache(obj);
+
+ delete protoAndIfaceCache;
+}
+
+/**
+ * Add constants to an object.
+ */
+bool DefineConstants(JSContext* cx, JS::Handle<JSObject*> obj,
+ const ConstantSpec* cs);
+
+struct JSNativeHolder {
+ JSNative mNative;
+ const NativePropertyHooks* mPropertyHooks;
+};
+
+struct LegacyFactoryFunction {
+ const char* mName;
+ const JSNativeHolder mHolder;
+ unsigned mNargs;
+};
+
+// clang-format off
+/*
+ * Create a DOM interface object (if constructorClass is non-null) and/or a
+ * DOM interface prototype object (if protoClass is non-null).
+ *
+ * global is used as the parent of the interface object and the interface
+ * prototype object
+ * protoProto is the prototype to use for the interface prototype object.
+ * interfaceProto is the prototype to use for the interface object. This can be
+ * null if both constructorClass and constructor are null (as in,
+ * if we're not creating an interface object at all).
+ * protoClass is the JSClass to use for the interface prototype object.
+ * This is null if we should not create an interface prototype
+ * object.
+ * protoCache a pointer to a JSObject pointer where we should cache the
+ * interface prototype object. This must be null if protoClass is and
+ * vice versa.
+ * constructorClass is the JSClass to use for the interface object.
+ * This is null if we should not create an interface object or
+ * if it should be a function object.
+ * constructor holds the JSNative to back the interface object which should be a
+ * Function, unless constructorClass is non-null in which case it is
+ * ignored. If this is null and constructorClass is also null then
+ * we should not create an interface object at all.
+ * ctorNargs is the length of the constructor function; 0 if no constructor
+ * isConstructorChromeOnly if true, the constructor is ChromeOnly.
+ * constructorCache a pointer to a JSObject pointer where we should cache the
+ * interface object. This must be null if both constructorClass
+ * and constructor are null, and non-null otherwise.
+ * properties contains the methods, attributes and constants to be defined on
+ * objects in any compartment.
+ * chromeProperties contains the methods, attributes and constants to be defined
+ * on objects in chrome compartments. This must be null if the
+ * interface doesn't have any ChromeOnly properties or if the
+ * object is being created in non-chrome compartment.
+ * name the name to use for 1) the WebIDL class string, which is the value
+ * that's used for @@toStringTag, 2) the name property for interface
+ * objects and 3) the property on the global object that would be set to
+ * the interface object. In general this is the interface identifier.
+ * LegacyNamespace would expect something different for 1), but we don't
+ * support that. The class string for default iterator objects is not
+ * usable as 2) or 3), but default iterator objects don't have an interface
+ * object.
+ * defineOnGlobal controls whether properties should be defined on the given
+ * global for the interface object (if any) and named
+ * constructors (if any) for this interface. This can be
+ * false in situations where we want the properties to only
+ * appear on privileged Xrays but not on the unprivileged
+ * underlying global.
+ * unscopableNames if not null it points to a null-terminated list of const
+ * char* names of the unscopable properties for this interface.
+ * isGlobal if true, we're creating interface objects for a [Global] interface,
+ * and hence shouldn't define properties on the prototype object.
+ * legacyWindowAliases if not null it points to a null-terminated list of const
+ * char* names of the legacy window aliases for this
+ * interface.
+ *
+ * At least one of protoClass, constructorClass or constructor should be
+ * non-null. If constructorClass or constructor are non-null, the resulting
+ * interface object will be defined on the given global with property name
+ * |name|, which must also be non-null.
+ */
+// clang-format on
+void CreateInterfaceObjects(
+ JSContext* cx, JS::Handle<JSObject*> global,
+ JS::Handle<JSObject*> protoProto, const JSClass* protoClass,
+ JS::Heap<JSObject*>* protoCache, JS::Handle<JSObject*> constructorProto,
+ const JSClass* constructorClass, unsigned ctorNargs,
+ bool isConstructorChromeOnly,
+ const LegacyFactoryFunction* namedConstructors,
+ JS::Heap<JSObject*>* constructorCache, const NativeProperties* properties,
+ const NativeProperties* chromeOnlyProperties, const char* name,
+ bool defineOnGlobal, const char* const* unscopableNames, bool isGlobal,
+ const char* const* legacyWindowAliases, bool isNamespace);
+
+/**
+ * Define the properties (regular and chrome-only) on obj.
+ *
+ * obj the object to install the properties on. This should be the interface
+ * prototype object for regular interfaces and the instance object for
+ * interfaces marked with Global.
+ * properties contains the methods, attributes and constants to be defined on
+ * objects in any compartment.
+ * chromeProperties contains the methods, attributes and constants to be defined
+ * on objects in chrome compartments. This must be null if the
+ * interface doesn't have any ChromeOnly properties or if the
+ * object is being created in non-chrome compartment.
+ */
+bool DefineProperties(JSContext* cx, JS::Handle<JSObject*> obj,
+ const NativeProperties* properties,
+ const NativeProperties* chromeOnlyProperties);
+
+/*
+ * Define the legacy unforgeable methods on an object.
+ */
+bool DefineLegacyUnforgeableMethods(
+ JSContext* cx, JS::Handle<JSObject*> obj,
+ const Prefable<const JSFunctionSpec>* props);
+
+/*
+ * Define the legacy unforgeable attributes on an object.
+ */
+bool DefineLegacyUnforgeableAttributes(
+ JSContext* cx, JS::Handle<JSObject*> obj,
+ const Prefable<const JSPropertySpec>* props);
+
+#define HAS_MEMBER_TYPEDEFS \
+ private: \
+ typedef char yes[1]; \
+ typedef char no[2]
+
+#ifdef _MSC_VER
+# define HAS_MEMBER_CHECK(_name) \
+ template <typename V> \
+ static yes& Check##_name(char(*)[(&V::_name == 0) + 1])
+#else
+# define HAS_MEMBER_CHECK(_name) \
+ template <typename V> \
+ static yes& Check##_name(char(*)[sizeof(&V::_name) + 1])
+#endif
+
+#define HAS_MEMBER(_memberName, _valueName) \
+ private: \
+ HAS_MEMBER_CHECK(_memberName); \
+ template <typename V> \
+ static no& Check##_memberName(...); \
+ \
+ public: \
+ static bool const _valueName = \
+ sizeof(Check##_memberName<T>(nullptr)) == sizeof(yes)
+
+template <class T>
+struct NativeHasMember {
+ HAS_MEMBER_TYPEDEFS;
+
+ HAS_MEMBER(GetParentObject, GetParentObject);
+ HAS_MEMBER(WrapObject, WrapObject);
+};
+
+template <class T>
+struct IsSmartPtr {
+ HAS_MEMBER_TYPEDEFS;
+
+ HAS_MEMBER(get, value);
+};
+
+template <class T>
+struct IsRefcounted {
+ HAS_MEMBER_TYPEDEFS;
+
+ HAS_MEMBER(AddRef, HasAddref);
+ HAS_MEMBER(Release, HasRelease);
+
+ public:
+ static bool const value = HasAddref && HasRelease;
+
+ private:
+ // This struct only works if T is fully declared (not just forward declared).
+ // The std::is_base_of check will ensure that, we don't really need it for any
+ // other reason (the static assert will of course always be true).
+ static_assert(!std::is_base_of<nsISupports, T>::value || IsRefcounted::value,
+ "Classes derived from nsISupports are refcounted!");
+};
+
+#undef HAS_MEMBER
+#undef HAS_MEMBER_CHECK
+#undef HAS_MEMBER_TYPEDEFS
+
+#ifdef DEBUG
+template <class T, bool isISupports = std::is_base_of<nsISupports, T>::value>
+struct CheckWrapperCacheCast {
+ static bool Check() {
+ return reinterpret_cast<uintptr_t>(
+ static_cast<nsWrapperCache*>(reinterpret_cast<T*>(1))) == 1;
+ }
+};
+template <class T>
+struct CheckWrapperCacheCast<T, true> {
+ static bool Check() { return true; }
+};
+#endif
+
+inline bool TryToOuterize(JS::MutableHandle<JS::Value> rval) {
+#ifdef ENABLE_RECORD_TUPLE
+ if (rval.isExtendedPrimitive()) {
+ return true;
+ }
+#endif
+ MOZ_ASSERT(rval.isObject());
+ if (js::IsWindow(&rval.toObject())) {
+ JSObject* obj = js::ToWindowProxyIfWindow(&rval.toObject());
+ MOZ_ASSERT(obj);
+ rval.set(JS::ObjectValue(*obj));
+ }
+
+ return true;
+}
+
+inline bool TryToOuterize(JS::MutableHandle<JSObject*> obj) {
+ if (js::IsWindow(obj)) {
+ JSObject* proxy = js::ToWindowProxyIfWindow(obj);
+ MOZ_ASSERT(proxy);
+ obj.set(proxy);
+ }
+
+ return true;
+}
+
+// Make sure to wrap the given string value into the right compartment, as
+// needed.
+MOZ_ALWAYS_INLINE
+bool MaybeWrapStringValue(JSContext* cx, JS::MutableHandle<JS::Value> rval) {
+ MOZ_ASSERT(rval.isString());
+ JSString* str = rval.toString();
+ if (JS::GetStringZone(str) != js::GetContextZone(cx)) {
+ return JS_WrapValue(cx, rval);
+ }
+ return true;
+}
+
+// Make sure to wrap the given object value into the right compartment as
+// needed. This will work correctly, but possibly slowly, on all objects.
+MOZ_ALWAYS_INLINE
+bool MaybeWrapObjectValue(JSContext* cx, JS::MutableHandle<JS::Value> rval) {
+ MOZ_ASSERT(rval.hasObjectPayload());
+
+ // Cross-compartment always requires wrapping.
+ JSObject* obj = &rval.getObjectPayload();
+ if (JS::GetCompartment(obj) != js::GetContextCompartment(cx)) {
+ return JS_WrapValue(cx, rval);
+ }
+
+ // We're same-compartment, but we might still need to outerize if we
+ // have a Window.
+ return TryToOuterize(rval);
+}
+
+// Like MaybeWrapObjectValue, but working with a
+// JS::MutableHandle<JSObject*> which must be non-null.
+MOZ_ALWAYS_INLINE
+bool MaybeWrapObject(JSContext* cx, JS::MutableHandle<JSObject*> obj) {
+ if (JS::GetCompartment(obj) != js::GetContextCompartment(cx)) {
+ return JS_WrapObject(cx, obj);
+ }
+
+ // We're same-compartment, but we might still need to outerize if we
+ // have a Window.
+ return TryToOuterize(obj);
+}
+
+// Like MaybeWrapObjectValue, but also allows null
+MOZ_ALWAYS_INLINE
+bool MaybeWrapObjectOrNullValue(JSContext* cx,
+ JS::MutableHandle<JS::Value> rval) {
+ MOZ_ASSERT(rval.isObjectOrNull());
+ if (rval.isNull()) {
+ return true;
+ }
+ return MaybeWrapObjectValue(cx, rval);
+}
+
+// Wrapping for objects that are known to not be DOM objects
+MOZ_ALWAYS_INLINE
+bool MaybeWrapNonDOMObjectValue(JSContext* cx,
+ JS::MutableHandle<JS::Value> rval) {
+ MOZ_ASSERT(rval.isObject());
+ // Compared to MaybeWrapObjectValue we just skip the TryToOuterize call. The
+ // only reason it would be needed is if we have a Window object, which would
+ // have a DOM class. Assert that we don't have any DOM-class objects coming
+ // through here.
+ MOZ_ASSERT(!GetDOMClass(&rval.toObject()));
+
+ JSObject* obj = &rval.toObject();
+ if (JS::GetCompartment(obj) == js::GetContextCompartment(cx)) {
+ return true;
+ }
+ return JS_WrapValue(cx, rval);
+}
+
+// Like MaybeWrapNonDOMObjectValue but allows null
+MOZ_ALWAYS_INLINE
+bool MaybeWrapNonDOMObjectOrNullValue(JSContext* cx,
+ JS::MutableHandle<JS::Value> rval) {
+ MOZ_ASSERT(rval.isObjectOrNull());
+ if (rval.isNull()) {
+ return true;
+ }
+ return MaybeWrapNonDOMObjectValue(cx, rval);
+}
+
+// If rval is a gcthing and is not in the compartment of cx, wrap rval
+// into the compartment of cx (typically by replacing it with an Xray or
+// cross-compartment wrapper around the original object).
+MOZ_ALWAYS_INLINE bool MaybeWrapValue(JSContext* cx,
+ JS::MutableHandle<JS::Value> rval) {
+ if (rval.isGCThing()) {
+ if (rval.isString()) {
+ return MaybeWrapStringValue(cx, rval);
+ }
+ if (rval.hasObjectPayload()) {
+ return MaybeWrapObjectValue(cx, rval);
+ }
+ // This could be optimized by checking the zone first, similar to
+ // the way strings are handled. At present, this is used primarily
+ // for structured cloning, so avoiding the overhead of JS_WrapValue
+ // calls is less important than for other types.
+ if (rval.isBigInt()) {
+ return JS_WrapValue(cx, rval);
+ }
+ MOZ_ASSERT(rval.isSymbol());
+ JS_MarkCrossZoneId(cx, JS::PropertyKey::Symbol(rval.toSymbol()));
+ }
+ return true;
+}
+
+namespace binding_detail {
+enum GetOrCreateReflectorWrapBehavior {
+ eWrapIntoContextCompartment,
+ eDontWrapIntoContextCompartment
+};
+
+template <class T>
+struct TypeNeedsOuterization {
+ // We only need to outerize Window objects, so anything inheriting from
+ // nsGlobalWindow (which inherits from EventTarget itself).
+ static const bool value = std::is_base_of<nsGlobalWindowInner, T>::value ||
+ std::is_base_of<nsGlobalWindowOuter, T>::value ||
+ std::is_same_v<EventTarget, T>;
+};
+
+#ifdef DEBUG
+template <typename T, bool isISupports = std::is_base_of<nsISupports, T>::value>
+struct CheckWrapperCacheTracing {
+ static inline void Check(T* aObject) {}
+};
+
+template <typename T>
+struct CheckWrapperCacheTracing<T, true> {
+ static void Check(T* aObject) {
+ // Rooting analysis thinks QueryInterface may GC, but we're dealing with
+ // a subset of QueryInterface, C++ only types here.
+ JS::AutoSuppressGCAnalysis nogc;
+
+ nsWrapperCache* wrapperCacheFromQI = nullptr;
+ aObject->QueryInterface(NS_GET_IID(nsWrapperCache),
+ reinterpret_cast<void**>(&wrapperCacheFromQI));
+
+ MOZ_ASSERT(wrapperCacheFromQI,
+ "Missing nsWrapperCache from QueryInterface implementation?");
+
+ if (!wrapperCacheFromQI->GetWrapperPreserveColor()) {
+ // Can't assert that we trace the wrapper, since we don't have any
+ // wrapper to trace.
+ return;
+ }
+
+ nsISupports* ccISupports = nullptr;
+ aObject->QueryInterface(NS_GET_IID(nsCycleCollectionISupports),
+ reinterpret_cast<void**>(&ccISupports));
+ MOZ_ASSERT(ccISupports,
+ "nsWrapperCache object which isn't cycle collectable?");
+
+ nsXPCOMCycleCollectionParticipant* participant = nullptr;
+ CallQueryInterface(ccISupports, &participant);
+ MOZ_ASSERT(participant, "Can't QI to CycleCollectionParticipant?");
+
+ wrapperCacheFromQI->CheckCCWrapperTraversal(ccISupports, participant);
+ }
+};
+
+void AssertReflectorHasGivenProto(JSContext* aCx, JSObject* aReflector,
+ JS::Handle<JSObject*> aGivenProto);
+#endif // DEBUG
+
+template <class T, GetOrCreateReflectorWrapBehavior wrapBehavior>
+MOZ_ALWAYS_INLINE bool DoGetOrCreateDOMReflector(
+ JSContext* cx, T* value, JS::Handle<JSObject*> givenProto,
+ JS::MutableHandle<JS::Value> rval) {
+ MOZ_ASSERT(value);
+ MOZ_ASSERT_IF(givenProto, js::IsObjectInContextCompartment(givenProto, cx));
+ JSObject* obj = value->GetWrapper();
+ if (obj) {
+#ifdef DEBUG
+ AssertReflectorHasGivenProto(cx, obj, givenProto);
+ // Have to reget obj because AssertReflectorHasGivenProto can
+ // trigger gc so the pointer may now be invalid.
+ obj = value->GetWrapper();
+#endif
+ } else {
+ obj = value->WrapObject(cx, givenProto);
+ if (!obj) {
+ // At this point, obj is null, so just return false.
+ // Callers seem to be testing JS_IsExceptionPending(cx) to
+ // figure out whether WrapObject() threw.
+ return false;
+ }
+
+#ifdef DEBUG
+ if (std::is_base_of<nsWrapperCache, T>::value) {
+ CheckWrapperCacheTracing<T>::Check(value);
+ }
+#endif
+ }
+
+#ifdef DEBUG
+ const DOMJSClass* clasp = GetDOMClass(obj);
+ // clasp can be null if the cache contained a non-DOM object.
+ if (clasp) {
+ // Some sanity asserts about our object. Specifically:
+ // 1) If our class claims we're nsISupports, we better be nsISupports
+ // XXXbz ideally, we could assert that reinterpret_cast to nsISupports
+ // does the right thing, but I don't see a way to do it. :(
+ // 2) If our class doesn't claim we're nsISupports we better be
+ // reinterpret_castable to nsWrapperCache.
+ MOZ_ASSERT(clasp, "What happened here?");
+ MOZ_ASSERT_IF(clasp->mDOMObjectIsISupports,
+ (std::is_base_of<nsISupports, T>::value));
+ MOZ_ASSERT(CheckWrapperCacheCast<T>::Check());
+ }
+#endif
+
+#ifdef ENABLE_RECORD_TUPLE
+ MOZ_ASSERT(!js::gc::MaybeForwardedIsExtendedPrimitive(*obj));
+#endif
+ rval.set(JS::ObjectValue(*obj));
+
+ if (JS::GetCompartment(obj) == js::GetContextCompartment(cx)) {
+ return TypeNeedsOuterization<T>::value ? TryToOuterize(rval) : true;
+ }
+
+ if (wrapBehavior == eDontWrapIntoContextCompartment) {
+ if (TypeNeedsOuterization<T>::value) {
+ JSAutoRealm ar(cx, obj);
+ return TryToOuterize(rval);
+ }
+
+ return true;
+ }
+
+ return JS_WrapValue(cx, rval);
+}
+
+} // namespace binding_detail
+
+// Create a JSObject wrapping "value", if there isn't one already, and store it
+// in rval. "value" must be a concrete class that implements a
+// GetWrapperPreserveColor() which can return its existing wrapper, if any, and
+// a WrapObject() which will try to create a wrapper. Typically, this is done by
+// having "value" inherit from nsWrapperCache.
+//
+// The value stored in rval will be ready to be exposed to whatever JS
+// is running on cx right now. In particular, it will be in the
+// compartment of cx, and outerized as needed.
+template <class T>
+MOZ_ALWAYS_INLINE bool GetOrCreateDOMReflector(
+ JSContext* cx, T* value, JS::MutableHandle<JS::Value> rval,
+ JS::Handle<JSObject*> givenProto = nullptr) {
+ using namespace binding_detail;
+ return DoGetOrCreateDOMReflector<T, eWrapIntoContextCompartment>(
+ cx, value, givenProto, rval);
+}
+
+// Like GetOrCreateDOMReflector but doesn't wrap into the context compartment,
+// and hence does not actually require cx to be in a compartment.
+template <class T>
+MOZ_ALWAYS_INLINE bool GetOrCreateDOMReflectorNoWrap(
+ JSContext* cx, T* value, JS::MutableHandle<JS::Value> rval) {
+ using namespace binding_detail;
+ return DoGetOrCreateDOMReflector<T, eDontWrapIntoContextCompartment>(
+ cx, value, nullptr, rval);
+}
+
+// Helper for different overloadings of WrapNewBindingNonWrapperCachedObject()
+inline bool FinishWrapping(JSContext* cx, JS::Handle<JSObject*> obj,
+ JS::MutableHandle<JS::Value> rval) {
+#ifdef ENABLE_RECORD_TUPLE
+ // If calling an (object) value's WrapObject() method returned a record/tuple,
+ // then something is very wrong.
+ MOZ_ASSERT(!js::gc::MaybeForwardedIsExtendedPrimitive(*obj));
+#endif
+
+ // We can end up here in all sorts of compartments, per comments in
+ // WrapNewBindingNonWrapperCachedObject(). Make sure to JS_WrapValue!
+ rval.set(JS::ObjectValue(*obj));
+ return MaybeWrapObjectValue(cx, rval);
+}
+
+// Create a JSObject wrapping "value", for cases when "value" is a
+// non-wrapper-cached object using WebIDL bindings. "value" must implement a
+// WrapObject() method taking a JSContext and a prototype (possibly null) and
+// returning the resulting object via a MutableHandle<JSObject*> outparam.
+template <class T>
+inline bool WrapNewBindingNonWrapperCachedObject(
+ JSContext* cx, JS::Handle<JSObject*> scopeArg, T* value,
+ JS::MutableHandle<JS::Value> rval,
+ JS::Handle<JSObject*> givenProto = nullptr) {
+ static_assert(IsRefcounted<T>::value, "Don't pass owned classes in here.");
+ MOZ_ASSERT(value);
+ // We try to wrap in the realm of the underlying object of "scope"
+ JS::Rooted<JSObject*> obj(cx);
+ {
+ // scope for the JSAutoRealm so that we restore the realm
+ // before we call JS_WrapValue.
+ Maybe<JSAutoRealm> ar;
+ // Maybe<Handle> doesn't so much work, and in any case, adding
+ // more Maybe (one for a Rooted and one for a Handle) adds more
+ // code (and branches!) than just adding a single rooted.
+ JS::Rooted<JSObject*> scope(cx, scopeArg);
+ JS::Rooted<JSObject*> proto(cx, givenProto);
+ if (js::IsWrapper(scope)) {
+ // We are working in the Realm of cx and will be producing our reflector
+ // there, so we need to succeed if that realm has access to the scope.
+ scope =
+ js::CheckedUnwrapDynamic(scope, cx, /* stopAtWindowProxy = */ false);
+ if (!scope) return false;
+ ar.emplace(cx, scope);
+ if (!JS_WrapObject(cx, &proto)) {
+ return false;
+ }
+ } else {
+ // cx and scope are same-compartment, but they might still be
+ // different-Realm. Enter the Realm of scope, since that's
+ // where we want to create our object.
+ ar.emplace(cx, scope);
+ }
+
+ MOZ_ASSERT_IF(proto, js::IsObjectInContextCompartment(proto, cx));
+ MOZ_ASSERT(js::IsObjectInContextCompartment(scope, cx));
+ if (!value->WrapObject(cx, proto, &obj)) {
+ return false;
+ }
+ }
+
+ return FinishWrapping(cx, obj, rval);
+}
+
+// Create a JSObject wrapping "value", for cases when "value" is a
+// non-wrapper-cached owned object using WebIDL bindings. "value" must
+// implement a WrapObject() method taking a taking a JSContext and a prototype
+// (possibly null) and returning two pieces of information: the resulting object
+// via a MutableHandle<JSObject*> outparam and a boolean return value that is
+// true if the JSObject took ownership
+template <class T>
+inline bool WrapNewBindingNonWrapperCachedObject(
+ JSContext* cx, JS::Handle<JSObject*> scopeArg, UniquePtr<T>& value,
+ JS::MutableHandle<JS::Value> rval,
+ JS::Handle<JSObject*> givenProto = nullptr) {
+ static_assert(!IsRefcounted<T>::value, "Only pass owned classes in here.");
+ // We do a runtime check on value, because otherwise we might in
+ // fact end up wrapping a null and invoking methods on it later.
+ if (!value) {
+ MOZ_CRASH("Don't try to wrap null objects");
+ }
+ // We try to wrap in the realm of the underlying object of "scope"
+ JS::Rooted<JSObject*> obj(cx);
+ {
+ // scope for the JSAutoRealm so that we restore the realm
+ // before we call JS_WrapValue.
+ Maybe<JSAutoRealm> ar;
+ // Maybe<Handle> doesn't so much work, and in any case, adding
+ // more Maybe (one for a Rooted and one for a Handle) adds more
+ // code (and branches!) than just adding a single rooted.
+ JS::Rooted<JSObject*> scope(cx, scopeArg);
+ JS::Rooted<JSObject*> proto(cx, givenProto);
+ if (js::IsWrapper(scope)) {
+ // We are working in the Realm of cx and will be producing our reflector
+ // there, so we need to succeed if that realm has access to the scope.
+ scope =
+ js::CheckedUnwrapDynamic(scope, cx, /* stopAtWindowProxy = */ false);
+ if (!scope) return false;
+ ar.emplace(cx, scope);
+ if (!JS_WrapObject(cx, &proto)) {
+ return false;
+ }
+ } else {
+ // cx and scope are same-compartment, but they might still be
+ // different-Realm. Enter the Realm of scope, since that's
+ // where we want to create our object.
+ ar.emplace(cx, scope);
+ }
+
+ MOZ_ASSERT_IF(proto, js::IsObjectInContextCompartment(proto, cx));
+ MOZ_ASSERT(js::IsObjectInContextCompartment(scope, cx));
+ if (!value->WrapObject(cx, proto, &obj)) {
+ return false;
+ }
+
+ // JS object took ownership
+ Unused << value.release();
+ }
+
+ return FinishWrapping(cx, obj, rval);
+}
+
+// Helper for smart pointers (nsRefPtr/nsCOMPtr).
+template <template <typename> class SmartPtr, typename T,
+ typename U = std::enable_if_t<IsRefcounted<T>::value, T>,
+ typename V = std::enable_if_t<IsSmartPtr<SmartPtr<T>>::value, T>>
+inline bool WrapNewBindingNonWrapperCachedObject(
+ JSContext* cx, JS::Handle<JSObject*> scope, const SmartPtr<T>& value,
+ JS::MutableHandle<JS::Value> rval,
+ JS::Handle<JSObject*> givenProto = nullptr) {
+ return WrapNewBindingNonWrapperCachedObject(cx, scope, value.get(), rval,
+ givenProto);
+}
+
+// Helper for object references (as opposed to pointers).
+template <typename T, typename U = std::enable_if_t<!IsSmartPtr<T>::value, T>>
+inline bool WrapNewBindingNonWrapperCachedObject(
+ JSContext* cx, JS::Handle<JSObject*> scope, T& value,
+ JS::MutableHandle<JS::Value> rval,
+ JS::Handle<JSObject*> givenProto = nullptr) {
+ return WrapNewBindingNonWrapperCachedObject(cx, scope, &value, rval,
+ givenProto);
+}
+
+template <bool Fatal>
+inline bool EnumValueNotFound(BindingCallContext& cx, JS::Handle<JSString*> str,
+ const char* type, const char* sourceDescription);
+
+template <>
+inline bool EnumValueNotFound<false>(BindingCallContext& cx,
+ JS::Handle<JSString*> str,
+ const char* type,
+ const char* sourceDescription) {
+ // TODO: Log a warning to the console.
+ return true;
+}
+
+template <>
+inline bool EnumValueNotFound<true>(BindingCallContext& cx,
+ JS::Handle<JSString*> str, const char* type,
+ const char* sourceDescription) {
+ JS::UniqueChars deflated = JS_EncodeStringToUTF8(cx, str);
+ if (!deflated) {
+ return false;
+ }
+ return cx.ThrowErrorMessage<MSG_INVALID_ENUM_VALUE>(sourceDescription,
+ deflated.get(), type);
+}
+
+template <typename CharT>
+inline int FindEnumStringIndexImpl(const CharT* chars, size_t length,
+ const EnumEntry* values) {
+ int i = 0;
+ for (const EnumEntry* value = values; value->value; ++value, ++i) {
+ if (length != value->length) {
+ continue;
+ }
+
+ bool equal = true;
+ const char* val = value->value;
+ for (size_t j = 0; j != length; ++j) {
+ if (unsigned(val[j]) != unsigned(chars[j])) {
+ equal = false;
+ break;
+ }
+ }
+
+ if (equal) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+template <bool InvalidValueFatal>
+inline bool FindEnumStringIndex(BindingCallContext& cx, JS::Handle<JS::Value> v,
+ const EnumEntry* values, const char* type,
+ const char* sourceDescription, int* index) {
+ // JS_StringEqualsAscii is slow as molasses, so don't use it here.
+ JS::Rooted<JSString*> str(cx, JS::ToString(cx, v));
+ if (!str) {
+ return false;
+ }
+
+ {
+ size_t length;
+ JS::AutoCheckCannotGC nogc;
+ if (JS::StringHasLatin1Chars(str)) {
+ const JS::Latin1Char* chars =
+ JS_GetLatin1StringCharsAndLength(cx, nogc, str, &length);
+ if (!chars) {
+ return false;
+ }
+ *index = FindEnumStringIndexImpl(chars, length, values);
+ } else {
+ const char16_t* chars =
+ JS_GetTwoByteStringCharsAndLength(cx, nogc, str, &length);
+ if (!chars) {
+ return false;
+ }
+ *index = FindEnumStringIndexImpl(chars, length, values);
+ }
+ if (*index >= 0) {
+ return true;
+ }
+ }
+
+ return EnumValueNotFound<InvalidValueFatal>(cx, str, type, sourceDescription);
+}
+
+inline nsWrapperCache* GetWrapperCache(const ParentObject& aParentObject) {
+ return aParentObject.mWrapperCache;
+}
+
+template <class T>
+inline T* GetParentPointer(T* aObject) {
+ return aObject;
+}
+
+inline nsISupports* GetParentPointer(const ParentObject& aObject) {
+ return aObject.mObject;
+}
+
+template <typename T>
+inline mozilla::dom::ReflectionScope GetReflectionScope(T* aParentObject) {
+ return mozilla::dom::ReflectionScope::Content;
+}
+
+inline mozilla::dom::ReflectionScope GetReflectionScope(
+ const ParentObject& aParentObject) {
+ return aParentObject.mReflectionScope;
+}
+
+template <class T>
+inline void ClearWrapper(T* p, nsWrapperCache* cache, JSObject* obj) {
+ MOZ_ASSERT(cache->GetWrapperMaybeDead() == obj ||
+ (js::RuntimeIsBeingDestroyed() && !cache->GetWrapperMaybeDead()));
+ cache->ClearWrapper(obj);
+}
+
+template <class T>
+inline void ClearWrapper(T* p, void*, JSObject* obj) {
+ // QueryInterface to nsWrapperCache can't GC, we hope.
+ JS::AutoSuppressGCAnalysis nogc;
+
+ nsWrapperCache* cache;
+ CallQueryInterface(p, &cache);
+ ClearWrapper(p, cache, obj);
+}
+
+template <class T>
+inline void UpdateWrapper(T* p, nsWrapperCache* cache, JSObject* obj,
+ const JSObject* old) {
+ JS::AutoAssertGCCallback inCallback;
+ cache->UpdateWrapper(obj, old);
+}
+
+template <class T>
+inline void UpdateWrapper(T* p, void*, JSObject* obj, const JSObject* old) {
+ JS::AutoAssertGCCallback inCallback;
+ nsWrapperCache* cache;
+ CallQueryInterface(p, &cache);
+ UpdateWrapper(p, cache, obj, old);
+}
+
+// Attempt to preserve the wrapper, if any, for a Paris DOM bindings object.
+// Return true if we successfully preserved the wrapper, or there is no wrapper
+// to preserve. In the latter case we don't need to preserve the wrapper,
+// because the object can only be obtained by JS once, or they cannot be
+// meaningfully owned from the native side.
+//
+// This operation will return false only for non-nsISupports cycle-collected
+// objects, because we cannot determine if they are wrappercached or not.
+bool TryPreserveWrapper(JS::Handle<JSObject*> obj);
+
+bool HasReleasedWrapper(JS::Handle<JSObject*> obj);
+
+// Can only be called with a DOM JSClass.
+bool InstanceClassHasProtoAtDepth(const JSClass* clasp, uint32_t protoID,
+ uint32_t depth);
+
+// Only set allowNativeWrapper to false if you really know you need it; if in
+// doubt use true. Setting it to false disables security wrappers.
+bool XPCOMObjectToJsval(JSContext* cx, JS::Handle<JSObject*> scope,
+ xpcObjectHelper& helper, const nsIID* iid,
+ bool allowNativeWrapper,
+ JS::MutableHandle<JS::Value> rval);
+
+// Special-cased wrapping for variants
+bool VariantToJsval(JSContext* aCx, nsIVariant* aVariant,
+ JS::MutableHandle<JS::Value> aRetval);
+
+// Wrap an object "p" which is not using WebIDL bindings yet. This _will_
+// actually work on WebIDL binding objects that are wrappercached, but will be
+// much slower than GetOrCreateDOMReflector. "cache" must either be null or be
+// the nsWrapperCache for "p".
+template <class T>
+inline bool WrapObject(JSContext* cx, T* p, nsWrapperCache* cache,
+ const nsIID* iid, JS::MutableHandle<JS::Value> rval) {
+ if (xpc_FastGetCachedWrapper(cx, cache, rval)) return true;
+ xpcObjectHelper helper(ToSupports(p), cache);
+ JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx));
+ return XPCOMObjectToJsval(cx, scope, helper, iid, true, rval);
+}
+
+// A specialization of the above for nsIVariant, because that needs to
+// do something different.
+template <>
+inline bool WrapObject<nsIVariant>(JSContext* cx, nsIVariant* p,
+ nsWrapperCache* cache, const nsIID* iid,
+ JS::MutableHandle<JS::Value> rval) {
+ MOZ_ASSERT(iid);
+ MOZ_ASSERT(iid->Equals(NS_GET_IID(nsIVariant)));
+ return VariantToJsval(cx, p, rval);
+}
+
+// Wrap an object "p" which is not using WebIDL bindings yet. Just like the
+// variant that takes an nsWrapperCache above, but will try to auto-derive the
+// nsWrapperCache* from "p".
+template <class T>
+inline bool WrapObject(JSContext* cx, T* p, const nsIID* iid,
+ JS::MutableHandle<JS::Value> rval) {
+ return WrapObject(cx, p, GetWrapperCache(p), iid, rval);
+}
+
+// Just like the WrapObject above, but without requiring you to pick which
+// interface you're wrapping as. This should only be used for objects that have
+// classinfo, for which it doesn't matter what IID is used to wrap.
+template <class T>
+inline bool WrapObject(JSContext* cx, T* p, JS::MutableHandle<JS::Value> rval) {
+ return WrapObject(cx, p, nullptr, rval);
+}
+
+// Helper to make it possible to wrap directly out of an nsCOMPtr
+template <class T>
+inline bool WrapObject(JSContext* cx, const nsCOMPtr<T>& p, const nsIID* iid,
+ JS::MutableHandle<JS::Value> rval) {
+ return WrapObject(cx, p.get(), iid, rval);
+}
+
+// Helper to make it possible to wrap directly out of an nsCOMPtr
+template <class T>
+inline bool WrapObject(JSContext* cx, const nsCOMPtr<T>& p,
+ JS::MutableHandle<JS::Value> rval) {
+ return WrapObject(cx, p, nullptr, rval);
+}
+
+// Helper to make it possible to wrap directly out of an nsRefPtr
+template <class T>
+inline bool WrapObject(JSContext* cx, const RefPtr<T>& p, const nsIID* iid,
+ JS::MutableHandle<JS::Value> rval) {
+ return WrapObject(cx, p.get(), iid, rval);
+}
+
+// Helper to make it possible to wrap directly out of an nsRefPtr
+template <class T>
+inline bool WrapObject(JSContext* cx, const RefPtr<T>& p,
+ JS::MutableHandle<JS::Value> rval) {
+ return WrapObject(cx, p, nullptr, rval);
+}
+
+// Specialization to make it easy to use WrapObject in codegen.
+template <>
+inline bool WrapObject<JSObject>(JSContext* cx, JSObject* p,
+ JS::MutableHandle<JS::Value> rval) {
+ rval.set(JS::ObjectOrNullValue(p));
+ return true;
+}
+
+inline bool WrapObject(JSContext* cx, JSObject& p,
+ JS::MutableHandle<JS::Value> rval) {
+ rval.set(JS::ObjectValue(p));
+ return true;
+}
+
+bool WrapObject(JSContext* cx, const WindowProxyHolder& p,
+ JS::MutableHandle<JS::Value> rval);
+
+// Given an object "p" that inherits from nsISupports, wrap it and return the
+// result. Null is returned on wrapping failure. This is somewhat similar to
+// WrapObject() above, but does NOT allow Xrays around the result, since we
+// don't want those for our parent object.
+template <typename T>
+static inline JSObject* WrapNativeISupports(JSContext* cx, T* p,
+ nsWrapperCache* cache) {
+ JS::Rooted<JSObject*> retval(cx);
+ {
+ xpcObjectHelper helper(ToSupports(p), cache);
+ JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx));
+ JS::Rooted<JS::Value> v(cx);
+ retval = XPCOMObjectToJsval(cx, scope, helper, nullptr, false, &v)
+ ? v.toObjectOrNull()
+ : nullptr;
+ }
+ return retval;
+}
+
+// Wrapping of our native parent, for cases when it's a WebIDL object.
+template <typename T, bool hasWrapObject = NativeHasMember<T>::WrapObject>
+struct WrapNativeHelper {
+ static inline JSObject* Wrap(JSContext* cx, T* parent,
+ nsWrapperCache* cache) {
+ MOZ_ASSERT(cache);
+
+ JSObject* obj;
+ if ((obj = cache->GetWrapper())) {
+ // GetWrapper always unmarks gray.
+ JS::AssertObjectIsNotGray(obj);
+ return obj;
+ }
+
+ // WrapObject never returns a gray thing.
+ obj = parent->WrapObject(cx, nullptr);
+ JS::AssertObjectIsNotGray(obj);
+
+ return obj;
+ }
+};
+
+// Wrapping of our native parent, for cases when it's not a WebIDL object. In
+// this case it must be nsISupports.
+template <typename T>
+struct WrapNativeHelper<T, false> {
+ static inline JSObject* Wrap(JSContext* cx, T* parent,
+ nsWrapperCache* cache) {
+ JSObject* obj;
+ if (cache && (obj = cache->GetWrapper())) {
+#ifdef DEBUG
+ JS::Rooted<JSObject*> rootedObj(cx, obj);
+ NS_ASSERTION(WrapNativeISupports(cx, parent, cache) == rootedObj,
+ "Unexpected object in nsWrapperCache");
+ obj = rootedObj;
+#endif
+ JS::AssertObjectIsNotGray(obj);
+ return obj;
+ }
+
+ obj = WrapNativeISupports(cx, parent, cache);
+ JS::AssertObjectIsNotGray(obj);
+ return obj;
+ }
+};
+
+// Finding the associated global for an object.
+template <typename T>
+static inline JSObject* FindAssociatedGlobal(
+ JSContext* cx, T* p, nsWrapperCache* cache,
+ mozilla::dom::ReflectionScope scope =
+ mozilla::dom::ReflectionScope::Content) {
+ if (!p) {
+ return JS::CurrentGlobalOrNull(cx);
+ }
+
+ JSObject* obj = WrapNativeHelper<T>::Wrap(cx, p, cache);
+ if (!obj) {
+ return nullptr;
+ }
+ JS::AssertObjectIsNotGray(obj);
+
+ // The object is never a CCW but it may not be in the current compartment of
+ // the JSContext.
+ obj = JS::GetNonCCWObjectGlobal(obj);
+
+ switch (scope) {
+ case mozilla::dom::ReflectionScope::NAC: {
+ return xpc::NACScope(obj);
+ }
+
+ case mozilla::dom::ReflectionScope::UAWidget: {
+ // If scope is set to UAWidgetScope, it means that the canonical reflector
+ // for this native object should live in the UA widget scope.
+ if (xpc::IsInUAWidgetScope(obj)) {
+ return obj;
+ }
+ JS::Rooted<JSObject*> rootedObj(cx, obj);
+ JSObject* uaWidgetScope = xpc::GetUAWidgetScope(cx, rootedObj);
+ MOZ_ASSERT_IF(uaWidgetScope, JS_IsGlobalObject(uaWidgetScope));
+ JS::AssertObjectIsNotGray(uaWidgetScope);
+ return uaWidgetScope;
+ }
+
+ case ReflectionScope::Content:
+ return obj;
+ }
+
+ MOZ_CRASH("Unknown ReflectionScope variant");
+
+ return nullptr;
+}
+
+// Finding of the associated global for an object, when we don't want to
+// explicitly pass in things like the nsWrapperCache for it.
+template <typename T>
+static inline JSObject* FindAssociatedGlobal(JSContext* cx, const T& p) {
+ return FindAssociatedGlobal(cx, GetParentPointer(p), GetWrapperCache(p),
+ GetReflectionScope(p));
+}
+
+// Specialization for the case of nsIGlobalObject, since in that case
+// we can just get the JSObject* directly.
+template <>
+inline JSObject* FindAssociatedGlobal(JSContext* cx,
+ nsIGlobalObject* const& p) {
+ if (!p) {
+ return JS::CurrentGlobalOrNull(cx);
+ }
+
+ JSObject* global = p->GetGlobalJSObject();
+ if (!global) {
+ // nsIGlobalObject doesn't have a JS object anymore,
+ // fallback to the current global.
+ return JS::CurrentGlobalOrNull(cx);
+ }
+
+ MOZ_ASSERT(JS_IsGlobalObject(global));
+ JS::AssertObjectIsNotGray(global);
+ return global;
+}
+
+template <typename T,
+ bool hasAssociatedGlobal = NativeHasMember<T>::GetParentObject>
+struct FindAssociatedGlobalForNative {
+ static JSObject* Get(JSContext* cx, JS::Handle<JSObject*> obj) {
+ MOZ_ASSERT(js::IsObjectInContextCompartment(obj, cx));
+ T* native = UnwrapDOMObject<T>(obj);
+ return FindAssociatedGlobal(cx, native->GetParentObject());
+ }
+};
+
+template <typename T>
+struct FindAssociatedGlobalForNative<T, false> {
+ static JSObject* Get(JSContext* cx, JS::Handle<JSObject*> obj) {
+ MOZ_CRASH();
+ return nullptr;
+ }
+};
+
+// Helper for calling GetOrCreateDOMReflector with smart pointers
+// (UniquePtr/RefPtr/nsCOMPtr) or references.
+template <class T, bool isSmartPtr = IsSmartPtr<T>::value>
+struct GetOrCreateDOMReflectorHelper {
+ static inline bool GetOrCreate(JSContext* cx, const T& value,
+ JS::Handle<JSObject*> givenProto,
+ JS::MutableHandle<JS::Value> rval) {
+ return GetOrCreateDOMReflector(cx, value.get(), rval, givenProto);
+ }
+};
+
+template <class T>
+struct GetOrCreateDOMReflectorHelper<T, false> {
+ static inline bool GetOrCreate(JSContext* cx, T& value,
+ JS::Handle<JSObject*> givenProto,
+ JS::MutableHandle<JS::Value> rval) {
+ static_assert(IsRefcounted<T>::value, "Don't pass owned classes in here.");
+ return GetOrCreateDOMReflector(cx, &value, rval, givenProto);
+ }
+};
+
+template <class T>
+inline bool GetOrCreateDOMReflector(
+ JSContext* cx, T& value, JS::MutableHandle<JS::Value> rval,
+ JS::Handle<JSObject*> givenProto = nullptr) {
+ return GetOrCreateDOMReflectorHelper<T>::GetOrCreate(cx, value, givenProto,
+ rval);
+}
+
+// Helper for calling GetOrCreateDOMReflectorNoWrap with smart pointers
+// (UniquePtr/RefPtr/nsCOMPtr) or references.
+template <class T, bool isSmartPtr = IsSmartPtr<T>::value>
+struct GetOrCreateDOMReflectorNoWrapHelper {
+ static inline bool GetOrCreate(JSContext* cx, const T& value,
+ JS::MutableHandle<JS::Value> rval) {
+ return GetOrCreateDOMReflectorNoWrap(cx, value.get(), rval);
+ }
+};
+
+template <class T>
+struct GetOrCreateDOMReflectorNoWrapHelper<T, false> {
+ static inline bool GetOrCreate(JSContext* cx, T& value,
+ JS::MutableHandle<JS::Value> rval) {
+ return GetOrCreateDOMReflectorNoWrap(cx, &value, rval);
+ }
+};
+
+template <class T>
+inline bool GetOrCreateDOMReflectorNoWrap(JSContext* cx, T& value,
+ JS::MutableHandle<JS::Value> rval) {
+ return GetOrCreateDOMReflectorNoWrapHelper<T>::GetOrCreate(cx, value, rval);
+}
+
+template <class T>
+inline JSObject* GetCallbackFromCallbackObject(JSContext* aCx, T* aObj) {
+ return aObj->Callback(aCx);
+}
+
+// Helper for getting the callback JSObject* of a smart ptr around a
+// CallbackObject or a reference to a CallbackObject or something like
+// that.
+template <class T, bool isSmartPtr = IsSmartPtr<T>::value>
+struct GetCallbackFromCallbackObjectHelper {
+ static inline JSObject* Get(JSContext* aCx, const T& aObj) {
+ return GetCallbackFromCallbackObject(aCx, aObj.get());
+ }
+};
+
+template <class T>
+struct GetCallbackFromCallbackObjectHelper<T, false> {
+ static inline JSObject* Get(JSContext* aCx, T& aObj) {
+ return GetCallbackFromCallbackObject(aCx, &aObj);
+ }
+};
+
+template <class T>
+inline JSObject* GetCallbackFromCallbackObject(JSContext* aCx, T& aObj) {
+ return GetCallbackFromCallbackObjectHelper<T>::Get(aCx, aObj);
+}
+
+static inline bool AtomizeAndPinJSString(JSContext* cx, jsid& id,
+ const char* chars) {
+ if (JSString* str = ::JS_AtomizeAndPinString(cx, chars)) {
+ id = JS::PropertyKey::fromPinnedString(str);
+ return true;
+ }
+ return false;
+}
+
+void GetInterfaceImpl(JSContext* aCx, nsIInterfaceRequestor* aRequestor,
+ nsWrapperCache* aCache, JS::Handle<JS::Value> aIID,
+ JS::MutableHandle<JS::Value> aRetval,
+ ErrorResult& aError);
+
+template <class T>
+void GetInterface(JSContext* aCx, T* aThis, JS::Handle<JS::Value> aIID,
+ JS::MutableHandle<JS::Value> aRetval, ErrorResult& aError) {
+ GetInterfaceImpl(aCx, aThis, aThis, aIID, aRetval, aError);
+}
+
+bool ThrowingConstructor(JSContext* cx, unsigned argc, JS::Value* vp);
+
+bool ThrowConstructorWithoutNew(JSContext* cx, const char* name);
+
+// Helper for throwing an "invalid this" exception.
+bool ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs,
+ bool aSecurityError, prototypes::ID aProtoId);
+
+bool GetPropertyOnPrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<JS::Value> receiver, JS::Handle<jsid> id,
+ bool* found, JS::MutableHandle<JS::Value> vp);
+
+//
+bool HasPropertyOnPrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id, bool* has);
+
+// Append the property names in "names" to "props". If
+// shadowPrototypeProperties is false then skip properties that are also
+// present on the proto chain of proxy. If shadowPrototypeProperties is true,
+// then the "proxy" argument is ignored.
+bool AppendNamedPropertyIds(JSContext* cx, JS::Handle<JSObject*> proxy,
+ nsTArray<nsString>& names,
+ bool shadowPrototypeProperties,
+ JS::MutableHandleVector<jsid> props);
+
+enum StringificationBehavior { eStringify, eEmpty, eNull };
+
+static inline JSString* ConvertJSValueToJSString(JSContext* cx,
+ JS::Handle<JS::Value> v) {
+ if (MOZ_LIKELY(v.isString())) {
+ return v.toString();
+ }
+ return JS::ToString(cx, v);
+}
+
+template <typename T>
+static inline bool ConvertJSValueToString(
+ JSContext* cx, JS::Handle<JS::Value> v,
+ StringificationBehavior nullBehavior,
+ StringificationBehavior undefinedBehavior, T& result) {
+ JSString* s;
+ if (v.isString()) {
+ s = v.toString();
+ } else {
+ StringificationBehavior behavior;
+ if (v.isNull()) {
+ behavior = nullBehavior;
+ } else if (v.isUndefined()) {
+ behavior = undefinedBehavior;
+ } else {
+ behavior = eStringify;
+ }
+
+ if (behavior != eStringify) {
+ if (behavior == eEmpty) {
+ result.Truncate();
+ } else {
+ result.SetIsVoid(true);
+ }
+ return true;
+ }
+
+ s = JS::ToString(cx, v);
+ if (!s) {
+ return false;
+ }
+ }
+
+ return AssignJSString(cx, result, s);
+}
+
+template <typename T>
+static inline bool ConvertJSValueToString(
+ JSContext* cx, JS::Handle<JS::Value> v,
+ const char* /* unused sourceDescription */, T& result) {
+ return ConvertJSValueToString(cx, v, eStringify, eStringify, result);
+}
+
+[[nodiscard]] bool NormalizeUSVString(nsAString& aString);
+
+[[nodiscard]] bool NormalizeUSVString(
+ binding_detail::FakeString<char16_t>& aString);
+
+template <typename T>
+static inline bool ConvertJSValueToUSVString(
+ JSContext* cx, JS::Handle<JS::Value> v,
+ const char* /* unused sourceDescription */, T& result) {
+ if (!ConvertJSValueToString(cx, v, eStringify, eStringify, result)) {
+ return false;
+ }
+
+ if (!NormalizeUSVString(result)) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+
+ return true;
+}
+
+template <typename T>
+inline bool ConvertIdToString(JSContext* cx, JS::Handle<JS::PropertyKey> id,
+ T& result, bool& isSymbol) {
+ if (MOZ_LIKELY(id.isString())) {
+ if (!AssignJSString(cx, result, id.toString())) {
+ return false;
+ }
+ } else if (id.isSymbol()) {
+ isSymbol = true;
+ return true;
+ } else {
+ JS::Rooted<JS::Value> nameVal(cx, js::IdToValue(id));
+ if (!ConvertJSValueToString(cx, nameVal, eStringify, eStringify, result)) {
+ return false;
+ }
+ }
+ isSymbol = false;
+ return true;
+}
+
+bool ConvertJSValueToByteString(BindingCallContext& cx, JS::Handle<JS::Value> v,
+ bool nullable, const char* sourceDescription,
+ nsACString& result);
+
+inline bool ConvertJSValueToByteString(BindingCallContext& cx,
+ JS::Handle<JS::Value> v,
+ const char* sourceDescription,
+ nsACString& result) {
+ return ConvertJSValueToByteString(cx, v, false, sourceDescription, result);
+}
+
+template <typename T>
+void DoTraceSequence(JSTracer* trc, FallibleTArray<T>& seq);
+template <typename T>
+void DoTraceSequence(JSTracer* trc, nsTArray<T>& seq);
+
+// Class used to trace sequences, with specializations for various
+// sequence types.
+template <typename T,
+ bool isDictionary = std::is_base_of<DictionaryBase, T>::value,
+ bool isTypedArray = std::is_base_of<AllTypedArraysBase, T>::value,
+ bool isOwningUnion = std::is_base_of<AllOwningUnionBase, T>::value>
+class SequenceTracer {
+ explicit SequenceTracer() = delete; // Should never be instantiated
+};
+
+// sequence<object> or sequence<object?>
+template <>
+class SequenceTracer<JSObject*, false, false, false> {
+ explicit SequenceTracer() = delete; // Should never be instantiated
+
+ public:
+ static void TraceSequence(JSTracer* trc, JSObject** objp, JSObject** end) {
+ for (; objp != end; ++objp) {
+ JS::TraceRoot(trc, objp, "sequence<object>");
+ }
+ }
+};
+
+// sequence<any>
+template <>
+class SequenceTracer<JS::Value, false, false, false> {
+ explicit SequenceTracer() = delete; // Should never be instantiated
+
+ public:
+ static void TraceSequence(JSTracer* trc, JS::Value* valp, JS::Value* end) {
+ for (; valp != end; ++valp) {
+ JS::TraceRoot(trc, valp, "sequence<any>");
+ }
+ }
+};
+
+// sequence<sequence<T>>
+template <typename T>
+class SequenceTracer<Sequence<T>, false, false, false> {
+ explicit SequenceTracer() = delete; // Should never be instantiated
+
+ public:
+ static void TraceSequence(JSTracer* trc, Sequence<T>* seqp,
+ Sequence<T>* end) {
+ for (; seqp != end; ++seqp) {
+ DoTraceSequence(trc, *seqp);
+ }
+ }
+};
+
+// sequence<sequence<T>> as return value
+template <typename T>
+class SequenceTracer<nsTArray<T>, false, false, false> {
+ explicit SequenceTracer() = delete; // Should never be instantiated
+
+ public:
+ static void TraceSequence(JSTracer* trc, nsTArray<T>* seqp,
+ nsTArray<T>* end) {
+ for (; seqp != end; ++seqp) {
+ DoTraceSequence(trc, *seqp);
+ }
+ }
+};
+
+// sequence<someDictionary>
+template <typename T>
+class SequenceTracer<T, true, false, false> {
+ explicit SequenceTracer() = delete; // Should never be instantiated
+
+ public:
+ static void TraceSequence(JSTracer* trc, T* dictp, T* end) {
+ for (; dictp != end; ++dictp) {
+ dictp->TraceDictionary(trc);
+ }
+ }
+};
+
+// sequence<SomeTypedArray>
+template <typename T>
+class SequenceTracer<T, false, true, false> {
+ explicit SequenceTracer() = delete; // Should never be instantiated
+
+ public:
+ static void TraceSequence(JSTracer* trc, T* arrayp, T* end) {
+ for (; arrayp != end; ++arrayp) {
+ arrayp->TraceSelf(trc);
+ }
+ }
+};
+
+// sequence<SomeOwningUnion>
+template <typename T>
+class SequenceTracer<T, false, false, true> {
+ explicit SequenceTracer() = delete; // Should never be instantiated
+
+ public:
+ static void TraceSequence(JSTracer* trc, T* arrayp, T* end) {
+ for (; arrayp != end; ++arrayp) {
+ arrayp->TraceUnion(trc);
+ }
+ }
+};
+
+// sequence<T?> with T? being a Nullable<T>
+template <typename T>
+class SequenceTracer<Nullable<T>, false, false, false> {
+ explicit SequenceTracer() = delete; // Should never be instantiated
+
+ public:
+ static void TraceSequence(JSTracer* trc, Nullable<T>* seqp,
+ Nullable<T>* end) {
+ for (; seqp != end; ++seqp) {
+ if (!seqp->IsNull()) {
+ // Pretend like we actually have a length-one sequence here so
+ // we can do template instantiation correctly for T.
+ T& val = seqp->Value();
+ T* ptr = &val;
+ SequenceTracer<T>::TraceSequence(trc, ptr, ptr + 1);
+ }
+ }
+ }
+};
+
+template <typename K, typename V>
+void TraceRecord(JSTracer* trc, Record<K, V>& record) {
+ for (auto& entry : record.Entries()) {
+ // Act like it's a one-element sequence to leverage all that infrastructure.
+ SequenceTracer<V>::TraceSequence(trc, &entry.mValue, &entry.mValue + 1);
+ }
+}
+
+// sequence<record>
+template <typename K, typename V>
+class SequenceTracer<Record<K, V>, false, false, false> {
+ explicit SequenceTracer() = delete; // Should never be instantiated
+
+ public:
+ static void TraceSequence(JSTracer* trc, Record<K, V>* seqp,
+ Record<K, V>* end) {
+ for (; seqp != end; ++seqp) {
+ TraceRecord(trc, *seqp);
+ }
+ }
+};
+
+template <typename T>
+void DoTraceSequence(JSTracer* trc, FallibleTArray<T>& seq) {
+ SequenceTracer<T>::TraceSequence(trc, seq.Elements(),
+ seq.Elements() + seq.Length());
+}
+
+template <typename T>
+void DoTraceSequence(JSTracer* trc, nsTArray<T>& seq) {
+ SequenceTracer<T>::TraceSequence(trc, seq.Elements(),
+ seq.Elements() + seq.Length());
+}
+
+// Rooter class for sequences; this is what we mostly use in the codegen
+template <typename T>
+class MOZ_RAII SequenceRooter final : private JS::CustomAutoRooter {
+ public:
+ template <typename CX>
+ SequenceRooter(const CX& cx, FallibleTArray<T>* aSequence)
+ : JS::CustomAutoRooter(cx),
+ mFallibleArray(aSequence),
+ mSequenceType(eFallibleArray) {}
+
+ template <typename CX>
+ SequenceRooter(const CX& cx, nsTArray<T>* aSequence)
+ : JS::CustomAutoRooter(cx),
+ mInfallibleArray(aSequence),
+ mSequenceType(eInfallibleArray) {}
+
+ template <typename CX>
+ SequenceRooter(const CX& cx, Nullable<nsTArray<T>>* aSequence)
+ : JS::CustomAutoRooter(cx),
+ mNullableArray(aSequence),
+ mSequenceType(eNullableArray) {}
+
+ private:
+ enum SequenceType { eInfallibleArray, eFallibleArray, eNullableArray };
+
+ virtual void trace(JSTracer* trc) override {
+ if (mSequenceType == eFallibleArray) {
+ DoTraceSequence(trc, *mFallibleArray);
+ } else if (mSequenceType == eInfallibleArray) {
+ DoTraceSequence(trc, *mInfallibleArray);
+ } else {
+ MOZ_ASSERT(mSequenceType == eNullableArray);
+ if (!mNullableArray->IsNull()) {
+ DoTraceSequence(trc, mNullableArray->Value());
+ }
+ }
+ }
+
+ union {
+ nsTArray<T>* mInfallibleArray;
+ FallibleTArray<T>* mFallibleArray;
+ Nullable<nsTArray<T>>* mNullableArray;
+ };
+
+ SequenceType mSequenceType;
+};
+
+// Rooter class for Record; this is what we mostly use in the codegen.
+template <typename K, typename V>
+class MOZ_RAII RecordRooter final : private JS::CustomAutoRooter {
+ public:
+ template <typename CX>
+ RecordRooter(const CX& cx, Record<K, V>* aRecord)
+ : JS::CustomAutoRooter(cx), mRecord(aRecord), mRecordType(eRecord) {}
+
+ template <typename CX>
+ RecordRooter(const CX& cx, Nullable<Record<K, V>>* aRecord)
+ : JS::CustomAutoRooter(cx),
+ mNullableRecord(aRecord),
+ mRecordType(eNullableRecord) {}
+
+ private:
+ enum RecordType { eRecord, eNullableRecord };
+
+ virtual void trace(JSTracer* trc) override {
+ if (mRecordType == eRecord) {
+ TraceRecord(trc, *mRecord);
+ } else {
+ MOZ_ASSERT(mRecordType == eNullableRecord);
+ if (!mNullableRecord->IsNull()) {
+ TraceRecord(trc, mNullableRecord->Value());
+ }
+ }
+ }
+
+ union {
+ Record<K, V>* mRecord;
+ Nullable<Record<K, V>>* mNullableRecord;
+ };
+
+ RecordType mRecordType;
+};
+
+template <typename T>
+class MOZ_RAII RootedUnion : public T, private JS::CustomAutoRooter {
+ public:
+ template <typename CX>
+ explicit RootedUnion(const CX& cx) : T(), JS::CustomAutoRooter(cx) {}
+
+ virtual void trace(JSTracer* trc) override { this->TraceUnion(trc); }
+};
+
+template <typename T>
+class MOZ_STACK_CLASS NullableRootedUnion : public Nullable<T>,
+ private JS::CustomAutoRooter {
+ public:
+ template <typename CX>
+ explicit NullableRootedUnion(const CX& cx)
+ : Nullable<T>(), JS::CustomAutoRooter(cx) {}
+
+ virtual void trace(JSTracer* trc) override {
+ if (!this->IsNull()) {
+ this->Value().TraceUnion(trc);
+ }
+ }
+};
+
+inline bool AddStringToIDVector(JSContext* cx,
+ JS::MutableHandleVector<jsid> vector,
+ const char* name) {
+ return vector.growBy(1) &&
+ AtomizeAndPinJSString(cx, *(vector[vector.length() - 1]).address(),
+ name);
+}
+
+// We use one constructor JSNative to represent all DOM interface objects (so
+// we can easily detect when we need to wrap them in an Xray wrapper). We store
+// the real JSNative in the mNative member of a JSNativeHolder in the
+// CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT slot of the JSFunction object for a
+// specific interface object. We also store the NativeProperties in the
+// JSNativeHolder.
+// Note that some interface objects are not yet a JSFunction but a normal
+// JSObject with a DOMJSClass, those do not use these slots.
+
+enum { CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT = 0 };
+
+bool Constructor(JSContext* cx, unsigned argc, JS::Value* vp);
+
+// Implementation of the bits that XrayWrapper needs
+
+/**
+ * This resolves operations, attributes and constants of the interfaces for obj.
+ *
+ * wrapper is the Xray JS object.
+ * obj is the target object of the Xray, a binding's instance object or a
+ * interface or interface prototype object.
+ */
+bool XrayResolveOwnProperty(
+ JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> obj,
+ JS::Handle<jsid> id,
+ JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc,
+ bool& cacheOnHolder);
+
+/**
+ * Define a property on obj through an Xray wrapper.
+ *
+ * wrapper is the Xray JS object.
+ * obj is the target object of the Xray, a binding's instance object or a
+ * interface or interface prototype object.
+ * id and desc are the parameters for the property to be defined.
+ * result is the out-parameter indicating success (read it only if
+ * this returns true and also sets *done to true).
+ * done will be set to true if a property was set as a result of this call
+ * or if we want to always avoid setting this property
+ * (i.e. indexed properties on DOM objects)
+ */
+bool XrayDefineProperty(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
+ JS::Handle<JS::PropertyDescriptor> desc,
+ JS::ObjectOpResult& result, bool* done);
+
+/**
+ * Add to props the property keys of all indexed or named properties of obj and
+ * operations, attributes and constants of the interfaces for obj.
+ *
+ * wrapper is the Xray JS object.
+ * obj is the target object of the Xray, a binding's instance object or a
+ * interface or interface prototype object.
+ * flags are JSITER_* flags.
+ */
+bool XrayOwnPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ JS::Handle<JSObject*> obj, unsigned flags,
+ JS::MutableHandleVector<jsid> props);
+
+/**
+ * Returns the prototype to use for an Xray for a DOM object, wrapped in cx's
+ * compartment. This always returns the prototype that would be used for a DOM
+ * object if we ignore any changes that might have been done to the prototype
+ * chain by JS, the XBL code or plugins.
+ *
+ * cx should be in the Xray's compartment.
+ * obj is the target object of the Xray, a binding's instance object or an
+ * interface or interface prototype object.
+ */
+inline bool XrayGetNativeProto(JSContext* cx, JS::Handle<JSObject*> obj,
+ JS::MutableHandle<JSObject*> protop) {
+ JS::Rooted<JSObject*> global(cx, JS::GetNonCCWObjectGlobal(obj));
+ {
+ JSAutoRealm ar(cx, global);
+ const DOMJSClass* domClass = GetDOMClass(obj);
+ if (domClass) {
+ ProtoHandleGetter protoGetter = domClass->mGetProto;
+ if (protoGetter) {
+ protop.set(protoGetter(cx));
+ } else {
+ protop.set(JS::GetRealmObjectPrototype(cx));
+ }
+ } else if (JS_ObjectIsFunction(obj)) {
+ MOZ_ASSERT(JS_IsNativeFunction(obj, Constructor));
+ protop.set(JS::GetRealmFunctionPrototype(cx));
+ } else {
+ const JSClass* clasp = JS::GetClass(obj);
+ MOZ_ASSERT(IsDOMIfaceAndProtoClass(clasp));
+ ProtoGetter protoGetter =
+ DOMIfaceAndProtoJSClass::FromJSClass(clasp)->mGetParentProto;
+ protop.set(protoGetter(cx));
+ }
+ }
+
+ return JS_WrapObject(cx, protop);
+}
+
+/**
+ * Get the Xray expando class to use for the given DOM object.
+ */
+const JSClass* XrayGetExpandoClass(JSContext* cx, JS::Handle<JSObject*> obj);
+
+/**
+ * Delete a named property, if any. Return value is false if exception thrown,
+ * true otherwise. The caller should not do any more work after calling this
+ * function, because it has no way whether a deletion was performed and hence
+ * opresult already has state set on it. If callers ever need to change that,
+ * add a "bool* found" argument and change the generated DeleteNamedProperty to
+ * use it instead of a local variable.
+ */
+bool XrayDeleteNamedProperty(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
+ JS::ObjectOpResult& opresult);
+
+namespace binding_detail {
+
+// Default implementations of the NativePropertyHooks' mResolveOwnProperty and
+// mEnumerateOwnProperties for WebIDL bindings implemented as proxies.
+bool ResolveOwnProperty(
+ JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> obj,
+ JS::Handle<jsid> id,
+ JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc);
+bool EnumerateOwnProperties(JSContext* cx, JS::Handle<JSObject*> wrapper,
+ JS::Handle<JSObject*> obj,
+ JS::MutableHandleVector<jsid> props);
+
+} // namespace binding_detail
+
+/**
+ * Get the object which should be used to cache the return value of a property
+ * getter in the case of a [Cached] or [StoreInSlot] property. `obj` is the
+ * `this` value for our property getter that we're working with.
+ *
+ * This function can return null on failure to allocate the object, throwing on
+ * the JSContext in the process.
+ *
+ * The isXray outparam will be set to true if obj is an Xray and false
+ * otherwise.
+ *
+ * Note that the Slow version should only be called from
+ * GetCachedSlotStorageObject.
+ */
+JSObject* GetCachedSlotStorageObjectSlow(JSContext* cx,
+ JS::Handle<JSObject*> obj,
+ bool* isXray);
+
+inline JSObject* GetCachedSlotStorageObject(JSContext* cx,
+ JS::Handle<JSObject*> obj,
+ bool* isXray) {
+ if (IsDOMObject(obj)) {
+ *isXray = false;
+ return obj;
+ }
+
+ return GetCachedSlotStorageObjectSlow(cx, obj, isXray);
+}
+
+extern NativePropertyHooks sEmptyNativePropertyHooks;
+
+extern const JSClassOps sBoringInterfaceObjectClassClassOps;
+
+extern const js::ObjectOps sInterfaceObjectClassObjectOps;
+
+inline bool UseDOMXray(JSObject* obj) {
+ const JSClass* clasp = JS::GetClass(obj);
+ return IsDOMClass(clasp) || JS_IsNativeFunction(obj, Constructor) ||
+ IsDOMIfaceAndProtoClass(clasp);
+}
+
+inline bool IsDOMConstructor(JSObject* obj) {
+ if (JS_IsNativeFunction(obj, dom::Constructor)) {
+ // LegacyFactoryFunction, like Image
+ return true;
+ }
+
+ const JSClass* clasp = JS::GetClass(obj);
+ // Check for a DOM interface object.
+ return dom::IsDOMIfaceAndProtoClass(clasp) &&
+ dom::DOMIfaceAndProtoJSClass::FromJSClass(clasp)->mType ==
+ dom::eInterface;
+}
+
+#ifdef DEBUG
+inline bool HasConstructor(JSObject* obj) {
+ return JS_IsNativeFunction(obj, Constructor) ||
+ JS::GetClass(obj)->getConstruct();
+}
+#endif
+
+// Helpers for creating a const version of a type.
+template <typename T>
+const T& Constify(T& arg) {
+ return arg;
+}
+
+// Helper for turning (Owning)NonNull<T> into T&
+template <typename T>
+T& NonNullHelper(T& aArg) {
+ return aArg;
+}
+
+template <typename T>
+T& NonNullHelper(NonNull<T>& aArg) {
+ return aArg;
+}
+
+template <typename T>
+const T& NonNullHelper(const NonNull<T>& aArg) {
+ return aArg;
+}
+
+template <typename T>
+T& NonNullHelper(OwningNonNull<T>& aArg) {
+ return aArg;
+}
+
+template <typename T>
+const T& NonNullHelper(const OwningNonNull<T>& aArg) {
+ return aArg;
+}
+
+template <typename CharT>
+inline void NonNullHelper(NonNull<binding_detail::FakeString<CharT>>& aArg) {
+ // This overload is here to make sure that we never end up applying
+ // NonNullHelper to a NonNull<binding_detail::FakeString>. If we
+ // try to, it should fail to compile, since presumably the caller will try to
+ // use our nonexistent return value.
+}
+
+template <typename CharT>
+inline void NonNullHelper(
+ const NonNull<binding_detail::FakeString<CharT>>& aArg) {
+ // This overload is here to make sure that we never end up applying
+ // NonNullHelper to a NonNull<binding_detail::FakeString>. If we
+ // try to, it should fail to compile, since presumably the caller will try to
+ // use our nonexistent return value.
+}
+
+template <typename CharT>
+inline void NonNullHelper(binding_detail::FakeString<CharT>& aArg) {
+ // This overload is here to make sure that we never end up applying
+ // NonNullHelper to a FakeString before we've constified it. If we
+ // try to, it should fail to compile, since presumably the caller will try to
+ // use our nonexistent return value.
+}
+
+template <typename CharT>
+MOZ_ALWAYS_INLINE const nsTSubstring<CharT>& NonNullHelper(
+ const binding_detail::FakeString<CharT>& aArg) {
+ return aArg;
+}
+
+// Given a DOM reflector aObj, give its underlying DOM object a reflector in
+// whatever global that underlying DOM object now thinks it should be in. If
+// this is in a different compartment from aObj, aObj will become a
+// cross-compatment wrapper for the new object. Otherwise, aObj will become the
+// new object (via a brain transplant). If the new global is the same as the
+// old global, we just keep using the same object.
+//
+// On entry to this method, aCx and aObj must be same-compartment.
+void UpdateReflectorGlobal(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ ErrorResult& aError);
+
+/**
+ * Used to implement the Symbol.hasInstance property of an interface object.
+ */
+bool InterfaceHasInstance(JSContext* cx, unsigned argc, JS::Value* vp);
+
+bool InterfaceHasInstance(JSContext* cx, int prototypeID, int depth,
+ JS::Handle<JSObject*> instance, bool* bp);
+
+// Used to implement the cross-context <Interface>.isInstance static method.
+bool InterfaceIsInstance(JSContext* cx, unsigned argc, JS::Value* vp);
+
+// Helper for lenient getters/setters to report to console. If this
+// returns false, we couldn't even get a global.
+bool ReportLenientThisUnwrappingFailure(JSContext* cx, JSObject* obj);
+
+// Given a JSObject* that represents the chrome side of a JS-implemented WebIDL
+// interface, get the nsIGlobalObject corresponding to the content side, if any.
+// A false return means an exception was thrown.
+bool GetContentGlobalForJSImplementedObject(BindingCallContext& cx,
+ JS::Handle<JSObject*> obj,
+ nsIGlobalObject** global);
+
+void ConstructJSImplementation(const char* aContractId,
+ nsIGlobalObject* aGlobal,
+ JS::MutableHandle<JSObject*> aObject,
+ ErrorResult& aRv);
+
+// XXX Avoid pulling in the whole ScriptSettings.h, however there should be a
+// unique declaration of this function somewhere else.
+JS::RootingContext* RootingCx();
+
+template <typename T>
+already_AddRefed<T> ConstructJSImplementation(const char* aContractId,
+ nsIGlobalObject* aGlobal,
+ ErrorResult& aRv) {
+ JS::RootingContext* cx = RootingCx();
+ JS::Rooted<JSObject*> jsImplObj(cx);
+ ConstructJSImplementation(aContractId, aGlobal, &jsImplObj, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ MOZ_RELEASE_ASSERT(!js::IsWrapper(jsImplObj));
+ JS::Rooted<JSObject*> jsImplGlobal(cx, JS::GetNonCCWObjectGlobal(jsImplObj));
+ RefPtr<T> newObj = new T(jsImplObj, jsImplGlobal, aGlobal);
+ return newObj.forget();
+}
+
+template <typename T>
+already_AddRefed<T> ConstructJSImplementation(const char* aContractId,
+ const GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ if (!global) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ return ConstructJSImplementation<T>(aContractId, global, aRv);
+}
+
+/**
+ * Convert an nsCString to jsval, returning true on success.
+ * These functions are intended for ByteString implementations.
+ * As such, the string is not UTF-8 encoded. Any UTF8 strings passed to these
+ * methods will be mangled.
+ */
+bool NonVoidByteStringToJsval(JSContext* cx, const nsACString& str,
+ JS::MutableHandle<JS::Value> rval);
+inline bool ByteStringToJsval(JSContext* cx, const nsACString& str,
+ JS::MutableHandle<JS::Value> rval) {
+ if (str.IsVoid()) {
+ rval.setNull();
+ return true;
+ }
+ return NonVoidByteStringToJsval(cx, str, rval);
+}
+
+// Convert an utf-8 encoded nsCString to jsval, returning true on success.
+//
+// TODO(bug 1606957): This could probably be better.
+inline bool NonVoidUTF8StringToJsval(JSContext* cx, const nsACString& str,
+ JS::MutableHandle<JS::Value> rval) {
+ JSString* jsStr =
+ JS_NewStringCopyUTF8N(cx, {str.BeginReading(), str.Length()});
+ if (!jsStr) {
+ return false;
+ }
+ rval.setString(jsStr);
+ return true;
+}
+
+inline bool UTF8StringToJsval(JSContext* cx, const nsACString& str,
+ JS::MutableHandle<JS::Value> rval) {
+ if (str.IsVoid()) {
+ rval.setNull();
+ return true;
+ }
+ return NonVoidUTF8StringToJsval(cx, str, rval);
+}
+
+template <class T, bool isISupports = std::is_base_of<nsISupports, T>::value>
+struct PreserveWrapperHelper {
+ static void PreserveWrapper(T* aObject) {
+ aObject->PreserveWrapper(aObject, NS_CYCLE_COLLECTION_PARTICIPANT(T));
+ }
+};
+
+template <class T>
+struct PreserveWrapperHelper<T, true> {
+ static void PreserveWrapper(T* aObject) {
+ aObject->PreserveWrapper(reinterpret_cast<nsISupports*>(aObject));
+ }
+};
+
+template <class T>
+void PreserveWrapper(T* aObject) {
+ PreserveWrapperHelper<T>::PreserveWrapper(aObject);
+}
+
+template <class T, bool isISupports = std::is_base_of<nsISupports, T>::value>
+struct CastingAssertions {
+ static bool ToSupportsIsCorrect(T*) { return true; }
+ static bool ToSupportsIsOnPrimaryInheritanceChain(T*, nsWrapperCache*) {
+ return true;
+ }
+};
+
+template <class T>
+struct CastingAssertions<T, true> {
+ static bool ToSupportsIsCorrect(T* aObject) {
+ return ToSupports(aObject) == reinterpret_cast<nsISupports*>(aObject);
+ }
+ static bool ToSupportsIsOnPrimaryInheritanceChain(T* aObject,
+ nsWrapperCache* aCache) {
+ return reinterpret_cast<void*>(aObject) != aCache;
+ }
+};
+
+template <class T>
+bool ToSupportsIsCorrect(T* aObject) {
+ return CastingAssertions<T>::ToSupportsIsCorrect(aObject);
+}
+
+template <class T>
+bool ToSupportsIsOnPrimaryInheritanceChain(T* aObject, nsWrapperCache* aCache) {
+ return CastingAssertions<T>::ToSupportsIsOnPrimaryInheritanceChain(aObject,
+ aCache);
+}
+
+// Get the size of allocated memory to associate with a binding JSObject for a
+// native object. This is supplied to the JS engine to allow it to schedule GC
+// when necessary.
+//
+// This function supplies a default value and is overloaded for specific native
+// object types.
+inline size_t BindingJSObjectMallocBytes(void* aNativePtr) { return 0; }
+
+// The BindingJSObjectCreator class is supposed to be used by a caller that
+// wants to create and initialise a binding JSObject. After initialisation has
+// been successfully completed it should call InitializationSucceeded().
+// The BindingJSObjectCreator object will root the JSObject until
+// InitializationSucceeded() is called on it. If the native object for the
+// binding is refcounted it will also hold a strong reference to it, that
+// reference is transferred to the JSObject (which holds the native in a slot)
+// when InitializationSucceeded() is called. If the BindingJSObjectCreator
+// object is destroyed and InitializationSucceeded() was never called on it then
+// the JSObject's slot holding the native will be set to undefined, and for a
+// refcounted native the strong reference will be released.
+template <class T>
+class MOZ_STACK_CLASS BindingJSObjectCreator {
+ public:
+ explicit BindingJSObjectCreator(JSContext* aCx) : mReflector(aCx) {}
+
+ ~BindingJSObjectCreator() {
+ if (mReflector) {
+ JS::SetReservedSlot(mReflector, DOM_OBJECT_SLOT, JS::UndefinedValue());
+ }
+ }
+
+ void CreateProxyObject(JSContext* aCx, const JSClass* aClass,
+ const DOMProxyHandler* aHandler,
+ JS::Handle<JSObject*> aProto, bool aLazyProto,
+ T* aNative, JS::Handle<JS::Value> aExpandoValue,
+ JS::MutableHandle<JSObject*> aReflector) {
+ js::ProxyOptions options;
+ options.setClass(aClass);
+ options.setLazyProto(aLazyProto);
+
+ aReflector.set(
+ js::NewProxyObject(aCx, aHandler, aExpandoValue, aProto, options));
+ if (aReflector) {
+ js::SetProxyReservedSlot(aReflector, DOM_OBJECT_SLOT,
+ JS::PrivateValue(aNative));
+ mNative = aNative;
+ mReflector = aReflector;
+
+ if (size_t mallocBytes = BindingJSObjectMallocBytes(aNative)) {
+ JS::AddAssociatedMemory(aReflector, mallocBytes,
+ JS::MemoryUse::DOMBinding);
+ }
+ }
+ }
+
+ void CreateObject(JSContext* aCx, const JSClass* aClass,
+ JS::Handle<JSObject*> aProto, T* aNative,
+ JS::MutableHandle<JSObject*> aReflector) {
+ aReflector.set(JS_NewObjectWithGivenProto(aCx, aClass, aProto));
+ if (aReflector) {
+ JS::SetReservedSlot(aReflector, DOM_OBJECT_SLOT,
+ JS::PrivateValue(aNative));
+ mNative = aNative;
+ mReflector = aReflector;
+
+ if (size_t mallocBytes = BindingJSObjectMallocBytes(aNative)) {
+ JS::AddAssociatedMemory(aReflector, mallocBytes,
+ JS::MemoryUse::DOMBinding);
+ }
+ }
+ }
+
+ void InitializationSucceeded() {
+ T* pointer;
+ mNative.forget(&pointer);
+ mReflector = nullptr;
+ }
+
+ private:
+ struct OwnedNative {
+ // Make sure the native objects inherit from NonRefcountedDOMObject so
+ // that we log their ctor and dtor.
+ static_assert(std::is_base_of<NonRefcountedDOMObject, T>::value,
+ "Non-refcounted objects with DOM bindings should inherit "
+ "from NonRefcountedDOMObject.");
+
+ OwnedNative& operator=(T* aNative) {
+ mNative = aNative;
+ return *this;
+ }
+
+ // This signature sucks, but it's the only one that will make a nsRefPtr
+ // just forget about its pointer without warning.
+ void forget(T** aResult) {
+ *aResult = mNative;
+ mNative = nullptr;
+ }
+
+ // Keep track of the pointer for use in InitializationSucceeded().
+ // The caller (or, after initialization succeeds, the JS object) retains
+ // ownership of the object.
+ T* mNative;
+ };
+
+ JS::Rooted<JSObject*> mReflector;
+ std::conditional_t<IsRefcounted<T>::value, RefPtr<T>, OwnedNative> mNative;
+};
+
+template <class T>
+struct DeferredFinalizerImpl {
+ using SmartPtr = std::conditional_t<
+ std::is_same_v<T, nsISupports>, nsCOMPtr<T>,
+ std::conditional_t<IsRefcounted<T>::value, RefPtr<T>, UniquePtr<T>>>;
+ typedef SegmentedVector<SmartPtr> SmartPtrArray;
+
+ static_assert(
+ std::is_same_v<T, nsISupports> || !std::is_base_of<nsISupports, T>::value,
+ "nsISupports classes should all use the nsISupports instantiation");
+
+ static inline void AppendAndTake(
+ SegmentedVector<nsCOMPtr<nsISupports>>& smartPtrArray, nsISupports* ptr) {
+ smartPtrArray.InfallibleAppend(dont_AddRef(ptr));
+ }
+ template <class U>
+ static inline void AppendAndTake(SegmentedVector<RefPtr<U>>& smartPtrArray,
+ U* ptr) {
+ smartPtrArray.InfallibleAppend(dont_AddRef(ptr));
+ }
+ template <class U>
+ static inline void AppendAndTake(SegmentedVector<UniquePtr<U>>& smartPtrArray,
+ U* ptr) {
+ smartPtrArray.InfallibleAppend(ptr);
+ }
+
+ static void* AppendDeferredFinalizePointer(void* aData, void* aObject) {
+ SmartPtrArray* pointers = static_cast<SmartPtrArray*>(aData);
+ if (!pointers) {
+ pointers = new SmartPtrArray();
+ }
+ AppendAndTake(*pointers, static_cast<T*>(aObject));
+ return pointers;
+ }
+ static bool DeferredFinalize(uint32_t aSlice, void* aData) {
+ MOZ_ASSERT(aSlice > 0, "nonsensical/useless call with aSlice == 0");
+ SmartPtrArray* pointers = static_cast<SmartPtrArray*>(aData);
+ uint32_t oldLen = pointers->Length();
+ if (oldLen < aSlice) {
+ aSlice = oldLen;
+ }
+ uint32_t newLen = oldLen - aSlice;
+ pointers->PopLastN(aSlice);
+ if (newLen == 0) {
+ delete pointers;
+ return true;
+ }
+ return false;
+ }
+};
+
+template <class T, bool isISupports = std::is_base_of<nsISupports, T>::value>
+struct DeferredFinalizer {
+ static void AddForDeferredFinalization(T* aObject) {
+ typedef DeferredFinalizerImpl<T> Impl;
+ DeferredFinalize(Impl::AppendDeferredFinalizePointer,
+ Impl::DeferredFinalize, aObject);
+ }
+};
+
+template <class T>
+struct DeferredFinalizer<T, true> {
+ static void AddForDeferredFinalization(T* aObject) {
+ DeferredFinalize(reinterpret_cast<nsISupports*>(aObject));
+ }
+};
+
+template <class T>
+static void AddForDeferredFinalization(T* aObject) {
+ DeferredFinalizer<T>::AddForDeferredFinalization(aObject);
+}
+
+// This returns T's CC participant if it participates in CC and does not inherit
+// from nsISupports. Otherwise, it returns null. QI should be used to get the
+// participant if T inherits from nsISupports.
+template <class T, bool isISupports = std::is_base_of<nsISupports, T>::value>
+class GetCCParticipant {
+ // Helper for GetCCParticipant for classes that participate in CC.
+ template <class U>
+ static constexpr nsCycleCollectionParticipant* GetHelper(
+ int, typename U::NS_CYCLE_COLLECTION_INNERCLASS* dummy = nullptr) {
+ return T::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant();
+ }
+ // Helper for GetCCParticipant for classes that don't participate in CC.
+ template <class U>
+ static constexpr nsCycleCollectionParticipant* GetHelper(double) {
+ return nullptr;
+ }
+
+ public:
+ static constexpr nsCycleCollectionParticipant* Get() {
+ // Passing int() here will try to call the GetHelper that takes an int as
+ // its first argument. If T doesn't participate in CC then substitution for
+ // the second argument (with a default value) will fail and because of
+ // SFINAE the next best match (the variant taking a double) will be called.
+ return GetHelper<T>(int());
+ }
+};
+
+template <class T>
+class GetCCParticipant<T, true> {
+ public:
+ static constexpr nsCycleCollectionParticipant* Get() { return nullptr; }
+};
+
+void FinalizeGlobal(JS::GCContext* aGcx, JSObject* aObj);
+
+bool ResolveGlobal(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::Handle<jsid> aId, bool* aResolvedp);
+
+bool MayResolveGlobal(const JSAtomState& aNames, jsid aId, JSObject* aMaybeObj);
+
+bool EnumerateGlobal(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::MutableHandleVector<jsid> aProperties,
+ bool aEnumerableOnly);
+
+struct CreateGlobalOptionsGeneric {
+ static void TraceGlobal(JSTracer* aTrc, JSObject* aObj) {
+ mozilla::dom::TraceProtoAndIfaceCache(aTrc, aObj);
+ }
+ static bool PostCreateGlobal(JSContext* aCx, JS::Handle<JSObject*> aGlobal) {
+ MOZ_ALWAYS_TRUE(TryPreserveWrapper(aGlobal));
+
+ return true;
+ }
+};
+
+struct CreateGlobalOptionsWithXPConnect {
+ static void TraceGlobal(JSTracer* aTrc, JSObject* aObj);
+ static bool PostCreateGlobal(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
+};
+
+template <class T>
+using IsGlobalWithXPConnect =
+ std::integral_constant<bool,
+ std::is_base_of<nsGlobalWindowInner, T>::value ||
+ std::is_base_of<MessageManagerGlobal, T>::value>;
+
+template <class T>
+struct CreateGlobalOptions
+ : std::conditional_t<IsGlobalWithXPConnect<T>::value,
+ CreateGlobalOptionsWithXPConnect,
+ CreateGlobalOptionsGeneric> {
+ static constexpr ProtoAndIfaceCache::Kind ProtoAndIfaceCacheKind =
+ ProtoAndIfaceCache::NonWindowLike;
+};
+
+template <>
+struct CreateGlobalOptions<nsGlobalWindowInner>
+ : public CreateGlobalOptionsWithXPConnect {
+ static constexpr ProtoAndIfaceCache::Kind ProtoAndIfaceCacheKind =
+ ProtoAndIfaceCache::WindowLike;
+};
+
+uint64_t GetWindowID(void* aGlobal);
+uint64_t GetWindowID(nsGlobalWindowInner* aGlobal);
+uint64_t GetWindowID(DedicatedWorkerGlobalScope* aGlobal);
+
+// The return value is true if we created and successfully performed our part of
+// the setup for the global, false otherwise.
+//
+// Typically this method's caller will want to ensure that
+// xpc::InitGlobalObjectOptions is called before, and xpc::InitGlobalObject is
+// called after, this method, to ensure that this global object and its
+// compartment are consistent with other global objects.
+template <class T, ProtoHandleGetter GetProto>
+bool CreateGlobal(JSContext* aCx, T* aNative, nsWrapperCache* aCache,
+ const JSClass* aClass, JS::RealmOptions& aOptions,
+ JSPrincipals* aPrincipal, bool aInitStandardClasses,
+ JS::MutableHandle<JSObject*> aGlobal) {
+ aOptions.creationOptions()
+ .setTrace(CreateGlobalOptions<T>::TraceGlobal)
+ .setProfilerRealmID(GetWindowID(aNative));
+ xpc::SetPrefableRealmOptions(aOptions);
+
+ aGlobal.set(JS_NewGlobalObject(aCx, aClass, aPrincipal,
+ JS::DontFireOnNewGlobalHook, aOptions));
+ if (!aGlobal) {
+ NS_WARNING("Failed to create global");
+ return false;
+ }
+
+ JSAutoRealm ar(aCx, aGlobal);
+
+ {
+ JS::SetReservedSlot(aGlobal, DOM_OBJECT_SLOT, JS::PrivateValue(aNative));
+ NS_ADDREF(aNative);
+
+ aCache->SetWrapper(aGlobal);
+
+ dom::AllocateProtoAndIfaceCache(
+ aGlobal, CreateGlobalOptions<T>::ProtoAndIfaceCacheKind);
+
+ if (!CreateGlobalOptions<T>::PostCreateGlobal(aCx, aGlobal)) {
+ return false;
+ }
+ }
+
+ if (aInitStandardClasses && !JS::InitRealmStandardClasses(aCx)) {
+ NS_WARNING("Failed to init standard classes");
+ return false;
+ }
+
+ JS::Handle<JSObject*> proto = GetProto(aCx);
+ if (!proto || !JS_SetPrototype(aCx, aGlobal, proto)) {
+ NS_WARNING("Failed to set proto");
+ return false;
+ }
+
+ bool succeeded;
+ if (!JS_SetImmutablePrototype(aCx, aGlobal, &succeeded)) {
+ return false;
+ }
+ MOZ_ASSERT(succeeded,
+ "making a fresh global object's [[Prototype]] immutable can "
+ "internally fail, but it should never be unsuccessful");
+
+ return true;
+}
+
+namespace binding_detail {
+/**
+ * WebIDL getters have a "generic" JSNative that is responsible for the
+ * following things:
+ *
+ * 1) Determining the "this" pointer for the C++ call.
+ * 2) Extracting the "specialized" getter from the jitinfo on the JSFunction.
+ * 3) Calling the specialized getter.
+ * 4) Handling exceptions as needed.
+ *
+ * There are several variants of (1) depending on the interface involved and
+ * there are two variants of (4) depending on whether the return type is a
+ * Promise. We handle this by templating our generic getter on a
+ * this-determination policy and an exception handling policy, then explicitly
+ * instantiating the relevant template specializations.
+ */
+template <typename ThisPolicy, typename ExceptionPolicy>
+bool GenericGetter(JSContext* cx, unsigned argc, JS::Value* vp);
+
+/**
+ * WebIDL setters have a "generic" JSNative that is responsible for the
+ * following things:
+ *
+ * 1) Determining the "this" pointer for the C++ call.
+ * 2) Extracting the "specialized" setter from the jitinfo on the JSFunction.
+ * 3) Calling the specialized setter.
+ *
+ * There are several variants of (1) depending on the interface
+ * involved. We handle this by templating our generic setter on a
+ * this-determination policy, then explicitly instantiating the
+ * relevant template specializations.
+ */
+template <typename ThisPolicy>
+bool GenericSetter(JSContext* cx, unsigned argc, JS::Value* vp);
+
+/**
+ * WebIDL methods have a "generic" JSNative that is responsible for the
+ * following things:
+ *
+ * 1) Determining the "this" pointer for the C++ call.
+ * 2) Extracting the "specialized" method from the jitinfo on the JSFunction.
+ * 3) Calling the specialized methodx.
+ * 4) Handling exceptions as needed.
+ *
+ * There are several variants of (1) depending on the interface involved and
+ * there are two variants of (4) depending on whether the return type is a
+ * Promise. We handle this by templating our generic method on a
+ * this-determination policy and an exception handling policy, then explicitly
+ * instantiating the relevant template specializations.
+ */
+template <typename ThisPolicy, typename ExceptionPolicy>
+bool GenericMethod(JSContext* cx, unsigned argc, JS::Value* vp);
+
+// A this-extraction policy for normal getters/setters/methods.
+struct NormalThisPolicy;
+
+// A this-extraction policy for getters/setters/methods on interfaces
+// that are on some global's proto chain.
+struct MaybeGlobalThisPolicy;
+
+// A this-extraction policy for lenient getters/setters.
+struct LenientThisPolicy;
+
+// A this-extraction policy for cross-origin getters/setters/methods.
+struct CrossOriginThisPolicy;
+
+// A this-extraction policy for getters/setters/methods that should
+// not be allowed to be called cross-origin but expect objects that
+// _can_ be cross-origin.
+struct MaybeCrossOriginObjectThisPolicy;
+
+// A this-extraction policy which is just like
+// MaybeCrossOriginObjectThisPolicy but has lenient-this behavior.
+struct MaybeCrossOriginObjectLenientThisPolicy;
+
+// An exception-reporting policy for normal getters/setters/methods.
+struct ThrowExceptions;
+
+// An exception-handling policy for Promise-returning getters/methods.
+struct ConvertExceptionsToPromises;
+} // namespace binding_detail
+
+bool StaticMethodPromiseWrapper(JSContext* cx, unsigned argc, JS::Value* vp);
+
+// ConvertExceptionToPromise should only be called when we have an error
+// condition (e.g. returned false from a JSAPI method). Note that there may be
+// no exception on cx, in which case this is an uncatchable failure that will
+// simply be propagated. Otherwise this method will attempt to convert the
+// exception to a Promise rejected with the exception that it will store in
+// rval.
+bool ConvertExceptionToPromise(JSContext* cx,
+ JS::MutableHandle<JS::Value> rval);
+
+#ifdef DEBUG
+void AssertReturnTypeMatchesJitinfo(const JSJitInfo* aJitinfo,
+ JS::Handle<JS::Value> aValue);
+#endif
+
+bool CallerSubsumes(JSObject* aObject);
+
+MOZ_ALWAYS_INLINE bool CallerSubsumes(JS::Handle<JS::Value> aValue) {
+ if (!aValue.isObject()) {
+ return true;
+ }
+ return CallerSubsumes(&aValue.toObject());
+}
+
+template <class T, class S>
+inline RefPtr<T> StrongOrRawPtr(already_AddRefed<S>&& aPtr) {
+ return std::move(aPtr);
+}
+
+template <class T, class S>
+inline RefPtr<T> StrongOrRawPtr(RefPtr<S>&& aPtr) {
+ return std::move(aPtr);
+}
+
+template <class T, class ReturnType = std::conditional_t<IsRefcounted<T>::value,
+ T*, UniquePtr<T>>>
+inline ReturnType StrongOrRawPtr(T* aPtr) {
+ return ReturnType(aPtr);
+}
+
+template <class T, template <typename> class SmartPtr, class S>
+inline void StrongOrRawPtr(SmartPtr<S>&& aPtr) = delete;
+
+template <class T>
+using StrongPtrForMember =
+ std::conditional_t<IsRefcounted<T>::value, RefPtr<T>, UniquePtr<T>>;
+
+namespace binding_detail {
+inline JSObject* GetHackedNamespaceProtoObject(JSContext* aCx) {
+ return JS_NewPlainObject(aCx);
+}
+} // namespace binding_detail
+
+// Resolve an id on the given global object that wants to be included in
+// Exposed=System webidl annotations. False return value means exception
+// thrown.
+bool SystemGlobalResolve(JSContext* cx, JS::Handle<JSObject*> obj,
+ JS::Handle<jsid> id, bool* resolvedp);
+
+// Enumerate all ids on the given global object that wants to be included in
+// Exposed=System webidl annotations. False return value means exception
+// 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);
+
+// Unpacks backing object (ES Proxy exotic object) from the reserved slot of a
+// reflector for a observableArray attribute. 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 GetObservableArrayBackingObject(
+ JSContext* aCx, JS::Handle<JSObject*> aObj, size_t aSlotIndex,
+ JS::MutableHandle<JSObject*> aBackingObj, bool* aBackingObjCreated,
+ const ObservableArrayProxyHandler* aHandler, void* aOwner);
+
+// Get the desired prototype object for an object construction from the given
+// CallArgs. The CallArgs must be for a constructor call. The
+// aProtoId/aCreator arguments are used to get a default if we don't find a
+// prototype on the newTarget of the callargs.
+bool GetDesiredProto(JSContext* aCx, const JS::CallArgs& aCallArgs,
+ prototypes::id::ID aProtoId,
+ CreateInterfaceObjectsMethod aCreator,
+ JS::MutableHandle<JSObject*> aDesiredProto);
+
+// This function is expected to be called from the constructor function for an
+// HTML or XUL element interface; the global/callargs need to be whatever was
+// passed to that constructor function.
+already_AddRefed<Element> CreateXULOrHTMLElement(
+ const GlobalObject& aGlobal, const JS::CallArgs& aCallArgs,
+ JS::Handle<JSObject*> aGivenProto, ErrorResult& aRv);
+
+void SetUseCounter(JSObject* aObject, UseCounter aUseCounter);
+void SetUseCounter(UseCounterWorker aUseCounter);
+
+// Warnings
+void DeprecationWarning(JSContext* aCx, JSObject* aObject,
+ DeprecatedOperations aOperation);
+
+void DeprecationWarning(const GlobalObject& aGlobal,
+ DeprecatedOperations aOperation);
+
+// A callback to perform funToString on an interface object
+JSString* InterfaceObjectToString(JSContext* aCx, JS::Handle<JSObject*> aObject,
+ unsigned /* indent */);
+
+namespace binding_detail {
+// Get a JS global object that can be used for some temporary allocations. The
+// idea is that this should be used for situations when you need to operate in
+// _some_ compartment but don't care which one. A typical example is when you
+// have non-JS input, non-JS output, but have to go through some sort of JS
+// representation in the middle, so need a compartment to allocate things in.
+//
+// It's VERY important that any consumers of this function only do things that
+// are guaranteed to be side-effect-free, even in the face of a script
+// environment controlled by a hostile adversary. This is because in the worker
+// case the global is in fact the worker global, so it and its standard objects
+// are controlled by the worker script. This is why this function is in the
+// binding_detail namespace. Any use of this function MUST be very carefully
+// reviewed by someone who is sufficiently devious and has a very good
+// understanding of all the code that will run while we're using the return
+// value, including the SpiderMonkey parts.
+JSObject* UnprivilegedJunkScopeOrWorkerGlobal(const fallible_t&);
+
+// Implementation of the [HTMLConstructor] extended attribute.
+bool HTMLConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp,
+ constructors::id::ID aConstructorId,
+ prototypes::id::ID aProtoId,
+ CreateInterfaceObjectsMethod aCreator);
+
+// A method to test whether an attribute with the given JSJitGetterOp getter is
+// enabled in the given set of prefable proeprty specs. For use for toJSON
+// conversions. aObj is the object that would be used as the "this" value.
+bool IsGetterEnabled(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JSJitGetterOp aGetter,
+ const Prefable<const JSPropertySpec>* aAttributes);
+
+// A class that can be used to examine the chars of a linear string.
+class StringIdChars {
+ public:
+ // Require a non-const ref to an AutoRequireNoGC to prevent callers
+ // from passing temporaries.
+ StringIdChars(JS::AutoRequireNoGC& nogc, JSLinearString* str) {
+ mIsLatin1 = JS::LinearStringHasLatin1Chars(str);
+ if (mIsLatin1) {
+ mLatin1Chars = JS::GetLatin1LinearStringChars(nogc, str);
+ } else {
+ mTwoByteChars = JS::GetTwoByteLinearStringChars(nogc, str);
+ }
+#ifdef DEBUG
+ mLength = JS::GetLinearStringLength(str);
+#endif // DEBUG
+ }
+
+ MOZ_ALWAYS_INLINE char16_t operator[](size_t index) {
+ MOZ_ASSERT(index < mLength);
+ if (mIsLatin1) {
+ return mLatin1Chars[index];
+ }
+ return mTwoByteChars[index];
+ }
+
+ private:
+ bool mIsLatin1;
+ union {
+ const JS::Latin1Char* mLatin1Chars;
+ const char16_t* mTwoByteChars;
+ };
+#ifdef DEBUG
+ size_t mLength;
+#endif // DEBUG
+};
+
+already_AddRefed<Promise> CreateRejectedPromiseFromThrownException(
+ JSContext* aCx, ErrorResult& aError);
+
+} // namespace binding_detail
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_BindingUtils_h__ */
diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf
new file mode 100644
index 0000000000..6882521149
--- /dev/null
+++ b/dom/bindings/Bindings.conf
@@ -0,0 +1,2131 @@
+# -*- Mode:Python; tab-width:8; indent-tabs-mode:nil -*- */
+# vim: set ts=8 sts=4 et sw=4 tw=80: */
+# 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/.
+
+# DOM Bindings Configuration.
+#
+# The WebIDL interfaces are defined in dom/webidl. For interfaces requiring
+# special handling, there are corresponding entries in the configuration table
+# below. The configuration table maps each interface name to a |descriptor|.
+#
+# Valid fields for all descriptors:
+# * nativeType - The native type (concrete class or XPCOM interface) that
+# instances of this interface will unwrap to. If not
+# specified, defaults to 'nsIDOM' followed by the interface
+# name for external interfaces and
+# 'mozilla::dom::InterfaceName' for everything else.
+# * headerFile - The file in which the nativeType is declared (defaults
+# to an educated guess).
+# * concrete - Indicates whether there exist JS objects with this interface as
+# their primary interface (and hence whose prototype is this
+# interface's prototype object). Always False for callback
+# interfaces. Defaults to True for leaf interfaces and
+# interfaces with constructors, false otherwise.
+# * notflattened - The native type does not have nsIClassInfo, so when
+# wrapping it the right IID needs to be passed in.
+# Only relevant for callback interfaces.
+# * register - True if this binding should be registered. Defaults to true.
+# * wrapperCache: True if this object is a wrapper cache. Objects that are
+# not can only be returned from a limited set of methods,
+# cannot be prefable, and must ensure that they disallow
+# XPConnect wrapping. Always false for callback interfaces.
+# Defaults to true for non-callback descriptors.
+# * implicitJSContext - Llist of names of attributes and methods specified in
+# the .webidl file that require a JSContext as the first
+# argument.
+#
+# The value for an interface is a dictionary which specifies the
+# descriptor to use when generating that interface's binding.
+
+DOMInterfaces = {
+
+'AbortSignal': {
+ 'implicitJSContext': [ 'throwIfAborted' ],
+ 'concrete': True,
+},
+
+'AnonymousContent': {
+ 'wrapperCache': False
+},
+
+'ArchiveReader': {
+ 'nativeType': 'mozilla::dom::archivereader::ArchiveReader',
+},
+
+'ArchiveRequest': {
+ 'nativeType': 'mozilla::dom::archivereader::ArchiveRequest',
+},
+
+'AudioBuffer': {
+ 'implicitJSContext': [ 'copyToChannel' ],
+},
+
+'AudioBufferSourceNode': {
+ 'implicitJSContext': [ 'buffer' ],
+},
+
+'AudioWorklet': {
+ 'nativeType': 'mozilla::dom::Worklet',
+},
+
+'AudioWorkletGlobalScope': {
+ 'implicitJSContext': [ 'registerProcessor' ],
+},
+
+'BarProp': {
+ 'headerFile': 'mozilla/dom/BarProps.h',
+},
+
+'BaseAudioContext': {
+ 'nativeType': 'mozilla::dom::AudioContext',
+},
+
+'BatteryManager': {
+ 'nativeType': 'mozilla::dom::battery::BatteryManager',
+ 'headerFile': 'BatteryManager.h'
+},
+
+'Blob': {
+ 'implicitJSContext': [ 'stream' ],
+},
+
+'BrowsingContext': {
+ 'concrete': True,
+},
+
+'Cache': {
+ 'implicitJSContext': [ 'add', 'addAll', 'match', 'matchAll', 'put',
+ 'delete', 'keys' ],
+ 'nativeType': 'mozilla::dom::cache::Cache',
+},
+
+'CacheStorage': {
+ 'implicitJSContext': [ 'match' ],
+ 'nativeType': 'mozilla::dom::cache::CacheStorage',
+},
+
+'CanvasRenderingContext2D': {
+ 'implicitJSContext': [
+ 'createImageData', 'getImageData', 'isPointInPath', 'isPointInStroke'
+ ],
+},
+
+'CaretPosition' : {
+ 'nativeType': 'nsDOMCaretPosition',
+},
+
+'ChannelWrapper': {
+ 'nativeType': 'mozilla::extensions::ChannelWrapper',
+},
+
+'Client' : {
+ 'concrete': True,
+},
+
+'ClonedErrorHolder': {
+ 'wrapperCache': False
+},
+
+'console': {
+ 'nativeType': 'mozilla::dom::Console',
+},
+
+'ConsoleInstance': {
+ 'implicitJSContext': ['clear', 'count', 'countReset', 'groupEnd', 'time', 'timeEnd'],
+},
+
+'ConvolverNode': {
+ 'implicitJSContext': [ 'buffer' ],
+},
+
+'Credential' : {
+ 'concrete': True,
+},
+
+'Crypto' : {
+ 'headerFile': 'Crypto.h'
+},
+
+'CSS2Properties': {
+ 'nativeType': 'nsDOMCSSDeclaration'
+},
+
+'CSSConditionRule': {
+ 'nativeType': 'mozilla::css::ConditionRule',
+ 'headerFile': 'mozilla/css/GroupRule.h',
+},
+
+'CSSGroupingRule': {
+ 'nativeType': 'mozilla::css::GroupRule',
+},
+
+'CSSLexer': {
+ 'wrapperCache': False
+},
+
+'CSSRule': {
+ 'nativeType': 'mozilla::css::Rule'
+},
+
+'CSSStyleDeclaration': {
+ 'nativeType': 'nsICSSDeclaration',
+ # Concrete because of the font-face mess.
+ 'concrete': True,
+},
+
+'CSSStyleRule': {
+ 'nativeType': 'mozilla::BindingStyleRule',
+},
+
+'CSSStyleSheet': {
+ 'nativeType': 'mozilla::StyleSheet',
+},
+
+'CustomElementRegistry': {
+ 'implicitJSContext': ['define'],
+},
+
+'DebuggerNotification': {
+ 'concrete': True,
+},
+'CallbackDebuggerNotification': {
+ 'concrete': True,
+},
+
+'DedicatedWorkerGlobalScope': {
+ 'headerFile': 'mozilla/dom/WorkerScope.h',
+},
+
+'DeviceAcceleration': {
+ 'headerFile': 'mozilla/dom/DeviceMotionEvent.h',
+},
+
+'DeviceRotationRate': {
+ 'headerFile': 'mozilla/dom/DeviceMotionEvent.h',
+},
+
+'DominatorTree': {
+ 'nativeType': 'mozilla::devtools::DominatorTree'
+},
+
+'DOMException': {
+ 'implicitJSContext': [ 'filename', 'lineNumber', 'stack' ],
+},
+
+'DOMMatrixReadOnly': {
+ 'headerFile': 'mozilla/dom/DOMMatrix.h',
+},
+
+'DOMPointReadOnly': {
+ 'headerFile': 'mozilla/dom/DOMPoint.h',
+},
+
+'DOMRectList': {
+ 'headerFile': 'mozilla/dom/DOMRect.h',
+},
+
+'DOMRectReadOnly': {
+ 'headerFile': 'mozilla/dom/DOMRect.h',
+},
+
+'DOMRequest': {
+ 'concrete': True,
+},
+
+'DOMStringMap': {
+ 'nativeType': 'nsDOMStringMap'
+},
+
+'DOMTokenList': {
+ 'nativeType': 'nsDOMTokenList',
+},
+
+'Element': {
+ 'concrete': True,
+},
+
+'Event': {
+ 'implicitJSContext': [ 'preventDefault' ],
+},
+
+'EventTarget': {
+ 'jsImplParent': 'mozilla::DOMEventTargetHelper',
+},
+
+'Exception': {
+ 'headerFile': 'mozilla/dom/DOMException.h',
+ 'implicitJSContext': [ '__stringifier', 'filename', 'lineNumber', 'stack' ],
+},
+
+'ExtendableEvent': {
+ 'headerFile': 'mozilla/dom/ServiceWorkerEvents.h',
+ 'implicitJSContext': [ 'waitUntil' ],
+},
+
+'ExtendableMessageEvent': {
+ 'headerFile': 'mozilla/dom/ServiceWorkerEvents.h',
+},
+
+'FetchEvent': {
+ 'headerFile': 'ServiceWorkerEvents.h',
+ 'implicitJSContext': [ 'respondWith' ],
+},
+
+'FileReader': {
+ 'implicitJSContext': [ 'readAsArrayBuffer' ],
+},
+
+'FileReaderSync': {
+ 'wrapperCache': False,
+},
+
+'FileSystemEntry': {
+ 'concrete': True,
+},
+
+'FileSystemHandle': {
+ 'concrete': True,
+},
+
+'FluentBundle': {
+ 'nativeType': 'mozilla::intl::FluentBundle',
+},
+
+'FluentBundleAsyncIterator': {
+ 'headerFile': 'mozilla/intl/L10nRegistry.h',
+ 'nativeType': 'mozilla::intl::FluentBundleAsyncIterator',
+},
+
+'FluentBundleIterator': {
+ 'headerFile': 'mozilla/intl/L10nRegistry.h',
+ 'nativeType': 'mozilla::intl::FluentBundleIterator',
+},
+
+'FluentPattern': {
+ 'headerFile': 'mozilla/intl/FluentBundle.h',
+ 'nativeType': 'mozilla::intl::FluentPattern',
+},
+
+'FluentResource': {
+ 'headerFile': 'mozilla/intl/FluentResource.h',
+ 'nativeType': 'mozilla::intl::FluentResource',
+},
+
+'FontFaceSet': {
+ 'implicitJSContext': [ 'load' ],
+},
+
+'FontFaceSetIterator': {
+ 'wrapperCache': False,
+},
+
+'FrameLoader': {
+ 'nativeType': 'nsFrameLoader',
+},
+
+'FuzzingFunctions': {
+ # The codegen is dumb, and doesn't understand that this interface is only a
+ # collection of static methods, so we have this `concrete: False` hack.
+ 'concrete': False,
+ 'headerFile': 'mozilla/dom/FuzzingFunctions.h',
+},
+
+'HeapSnapshot': {
+ 'nativeType': 'mozilla::devtools::HeapSnapshot'
+},
+
+'History': {
+ 'headerFile': 'nsHistory.h',
+ 'nativeType': 'nsHistory'
+},
+
+'HTMLBaseElement': {
+ 'nativeType': 'mozilla::dom::HTMLSharedElement'
+},
+
+'HTMLCollection': {
+ 'nativeType': 'nsIHTMLCollection',
+ # nsContentList.h pulls in nsIHTMLCollection.h
+ 'headerFile': 'nsContentList.h',
+ 'concrete': True,
+},
+
+'HTMLDirectoryElement': {
+ 'nativeType': 'mozilla::dom::HTMLSharedElement'
+},
+
+'HTMLDListElement': {
+ 'nativeType' : 'mozilla::dom::HTMLSharedListElement'
+},
+
+'HTMLDocument': {
+ 'nativeType': 'nsHTMLDocument',
+ 'concrete': True,
+},
+
+'HTMLElement': {
+ 'nativeType': 'nsGenericHTMLElement',
+},
+
+'HTMLHeadElement': {
+ 'nativeType': 'mozilla::dom::HTMLSharedElement'
+},
+
+'HTMLHtmlElement': {
+ 'nativeType': 'mozilla::dom::HTMLSharedElement'
+},
+
+'HTMLOListElement': {
+ 'nativeType' : 'mozilla::dom::HTMLSharedListElement'
+},
+
+'HTMLParamElement': {
+ 'nativeType': 'mozilla::dom::HTMLSharedElement'
+},
+
+'HTMLQuoteElement': {
+ 'nativeType': 'mozilla::dom::HTMLSharedElement'
+},
+
+'HTMLUListElement': {
+ 'nativeType' : 'mozilla::dom::HTMLSharedListElement'
+},
+
+'IDBCursor': {
+ 'implicitJSContext': [ 'delete' ],
+ 'concrete': True,
+},
+
+'IDBCursorWithValue': {
+ 'nativeType': 'mozilla::dom::IDBCursor',
+},
+
+'IDBDatabase': {
+ 'implicitJSContext': [ 'transaction' ],
+},
+
+'IDBFactory': {
+ 'implicitJSContext': [ 'open', 'deleteDatabase', 'openForPrincipal',
+ 'deleteForPrincipal' ],
+},
+
+'IDBKeyRange': {
+ 'wrapperCache': False,
+ 'concrete': True,
+},
+
+'IDBLocaleAwareKeyRange': {
+ 'headerFile': 'IDBKeyRange.h',
+ 'wrapperCache': False,
+},
+
+'IDBObjectStore': {
+ 'implicitJSContext': [ 'clear' ],
+},
+
+'IDBOpenDBRequest': {
+ 'headerFile': 'IDBRequest.h'
+},
+
+'IDBRequest': {
+ 'concrete': True,
+},
+
+'IDBVersionChangeEvent': {
+ 'headerFile': 'IDBEvents.h',
+},
+
+'ImageData': {
+ 'wrapperCache': False,
+},
+
+'InputStream': {
+ 'nativeType': 'nsIInputStream',
+ 'notflattened': True
+},
+
+'InspectorFontFace': {
+ 'wrapperCache': False,
+},
+
+'IntersectionObserver': {
+ 'nativeType': 'mozilla::dom::DOMIntersectionObserver',
+},
+
+'IntersectionObserverEntry': {
+ 'nativeType': 'mozilla::dom::DOMIntersectionObserverEntry',
+ 'headerFile': 'DOMIntersectionObserver.h',
+},
+
+'KeyEvent' : {
+ 'concrete': False,
+},
+
+'L10nFileSource': {
+ 'headerFile': 'mozilla/intl/FileSource.h',
+ 'nativeType': 'mozilla::intl::L10nFileSource',
+},
+
+'L10nRegistry': {
+ 'nativeType': 'mozilla::intl::L10nRegistry',
+},
+
+'LegacyMozTCPSocket': {
+ 'headerFile': 'TCPSocket.h',
+ 'wrapperCache': False,
+},
+
+'Localization': {
+ 'nativeType': 'mozilla::intl::Localization',
+},
+
+'MatchGlob': {
+ 'nativeType': 'mozilla::extensions::MatchGlob',
+},
+
+'MatchPattern': {
+ 'nativeType': 'mozilla::extensions::MatchPattern',
+},
+
+'MatchPatternSet': {
+ 'headerFile': 'mozilla/extensions/MatchPattern.h',
+ 'nativeType': 'mozilla::extensions::MatchPatternSet',
+},
+
+'MediaCapabilitiesInfo' : {
+ 'wrapperCache': False,
+},
+
+'MediaStream': {
+ 'headerFile': 'DOMMediaStream.h',
+ 'nativeType': 'mozilla::DOMMediaStream'
+},
+
+'MediaStreamList': {
+ 'headerFile': 'MediaStreamList.h',
+},
+
+'MediaRecorder': {
+ 'headerFile': 'MediaRecorder.h',
+},
+
+'MimeType': {
+ 'headerFile' : 'nsMimeTypeArray.h',
+ 'nativeType': 'nsMimeType',
+},
+
+'MimeTypeArray': {
+ 'nativeType': 'nsMimeTypeArray',
+},
+
+'MozCanvasPrintState': {
+ 'headerFile': 'mozilla/dom/HTMLCanvasElement.h',
+ 'nativeType': 'mozilla::dom::HTMLCanvasPrintState',
+},
+
+'MozChannel': {
+ 'nativeType': 'nsIChannel',
+ 'notflattened': True
+},
+
+'MozDocumentMatcher': {
+ 'nativeType': 'mozilla::extensions::MozDocumentMatcher',
+ 'headerFile': 'mozilla/extensions/WebExtensionContentScript.h',
+},
+
+'MozDocumentObserver': {
+ 'nativeType': 'mozilla::extensions::DocumentObserver',
+},
+
+'MozSharedMap': {
+ 'nativeType': 'mozilla::dom::ipc::SharedMap',
+ 'concrete': True,
+},
+
+'MozWritableSharedMap': {
+ 'headerFile': 'mozilla/dom/ipc/SharedMap.h',
+ 'nativeType': 'mozilla::dom::ipc::WritableSharedMap',
+},
+
+'MozSharedMapChangeEvent': {
+ 'nativeType': 'mozilla::dom::ipc::SharedMapChangeEvent',
+},
+
+'MozStorageAsyncStatementParams': {
+ 'headerFile': 'mozilla/storage/mozStorageAsyncStatementParams.h',
+ 'nativeType': 'mozilla::storage::AsyncStatementParams',
+},
+
+'MozStorageStatementParams': {
+ 'headerFile': 'mozilla/storage/mozStorageStatementParams.h',
+ 'nativeType': 'mozilla::storage::StatementParams',
+},
+
+'MozStorageStatementRow': {
+ 'headerFile': 'mozilla/storage/mozStorageStatementRow.h',
+ 'nativeType': 'mozilla::storage::StatementRow',
+},
+
+'MozQueryInterface': {
+ 'wrapperCache': False,
+},
+
+'MutationObserver': {
+ 'nativeType': 'nsDOMMutationObserver',
+},
+
+'MutationRecord': {
+ 'nativeType': 'nsDOMMutationRecord',
+ 'headerFile': 'nsDOMMutationObserver.h',
+},
+
+'NamedNodeMap': {
+ 'nativeType': 'nsDOMAttributeMap',
+},
+
+'NetworkInformation': {
+ 'nativeType': 'mozilla::dom::network::Connection',
+},
+
+'Node': {
+ 'nativeType': 'nsINode',
+ # Some WebIDL APIs that return Node use nsIContent internally (which doesn't
+ # have a direct correspondence with any WebIDL interface), so we need to use
+ # nsIContent.h so that the compiler knows nsIContent and nsINode are related
+ # by inheritance.
+ 'headerFile': 'nsIContent.h',
+},
+
+'NodeIterator': {
+ 'wrapperCache': False,
+},
+
+'NodeList': {
+ 'nativeType': 'nsINodeList',
+ 'concrete': True,
+},
+
+'OfflineAudioContext': {
+ 'nativeType': 'mozilla::dom::AudioContext',
+},
+
+'OffscreenCanvasRenderingContext2D': {
+ 'implicitJSContext': [
+ 'createImageData', 'getImageData', 'isPointInPath', 'isPointInStroke'
+ ],
+},
+
+'PaintRequestList': {
+ 'headerFile': 'mozilla/dom/PaintRequest.h',
+},
+
+'Path2D': {
+ 'nativeType': 'mozilla::dom::CanvasPath',
+ 'headerFile': 'CanvasPath.h'
+},
+
+'PeerConnectionImpl': {
+ 'nativeType': 'mozilla::PeerConnectionImpl',
+ 'headerFile': 'PeerConnectionImpl.h',
+},
+
+'Performance' : {
+ 'implicitJSContext': [
+ 'mark'
+ ],
+},
+
+'PerformanceResourceTiming' : {
+ 'concrete': True,
+},
+
+'PlacesBookmark' : {
+ 'concrete': True,
+},
+
+'PlacesEvent' : {
+ 'concrete': True,
+},
+
+'TaskController' : {
+ 'nativeType' : 'mozilla::dom::WebTaskController',
+ 'headerFile' : 'mozilla/dom/WebTaskController.h'
+},
+
+'TransceiverImpl': {
+ 'nativeType': 'mozilla::TransceiverImpl',
+ 'headerFile': 'TransceiverImpl.h'
+},
+
+'TransformStreamDefaultController': {
+ 'implicitJSContext': ['terminate'],
+},
+
+'Plugin': {
+ 'headerFile' : 'nsPluginArray.h',
+ 'nativeType': 'nsPluginElement',
+},
+
+'PluginArray': {
+ 'nativeType': 'nsPluginArray',
+},
+
+'PluginTag': {
+ 'nativeType': 'nsIPluginTag',
+},
+
+'Policy': {
+ 'nativeType': 'mozilla::dom::FeaturePolicy',
+},
+
+'PromiseNativeHandler': {
+ 'wrapperCache': False,
+},
+
+'PushEvent': {
+ 'headerFile': 'ServiceWorkerEvents.h',
+},
+
+'PushMessageData': {
+ 'headerFile': 'ServiceWorkerEvents.h',
+},
+
+'Range': {
+ 'nativeType': 'nsRange',
+},
+
+# Bug 1734174: We should validate ReadableStream usage of implicitJSContext.
+'ReadableByteStreamController': {
+ 'implicitJSContext': ['byobRequest', 'close', 'enqueue'],
+},
+
+'ReadableStream': {
+ 'implicitJSContext': ['tee'],
+},
+
+'ReadableStreamBYOBRequest': {
+ 'implicitJSContext': ['respond', 'respondWithNewView'],
+},
+
+'ReadableStreamDefaultController': {
+ 'implicitJSContext': ['close'],
+},
+
+'Request': {
+ 'implicitJSContext': [ 'arrayBuffer', 'blob', 'formData', 'json', 'text' ],
+},
+
+'ResizeObserverEntry': {
+ 'nativeType': 'mozilla::dom::ResizeObserverEntry',
+ 'headerFile': 'mozilla/dom/ResizeObserver.h',
+},
+
+'ResizeObserverSize': {
+ 'nativeType': 'mozilla::dom::ResizeObserverSize',
+ 'headerFile': 'mozilla/dom/ResizeObserver.h',
+},
+
+'Response': {
+ 'implicitJSContext': [ 'arrayBuffer', 'blob', 'body', 'formData', 'json', 'text',
+ 'clone', 'cloneUnfiltered' ],
+},
+
+'RTCDataChannel': {
+ 'nativeType': 'nsDOMDataChannel',
+},
+
+'Scheduler': {
+ 'nativeType': 'mozilla::dom::WebTaskScheduler',
+ 'headerFile': 'mozilla/dom/WebTaskScheduler.h',
+},
+
+'Screen': {
+ 'nativeType': 'nsScreen',
+},
+
+'ServiceWorkerGlobalScope': {
+ 'headerFile': 'mozilla/dom/WorkerScope.h',
+},
+
+'ServiceWorkerRegistration': {
+ 'implicitJSContext': [ 'pushManager' ],
+},
+
+'ShadowRealmGlobalScope': {
+ 'hasOrdinaryObjectPrototype': True,
+},
+
+'SharedWorkerGlobalScope': {
+ 'headerFile': 'mozilla/dom/WorkerScope.h',
+},
+
+'StreamFilter': {
+ 'nativeType': 'mozilla::extensions::StreamFilter',
+},
+
+'StreamFilterDataEvent': {
+ 'nativeType': 'mozilla::extensions::StreamFilterDataEvent',
+ 'headerFile': 'mozilla/extensions/StreamFilterEvents.h',
+},
+
+'StructuredCloneHolder': {
+ 'nativeType': 'mozilla::dom::StructuredCloneBlob',
+ 'wrapperCache': False,
+},
+
+'StyleSheet': {
+ 'nativeType': 'mozilla::StyleSheet',
+ 'headerFile': 'mozilla/StyleSheetInlines.h',
+},
+
+'SVGAnimatedAngle': {
+ 'nativeType': 'mozilla::dom::DOMSVGAnimatedAngle',
+ 'headerFile': 'DOMSVGAnimatedAngle.h',
+},
+
+'SVGAnimatedBoolean': {
+ 'nativeType': 'mozilla::dom::DOMSVGAnimatedBoolean',
+ 'headerFile': 'DOMSVGAnimatedBoolean.h',
+},
+
+'SVGAnimatedEnumeration': {
+ 'nativeType': 'mozilla::dom::DOMSVGAnimatedEnumeration',
+ 'headerFile': 'DOMSVGAnimatedEnumeration.h',
+},
+
+'SVGAnimatedInteger': {
+ 'nativeType': 'mozilla::dom::DOMSVGAnimatedInteger',
+ 'headerFile': 'DOMSVGAnimatedInteger.h',
+},
+
+'SVGAnimatedPreserveAspectRatio': {
+ 'nativeType': 'mozilla::dom::DOMSVGAnimatedPreserveAspectRatio',
+ 'headerFile': 'SVGAnimatedPreserveAspectRatio.h'
+},
+
+'SVGAnimatedLength': {
+ 'nativeType': 'mozilla::dom::DOMSVGAnimatedLength',
+ 'headerFile': 'DOMSVGAnimatedLength.h',
+},
+
+'SVGAnimatedLengthList': {
+ 'nativeType': 'mozilla::dom::DOMSVGAnimatedLengthList',
+ 'headerFile': 'DOMSVGAnimatedLengthList.h',
+},
+
+'SVGAnimatedNumber': {
+ 'nativeType': 'mozilla::dom::DOMSVGAnimatedNumber',
+ 'headerFile': 'DOMSVGAnimatedNumber.h',
+},
+
+'SVGAnimatedNumberList': {
+ 'nativeType': 'mozilla::dom::DOMSVGAnimatedNumberList',
+ 'headerFile': 'DOMSVGAnimatedNumberList.h'
+},
+
+'SVGAnimatedString': {
+ 'nativeType': 'mozilla::dom::DOMSVGAnimatedString',
+ 'headerFile': 'DOMSVGAnimatedString.h',
+},
+
+'SVGAnimatedTransformList': {
+ 'nativeType': 'mozilla::dom::DOMSVGAnimatedTransformList',
+ 'headerFile': 'DOMSVGAnimatedTransformList.h'
+},
+
+'SVGAngle': {
+ 'nativeType': 'mozilla::dom::DOMSVGAngle',
+ 'headerFile': 'DOMSVGAngle.h'
+},
+
+'SVGElement': {
+ 'concrete': True,
+},
+
+'SVGFEFuncAElement': {
+ 'headerFile': 'mozilla/dom/SVGComponentTransferFunctionElement.h',
+},
+
+'SVGFEFuncBElement': {
+ 'headerFile': 'mozilla/dom/SVGComponentTransferFunctionElement.h',
+},
+
+'SVGFEFuncGElement': {
+ 'headerFile': 'mozilla/dom/SVGComponentTransferFunctionElement.h',
+},
+
+'SVGFEFuncRElement': {
+ 'headerFile': 'mozilla/dom/SVGComponentTransferFunctionElement.h',
+},
+
+'SVGLength': {
+ 'nativeType': 'mozilla::dom::DOMSVGLength',
+ 'headerFile': 'DOMSVGLength.h'
+},
+
+'SVGLengthList': {
+ 'nativeType': 'mozilla::dom::DOMSVGLengthList',
+ 'headerFile': 'DOMSVGLengthList.h'
+},
+
+'SVGLinearGradientElement': {
+ 'headerFile': 'mozilla/dom/SVGGradientElement.h',
+},
+
+'SVGNumber': {
+ 'nativeType': 'mozilla::dom::DOMSVGNumber',
+ 'headerFile': 'DOMSVGNumber.h',
+},
+
+'SVGNumberList': {
+ 'nativeType': 'mozilla::dom::DOMSVGNumberList',
+ 'headerFile': 'DOMSVGNumberList.h'
+},
+
+'SVGPathSeg': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSeg',
+ 'headerFile': 'DOMSVGPathSeg.h',
+},
+
+'SVGPathSegClosePath': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSegClosePath',
+ 'headerFile': 'DOMSVGPathSeg.h'
+},
+
+'SVGPathSegMovetoAbs': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSegMovetoAbs',
+ 'headerFile': 'DOMSVGPathSeg.h'
+},
+
+'SVGPathSegMovetoRel': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSegMovetoRel',
+ 'headerFile': 'DOMSVGPathSeg.h'
+},
+
+'SVGPathSegLinetoAbs': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSegLinetoAbs',
+ 'headerFile': 'DOMSVGPathSeg.h'
+},
+
+'SVGPathSegLinetoRel': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSegLinetoRel',
+ 'headerFile': 'DOMSVGPathSeg.h'
+},
+
+'SVGPathSegCurvetoCubicAbs': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSegCurvetoCubicAbs',
+ 'headerFile': 'DOMSVGPathSeg.h'
+},
+
+'SVGPathSegCurvetoCubicRel': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSegCurvetoCubicRel',
+ 'headerFile': 'DOMSVGPathSeg.h'
+},
+
+'SVGPathSegCurvetoQuadraticAbs': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSegCurvetoQuadraticAbs',
+ 'headerFile': 'DOMSVGPathSeg.h'
+},
+
+'SVGPathSegCurvetoQuadraticRel': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSegCurvetoQuadraticRel',
+ 'headerFile': 'DOMSVGPathSeg.h'
+},
+
+'SVGPathSegArcAbs': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSegArcAbs',
+ 'headerFile': 'DOMSVGPathSeg.h'
+},
+
+'SVGPathSegArcRel': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSegArcRel',
+ 'headerFile': 'DOMSVGPathSeg.h'
+},
+
+'SVGPathSegLinetoHorizontalAbs': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSegLinetoHorizontalAbs',
+ 'headerFile': 'DOMSVGPathSeg.h'
+},
+
+'SVGPathSegLinetoHorizontalRel': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSegLinetoHorizontalRel',
+ 'headerFile': 'DOMSVGPathSeg.h'
+},
+
+'SVGPathSegLinetoVerticalAbs': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSegLinetoVerticalAbs',
+ 'headerFile': 'DOMSVGPathSeg.h'
+},
+
+'SVGPathSegLinetoVerticalRel': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSegLinetoVerticalRel',
+ 'headerFile': 'DOMSVGPathSeg.h'
+},
+
+'SVGPathSegCurvetoCubicSmoothAbs': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSegCurvetoCubicSmoothAbs',
+ 'headerFile': 'DOMSVGPathSeg.h'
+},
+
+'SVGPathSegCurvetoCubicSmoothRel': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSegCurvetoCubicSmoothRel',
+ 'headerFile': 'DOMSVGPathSeg.h'
+},
+
+'SVGPathSegCurvetoQuadraticSmoothAbs': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSegCurvetoQuadraticSmoothAbs',
+ 'headerFile': 'DOMSVGPathSeg.h'
+},
+
+'SVGPathSegCurvetoQuadraticSmoothRel': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSegCurvetoQuadraticSmoothRel',
+ 'headerFile': 'DOMSVGPathSeg.h'
+},
+
+'SVGPathSegList': {
+ 'nativeType': 'mozilla::dom::DOMSVGPathSegList',
+ 'headerFile': 'DOMSVGPathSegList.h'
+},
+
+'SVGPoint': {
+ 'nativeType': 'mozilla::dom::DOMSVGPoint',
+ 'headerFile': 'DOMSVGPoint.h'
+},
+
+'SVGPointList': {
+ 'nativeType': 'mozilla::dom::DOMSVGPointList',
+ 'headerFile': 'DOMSVGPointList.h'
+},
+
+'SVGPreserveAspectRatio': {
+ 'nativeType': 'mozilla::dom::DOMSVGPreserveAspectRatio',
+ 'headerFile': 'SVGPreserveAspectRatio.h'
+},
+
+'SVGRadialGradientElement': {
+ 'headerFile': 'mozilla/dom/SVGGradientElement.h',
+},
+
+'SVGStringList': {
+ 'nativeType': 'mozilla::dom::DOMSVGStringList',
+ 'headerFile': 'DOMSVGStringList.h',
+},
+
+'SVGTransform': {
+ 'nativeType': 'mozilla::dom::DOMSVGTransform',
+ 'headerFile': 'DOMSVGTransform.h',
+},
+
+'SVGTransformList': {
+ 'nativeType': 'mozilla::dom::DOMSVGTransformList',
+ 'headerFile': 'DOMSVGTransformList.h'
+},
+
+'SVGUnitTypes' : {
+ # Maybe should be a namespace.
+ 'concrete': False,
+},
+
+'SVGZoomAndPan' : {
+ # Part of a kinda complicated legacy setup for putting some constants on
+ # both interfaces and this thing, which ideally should be a namespace.
+ 'concrete': False,
+},
+
+'SyncReadFile': {
+ 'headerFile': 'mozilla/dom/IOUtils.h',
+},
+
+'TelemetryStopwatch': {
+ 'nativeType': 'mozilla::telemetry::Stopwatch',
+},
+
+'TestFunctions': {
+ 'wrapperCache': False
+},
+
+'Text': {
+ # Total hack to allow binding code to realize that nsTextNode can
+ # in fact be cast to Text.
+ 'headerFile': 'nsTextNode.h',
+},
+
+'TextDecoder': {
+ 'wrapperCache': False
+},
+
+'TextEncoder': {
+ 'wrapperCache': False
+},
+
+'TextMetrics': {
+ 'wrapperCache': False
+},
+
+'TouchList': {
+ 'headerFile': 'mozilla/dom/TouchEvent.h',
+},
+
+'TreeColumn': {
+ 'nativeType': 'nsTreeColumn',
+ 'headerFile': 'nsTreeColumns.h',
+},
+
+'TreeColumns': {
+ 'nativeType': 'nsTreeColumns',
+},
+
+'TreeContentView': {
+ 'nativeType': 'nsTreeContentView',
+},
+
+'TreeWalker': {
+ 'wrapperCache': False,
+},
+
+'UserInteraction': {
+ 'nativeType': 'mozilla::telemetry::UserInteractionStopwatch',
+ 'headerFile': 'mozilla/telemetry/Stopwatch.h',
+},
+
+'VisualViewport': {
+ 'nativeType': 'mozilla::dom::VisualViewport',
+},
+
+'VTTCue': {
+ 'nativeType': 'mozilla::dom::TextTrackCue'
+},
+
+'VTTRegion': {
+ 'nativeType': 'mozilla::dom::TextTrackRegion',
+},
+
+'WebExtensionContentScript': {
+ 'nativeType': 'mozilla::extensions::WebExtensionContentScript',
+},
+
+'WebExtensionPolicy': {
+ 'nativeType': 'mozilla::extensions::WebExtensionPolicy',
+},
+
+'WindowClient': {
+ 'nativeType': 'mozilla::dom::Client',
+},
+
+'WindowGlobalChild': {
+ 'implicitJSContext': ['getActor'],
+},
+
+'WindowGlobalParent': {
+ 'implicitJSContext': ['getActor'],
+},
+
+'WebGLActiveInfo': {
+ 'nativeType': 'mozilla::WebGLActiveInfoJS',
+ 'headerFile': 'ClientWebGLContext.h',
+ 'wrapperCache': False
+},
+
+'WebGLBuffer': {
+ 'nativeType': 'mozilla::WebGLBufferJS',
+ 'headerFile': 'ClientWebGLContext.h'
+},
+
+'EXT_float_blend': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionFloatBlend',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'EXT_texture_compression_bptc': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionCompressedTextureBPTC',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'EXT_texture_compression_rgtc': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionCompressedTextureRGTC',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'EXT_texture_norm16': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionTextureNorm16',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'OES_fbo_render_mipmap': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionFBORenderMipmap',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'OVR_multiview2': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionMultiview',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'WEBGL_compressed_texture_astc': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionCompressedTextureASTC',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'WEBGL_compressed_texture_etc': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionCompressedTextureES3',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'WEBGL_compressed_texture_etc1': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionCompressedTextureETC1',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'WEBGL_compressed_texture_pvrtc': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionCompressedTexturePVRTC',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'WEBGL_compressed_texture_s3tc': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionCompressedTextureS3TC',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'WEBGL_compressed_texture_s3tc_srgb': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionCompressedTextureS3TC_SRGB',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'WEBGL_depth_texture': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionDepthTexture',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'WEBGL_debug_renderer_info': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionDebugRendererInfo',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'WEBGL_debug_shaders': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionDebugShaders',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'WEBGL_explicit_present': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionExplicitPresent',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'WEBGL_provoking_vertex': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionProvokingVertex',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'OES_draw_buffers_indexed': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionDrawBuffersIndexed',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'OES_element_index_uint': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionElementIndexUint',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'EXT_frag_depth': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionFragDepth',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'WEBGL_lose_context': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionLoseContext',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'EXT_sRGB': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionSRGB',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'OES_standard_derivatives': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionStandardDerivatives',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'EXT_shader_texture_lod': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionShaderTextureLod',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'EXT_texture_filter_anisotropic': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionTextureFilterAnisotropic',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'OES_texture_float': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionTextureFloat',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'OES_texture_float_linear': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionTextureFloatLinear',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'OES_texture_half_float': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionTextureHalfFloat',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'OES_texture_half_float_linear': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionTextureHalfFloatLinear',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'WEBGL_color_buffer_float': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionColorBufferFloat',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'EXT_color_buffer_half_float': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionColorBufferHalfFloat',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'EXT_color_buffer_float': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionEXTColorBufferFloat',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'WEBGL_draw_buffers': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionDrawBuffers',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'OES_vertex_array_object': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionVertexArray',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'ANGLE_instanced_arrays': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionInstancedArrays',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'EXT_blend_minmax': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionBlendMinMax',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'EXT_disjoint_timer_query': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionDisjointTimerQuery',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'MOZ_debug': {
+ 'nativeType': 'mozilla::ClientWebGLExtensionMOZDebug',
+ 'headerFile': 'ClientWebGLExtensions.h'
+},
+
+'WebGLFramebuffer': {
+ 'nativeType': 'mozilla::WebGLFramebufferJS',
+ 'headerFile': 'ClientWebGLContext.h'
+},
+
+'WebGLProgram': {
+ 'nativeType': 'mozilla::WebGLProgramJS',
+ 'headerFile': 'ClientWebGLContext.h'
+},
+
+'WebGLQuery': {
+ 'nativeType': 'mozilla::WebGLQueryJS',
+ 'headerFile': 'ClientWebGLContext.h'
+},
+
+'WebGLRenderbuffer': {
+ 'nativeType': 'mozilla::WebGLRenderbufferJS',
+ 'headerFile': 'ClientWebGLContext.h'
+},
+
+'WebGLRenderingContext': {
+ 'nativeType': 'mozilla::ClientWebGLContext',
+ 'headerFile': 'ClientWebGLContext.h',
+},
+
+'WebGL2RenderingContext': {
+ 'nativeType': 'mozilla::ClientWebGLContext',
+ 'headerFile': 'ClientWebGLContext.h',
+},
+
+'WebGLSampler': {
+ 'nativeType': 'mozilla::WebGLSamplerJS',
+ 'headerFile': 'ClientWebGLContext.h'
+},
+
+'WebGLShader': {
+ 'nativeType': 'mozilla::WebGLShaderJS',
+ 'headerFile': 'ClientWebGLContext.h'
+},
+
+'WebGLShaderPrecisionFormat': {
+ 'nativeType': 'mozilla::WebGLShaderPrecisionFormatJS',
+ 'headerFile': 'ClientWebGLContext.h',
+ 'wrapperCache': False
+},
+
+'WebGLSync': {
+ 'nativeType': 'mozilla::WebGLSyncJS',
+ 'headerFile': 'ClientWebGLContext.h'
+},
+
+'WebGLTexture': {
+ 'nativeType': 'mozilla::WebGLTextureJS',
+ 'headerFile': 'ClientWebGLContext.h'
+},
+
+'WebGLTransformFeedback': {
+ 'nativeType': 'mozilla::WebGLTransformFeedbackJS',
+ 'headerFile': 'ClientWebGLContext.h'
+},
+
+'WebGLUniformLocation': {
+ 'nativeType': 'mozilla::WebGLUniformLocationJS',
+ 'headerFile': 'ClientWebGLContext.h'
+},
+
+'WebGLVertexArrayObject': {
+ 'nativeType': 'mozilla::WebGLVertexArrayJS',
+ 'headerFile': 'ClientWebGLContext.h'
+},
+
+# WebGPU
+
+'GPU': {
+ 'nativeType': 'mozilla::webgpu::Instance',
+},
+'GPUAdapter': {
+ 'nativeType': 'mozilla::webgpu::Adapter',
+},
+'GPUAdapterInfo': {
+ 'nativeType': 'mozilla::webgpu::AdapterInfo',
+ 'headerFile': 'mozilla/webgpu/Adapter.h',
+ 'wrapperCache': False
+},
+'GPUBindGroup': {
+ 'nativeType': 'mozilla::webgpu::BindGroup',
+},
+'GPUBindGroupLayout': {
+ 'nativeType': 'mozilla::webgpu::BindGroupLayout',
+},
+'GPUBuffer': {
+ 'nativeType': 'mozilla::webgpu::Buffer',
+ 'implicitJSContext': [ 'unmap', 'destroy' ],
+},
+'GPUCanvasContext': {
+ 'nativeType': 'mozilla::webgpu::CanvasContext',
+},
+'GPUCommandBuffer': {
+ 'nativeType': 'mozilla::webgpu::CommandBuffer',
+},
+'GPUCommandEncoder': {
+ 'nativeType': 'mozilla::webgpu::CommandEncoder',
+},
+'GPUCompilationInfo': {
+ 'nativeType': 'mozilla::webgpu::CompilationInfo',
+},
+'GPUCompilationMessage': {
+ 'nativeType': 'mozilla::webgpu::CompilationMessage',
+},
+'GPUComputePassEncoder': {
+ 'nativeType': 'mozilla::webgpu::ComputePassEncoder',
+},
+'GPUComputePipeline': {
+ 'nativeType': 'mozilla::webgpu::ComputePipeline',
+},
+'GPUDevice': {
+ 'nativeType': 'mozilla::webgpu::Device',
+},
+'GPUDeviceLostInfo': {
+ 'nativeType': 'mozilla::webgpu::DeviceLostInfo',
+},
+'GPUOutOfMemoryError': {
+ 'nativeType': 'mozilla::webgpu::OutOfMemoryError',
+},
+'GPUPipelineLayout': {
+ 'nativeType': 'mozilla::webgpu::PipelineLayout',
+},
+'GPUQuerySet': {
+ 'nativeType': 'mozilla::webgpu::QuerySet',
+},
+'GPUQueue': {
+ 'nativeType': 'mozilla::webgpu::Queue',
+},
+'GPURenderBundle': {
+ 'nativeType': 'mozilla::webgpu::RenderBundle',
+},
+'GPURenderBundleEncoder': {
+ 'nativeType': 'mozilla::webgpu::RenderBundleEncoder',
+},
+'GPURenderPassEncoder': {
+ 'nativeType': 'mozilla::webgpu::RenderPassEncoder',
+},
+'GPURenderPipeline': {
+ 'nativeType': 'mozilla::webgpu::RenderPipeline',
+},
+'GPUSampler': {
+ 'nativeType': 'mozilla::webgpu::Sampler',
+},
+'GPUShaderModule': {
+ 'nativeType': 'mozilla::webgpu::ShaderModule',
+},
+'GPUSupportedFeatures': {
+ 'nativeType': 'mozilla::webgpu::SupportedFeatures',
+},
+'GPUSupportedLimits': {
+ 'nativeType': 'mozilla::webgpu::SupportedLimits',
+},
+'GPUTexture': {
+ 'nativeType': 'mozilla::webgpu::Texture',
+},
+'GPUTextureView': {
+ 'nativeType': 'mozilla::webgpu::TextureView',
+},
+'GPUValidationError': {
+ 'nativeType': 'mozilla::webgpu::ValidationError',
+},
+
+'GPUBindingType': {
+ 'concrete': False,
+},
+'GPUBlendFactor': {
+ 'concrete': False,
+},
+'GPUBlendOperation': {
+ 'concrete': False,
+},
+'GPUBufferUsage': {
+ 'concrete': False,
+},
+'GPUColorWrite': {
+ 'concrete': False,
+},
+'GPUCompareFunction': {
+ 'concrete': False,
+},
+'GPUFilterMode': {
+ 'concrete': False,
+},
+'GPUIndexFormat': {
+ 'concrete': False,
+},
+'GPUInputStepMode': {
+ 'concrete': False,
+},
+'GPULoadOp': {
+ 'concrete': False,
+},
+'GPUMapMode': {
+ 'concrete': False,
+},
+'GPUPrimitiveTopology': {
+ 'concrete': False,
+},
+'GPUShaderStage': {
+ 'concrete': False,
+},
+'GPUStencilOperation': {
+ 'concrete': False,
+},
+'GPUStoreOp': {
+ 'concrete': False,
+},
+'GPUTextureDimension': {
+ 'concrete': False,
+},
+'GPUTextureFormat': {
+ 'concrete': False,
+},
+'GPUTextureUsage': {
+ 'concrete': False,
+},
+'GPUVertexFormat': {
+ 'concrete': False,
+},
+
+# Glean
+
+'GleanImpl': {
+ 'nativeType': 'mozilla::glean::Glean',
+ 'headerFile': 'mozilla/glean/bindings/Glean.h',
+},
+'GleanCategory': {
+ 'nativeType': 'mozilla::glean::Category',
+ 'headerFile': 'mozilla/glean/bindings/Category.h',
+},
+'GleanPingsImpl': {
+ 'nativeType': 'mozilla::glean::GleanPings',
+ 'headerFile': 'mozilla/glean/bindings/GleanPings.h',
+},
+'GleanLabeled': {
+ 'nativeType': 'mozilla::glean::GleanLabeled',
+ 'headerFile': 'mozilla/glean/bindings/Labeled.h',
+},
+
+# WebRTC
+
+'WebrtcGlobalInformation': {
+ 'nativeType': 'mozilla::dom::WebrtcGlobalInformation',
+ 'headerFile': 'WebrtcGlobalInformation.h',
+},
+
+'Window': {
+ 'nativeType': 'nsGlobalWindowInner',
+ 'headerFile': 'nsGlobalWindow.h',
+ 'implicitJSContext': [
+ 'requestIdleCallback', 'indexedDB'
+ ],
+},
+
+'WindowContext': {
+ 'concrete': True
+},
+
+'WindowProxy': {
+ 'nativeType': 'mozilla::dom::WindowProxyHolder',
+ 'headerFile': 'mozilla/dom/WindowProxyHolder.h',
+ 'concrete': False
+},
+
+'WindowRoot': {
+ 'nativeType': 'nsWindowRoot'
+},
+
+'WorkerDebuggerGlobalScope': {
+ 'headerFile': 'mozilla/dom/WorkerScope.h',
+ 'implicitJSContext': [
+ 'dump', 'clearConsoleEvents', 'reportError', 'setConsoleEventHandler',
+ ],
+},
+
+'WorkerGlobalScope': {
+ 'headerFile': 'mozilla/dom/WorkerScope.h',
+ 'implicitJSContext': [ 'importScripts', 'indexedDB' ],
+},
+
+'Worklet': {
+ # Paint worklets just use the Worklet interface.
+ 'concrete': True,
+ 'implicitJSContext': [ 'addModule' ],
+},
+
+# Bug 1734174: We should validate ReadableStream usage of implicitJSContext.
+'WritableStream': {
+ 'implicitJSContext': ['close'],
+},
+
+'WritableStreamDefaultWriter': {
+ 'implicitJSContext': ['close', 'releaseLock'],
+},
+
+'XMLSerializer': {
+ 'nativeType': 'nsDOMSerializer',
+ 'wrapperCache': False
+},
+
+'XPathEvaluator': {
+ 'wrapperCache': False,
+ 'concrete': True,
+},
+
+'XPathExpression': {
+ 'wrapperCache': False,
+},
+
+'XRPose': {
+ 'concrete': True,
+},
+
+'XRReferenceSpace': {
+ 'concrete': True,
+},
+
+'XRSpace': {
+ 'concrete': True,
+},
+
+'XSLTProcessor': {
+ 'nativeType': 'txMozillaXSLTProcessor',
+},
+
+'XULElement': {
+ 'nativeType': 'nsXULElement',
+},
+
+# WebExtension API
+
+'ExtensionBrowser': {
+ 'headerFile': 'mozilla/extensions/ExtensionBrowser.h',
+ 'nativeType': 'mozilla::extensions::ExtensionBrowser',
+},
+
+'ExtensionMockAPI': {
+ 'headerFile': 'mozilla/extensions/ExtensionMockAPI.h',
+ 'nativeType': 'mozilla::extensions::ExtensionMockAPI',
+},
+
+'ExtensionBrowserSettings': {
+ 'headerFile': 'mozilla/extensions/ExtensionBrowserSettings.h',
+ 'nativeType': 'mozilla::extensions::ExtensionBrowserSettings',
+},
+
+'ExtensionBrowserSettingsColorManagement': {
+ 'headerFile': 'mozilla/extensions/ExtensionBrowserSettingsColorManagement.h',
+ 'nativeType': 'mozilla::extensions::ExtensionBrowserSettingsColorManagement',
+},
+
+'ExtensionDns': {
+ 'headerFile': 'mozilla/extensions/ExtensionDns.h',
+ 'nativeType': 'mozilla::extensions::ExtensionDns',
+},
+
+'ExtensionEventManager': {
+ 'headerFile': 'mozilla/extensions/ExtensionEventManager.h',
+ 'nativeType': 'mozilla::extensions::ExtensionEventManager',
+},
+
+'ExtensionPort': {
+ 'headerFile': 'mozilla/extensions/ExtensionPort.h',
+ 'nativeType': 'mozilla::extensions::ExtensionPort',
+},
+
+'ExtensionProxy': {
+ 'headerFile': 'mozilla/extensions/ExtensionProxy.h',
+ 'nativeType': 'mozilla::extensions::ExtensionProxy',
+},
+
+'ExtensionRuntime': {
+ 'headerFile': 'mozilla/extensions/ExtensionRuntime.h',
+ 'nativeType': 'mozilla::extensions::ExtensionRuntime',
+},
+
+'ExtensionScripting': {
+ 'headerFile': 'mozilla/extensions/ExtensionScripting.h',
+ 'nativeType': 'mozilla::extensions::ExtensionScripting',
+},
+
+'ExtensionSetting': {
+ 'headerFile': 'mozilla/extensions/ExtensionSetting.h',
+ 'nativeType': 'mozilla::extensions::ExtensionSetting',
+},
+
+'ExtensionTest': {
+ 'headerFile': 'mozilla/extensions/ExtensionTest.h',
+ 'nativeType': 'mozilla::extensions::ExtensionTest',
+},
+
+'ExtensionAlarms': {
+ 'headerFile': 'mozilla/extensions/ExtensionAlarms.h',
+ 'nativeType': 'mozilla::extensions::ExtensionAlarms',
+},
+
+####################################
+# Test Interfaces of various sorts #
+####################################
+
+'TestInterface' : {
+ # Keep this in sync with TestExampleInterface
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestParentInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestChildInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestCImplementedInterface' : {
+ 'headerFile': 'TestCImplementedInterface.h',
+ 'register': False,
+ },
+
+'TestCImplementedInterface2' : {
+ 'headerFile': 'TestCImplementedInterface.h',
+ 'register': False,
+ },
+
+'TestJSImplInterface' : {
+ # Keep this in sync with TestExampleInterface
+ 'headerFile': 'TestJSImplGenBinding.h',
+ 'register': False,
+ },
+
+'TestJSImplInterface2' : {
+ 'headerFile': 'TestJSImplGenBinding.h',
+ 'register': False
+ },
+
+'TestJSImplInterface3' : {
+ 'headerFile': 'TestJSImplGenBinding.h',
+ 'register': False
+ },
+
+'TestJSImplInterface4' : {
+ 'headerFile': 'TestJSImplGenBinding.h',
+ 'register': False
+ },
+
+'TestJSImplInterface5' : {
+ 'headerFile': 'TestJSImplGenBinding.h',
+ 'register': False
+ },
+
+'TestJSImplInterface6' : {
+ 'headerFile': 'TestJSImplGenBinding.h',
+ 'register': False
+ },
+
+'TestNavigator' : {
+ 'headerFile': 'TestJSImplGenBinding.h',
+ 'register' : False
+ },
+
+'TestNavigatorWithConstructor' : {
+ 'headerFile': 'TestJSImplGenBinding.h',
+ 'register' : False
+ },
+
+'TestExternalInterface' : {
+ 'nativeType': 'mozilla::dom::TestExternalInterface',
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False
+ },
+
+'TestNonWrapperCacheInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ 'wrapperCache': False
+ },
+
+'IndirectlyImplementedInterface': {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ 'castable': False,
+ },
+
+'OnlyForUseInConstructor' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False
+ },
+
+'ImplementedInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'ImplementedInterfaceParent' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False
+ },
+
+'DiamondImplements' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False
+ },
+
+'DiamondBranch1A' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False
+ },
+
+'DiamondBranch1B' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False
+ },
+
+'DiamondBranch2A' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False
+ },
+
+'DiamondBranch2B' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False
+ },
+
+'TestIndexedGetterInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False
+ },
+
+'TestNamedGetterInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False
+ },
+
+'TestIndexedGetterAndSetterAndNamedGetterInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False
+ },
+
+'TestIndexedAndNamedGetterInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False
+ },
+
+'TestIndexedSetterInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False
+ },
+
+'TestNamedSetterInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False
+ },
+
+'TestIndexedAndNamedSetterInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False
+ },
+
+'TestIndexedAndNamedGetterAndSetterInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestRenamedInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ 'nativeType': 'nsRenamedInterface'
+ },
+
+'TestNamedDeleterInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False
+ },
+
+'TestNamedDeleterWithRetvalInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False
+ },
+
+'TestCppKeywordNamedMethodsInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False
+ },
+
+'TestExampleInterface' : {
+ # Keep this in sync with TestInterface
+ 'headerFile': 'TestExampleInterface-example.h',
+ 'register': False,
+ },
+
+'TestExampleWorkerInterface' : {
+ 'headerFile': 'TestExampleWorkerInterface-example.h',
+ 'register': False,
+ },
+
+'TestExampleProxyInterface' : {
+ 'headerFile': 'TestExampleProxyInterface-example.h',
+ 'register': False
+ },
+
+'TestExampleThrowingConstructorInterface' : {
+ 'headerFile': 'TestExampleThrowingConstructorInterface-example.h',
+ 'register': False,
+ },
+
+'TestDeprecatedInterface' : {
+ # Keep this in sync with TestExampleInterface
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False
+ },
+
+'TestInterfaceWithPromiseConstructorArg' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestSecureContextInterface' : {
+ # Keep this in sync with TestExampleInterface
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False
+ },
+
+'TestNamespace' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestRenamedNamespace' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestProtoObjectHackedNamespace' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestWorkerExposedInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestHTMLConstructorInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestThrowingConstructorInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestCEReactionsInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestAttributesOnTypes' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestPrefConstructorForInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestConstructorForPrefInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestPrefConstructorForDifferentPrefInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestConstructorForSCInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestSCConstructorForInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestConstructorForFuncInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestFuncConstructorForInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestFuncConstructorForDifferentFuncInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+
+'TestPrefChromeOnlySCFuncConstructorForInterface' : {
+ 'headerFile': 'TestBindingHeader.h',
+ 'register': False,
+ },
+}
+
+# These are temporary, until they've been converted to use new DOM bindings
+def addExternalIface(iface, nativeType=None, headerFile=None,
+ notflattened=False):
+ if iface in DOMInterfaces:
+ raise Exception('Interface declared both as WebIDL and External interface')
+ domInterface = {
+ 'concrete': False
+ }
+ if not nativeType is None:
+ domInterface['nativeType'] = nativeType
+ if not headerFile is None:
+ domInterface['headerFile'] = headerFile
+ domInterface['notflattened'] = notflattened
+ DOMInterfaces[iface] = domInterface
+
+addExternalIface('Cookie', nativeType='nsICookie',
+ headerFile='nsICookie.h', notflattened=True)
+addExternalIface('ContentSecurityPolicy', nativeType='nsIContentSecurityPolicy',
+ notflattened=True)
+addExternalIface('HitRegionOptions', nativeType='nsISupports')
+addExternalIface('imgINotificationObserver', nativeType='imgINotificationObserver')
+addExternalIface('imgIRequest', nativeType='imgIRequest', notflattened=True)
+addExternalIface('LoadContext', nativeType='nsILoadContext', notflattened=True)
+addExternalIface('LoadInfo', nativeType='nsILoadInfo',
+ headerFile='nsILoadInfo.h', notflattened=True)
+addExternalIface('XULControllers', nativeType='nsIControllers', notflattened=True)
+addExternalIface('MozObserver', nativeType='nsIObserver', notflattened=True)
+addExternalIface('MozTreeView', nativeType='nsITreeView',
+ headerFile='nsITreeView.h', notflattened=True)
+addExternalIface('MozWakeLockListener', headerFile='nsIDOMWakeLockListener.h')
+addExternalIface('nsIBrowserDOMWindow', nativeType='nsIBrowserDOMWindow',
+ notflattened=True)
+addExternalIface('nsIDOMWindowUtils', nativeType='nsIDOMWindowUtils', notflattened=True)
+addExternalIface('nsIEventTarget', nativeType='nsIEventTarget', notflattened=True)
+addExternalIface('nsIFile', nativeType='nsIFile', notflattened=True)
+addExternalIface('nsILoadGroup', nativeType='nsILoadGroup',
+ headerFile='nsILoadGroup.h', notflattened=True)
+addExternalIface('nsIMediaDevice', nativeType='nsIMediaDevice',
+ notflattened=True)
+addExternalIface('nsIPrintSettings', nativeType='nsIPrintSettings',
+ notflattened=True)
+addExternalIface('nsISelectionListener', nativeType='nsISelectionListener')
+addExternalIface('nsIStreamListener', nativeType='nsIStreamListener', notflattened=True)
+addExternalIface('nsISocketTransport', nativeType='nsISocketTransport',
+ notflattened=True)
+addExternalIface('nsITransportProvider', nativeType='nsITransportProvider')
+addExternalIface('nsITreeSelection', nativeType='nsITreeSelection',
+ notflattened=True)
+addExternalIface('nsISupports', nativeType='nsISupports')
+addExternalIface('nsIDocShell', nativeType='nsIDocShell', notflattened=True)
+addExternalIface('nsIDOMProcessChild', nativeType='nsIDOMProcessChild', notflattened=True)
+addExternalIface('nsIDOMProcessParent', nativeType='nsIDOMProcessParent', notflattened=True)
+addExternalIface('nsIReferrerInfo', nativeType='nsIReferrerInfo', notflattened=True)
+addExternalIface('nsISecureBrowserUI', nativeType='nsISecureBrowserUI', notflattened=True)
+addExternalIface('nsIWebProgress', nativeType='nsIWebProgress', notflattened=True)
+addExternalIface('nsIWebNavigation', nativeType='nsIWebNavigation', notflattened=True)
+addExternalIface('nsIEditor', nativeType='nsIEditor', notflattened=True)
+addExternalIface('nsIWebBrowserPersistDocumentReceiver',
+ nativeType='nsIWebBrowserPersistDocumentReceiver',
+ headerFile='nsIWebBrowserPersistDocument.h',
+ notflattened=True)
+addExternalIface('nsIWebProgressListener', nativeType='nsIWebProgressListener',
+ notflattened=True)
+addExternalIface('OutputStream', nativeType='nsIOutputStream',
+ notflattened=True)
+addExternalIface('Principal', nativeType='nsIPrincipal',
+ headerFile='nsIPrincipal.h', notflattened=True)
+addExternalIface('StackFrame', nativeType='nsIStackFrame',
+ headerFile='nsIException.h', notflattened=True)
+addExternalIface('RemoteTab', nativeType='nsIRemoteTab',
+ notflattened=True)
+addExternalIface('URI', nativeType='nsIURI', headerFile='nsIURI.h',
+ notflattened=True)
+addExternalIface('XULCommandDispatcher', notflattened=True)
+addExternalIface('nsISHistory', nativeType='nsISHistory', notflattened=True)
+addExternalIface('nsISHEntry', nativeType='nsISHEntry', notflattened=True)
+addExternalIface('ReferrerInfo', nativeType='nsIReferrerInfo')
+addExternalIface('nsIPermissionDelegateHandler',
+ nativeType='nsIPermissionDelegateHandler',
+ notflattened=True)
+addExternalIface('nsIOpenWindowInfo', nativeType='nsIOpenWindowInfo',
+ notflattened=True)
+addExternalIface('nsICookieJarSettings', nativeType='nsICookieJarSettings',
+ notflattened=True)
+addExternalIface('nsIGleanPing', headerFile='mozilla/glean/bindings/Ping.h',
+ nativeType='nsIGleanPing', notflattened=True)
+addExternalIface('nsISessionStoreRestoreData',
+ nativeType='nsISessionStoreRestoreData',
+ headerFile='nsISessionStoreRestoreData.h', notflattened=True)
+addExternalIface('nsIScreen', nativeType='nsIScreen',
+ headerFile='nsIScreen.h', notflattened=True)
diff --git a/dom/bindings/CallbackFunction.h b/dom/bindings/CallbackFunction.h
new file mode 100644
index 0000000000..ef0487d875
--- /dev/null
+++ b/dom/bindings/CallbackFunction.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/**
+ * A common base class for representing WebIDL callback function types in C++.
+ *
+ * This class implements common functionality like lifetime
+ * management, initialization with the callable, and setup of the call
+ * environment. Subclasses corresponding to particular callback
+ * function types should provide a Call() method that actually does
+ * the call.
+ */
+
+#ifndef mozilla_dom_CallbackFunction_h
+#define mozilla_dom_CallbackFunction_h
+
+#include "mozilla/dom/CallbackObject.h"
+
+namespace mozilla::dom {
+
+class CallbackFunction : public CallbackObject {
+ public:
+ // See CallbackObject for an explanation of the arguments.
+ explicit CallbackFunction(JSContext* aCx, JS::Handle<JSObject*> aCallable,
+ JS::Handle<JSObject*> aCallableGlobal,
+ nsIGlobalObject* aIncumbentGlobal)
+ : CallbackObject(aCx, aCallable, aCallableGlobal, aIncumbentGlobal) {}
+
+ // See CallbackObject for an explanation of the arguments.
+ explicit CallbackFunction(JSObject* aCallable, JSObject* aCallableGlobal,
+ JSObject* aAsyncStack,
+ nsIGlobalObject* aIncumbentGlobal)
+ : CallbackObject(aCallable, aCallableGlobal, aAsyncStack,
+ aIncumbentGlobal) {}
+
+ JSObject* CallableOrNull() const { return CallbackOrNull(); }
+
+ JSObject* CallablePreserveColor() const { return CallbackPreserveColor(); }
+
+ protected:
+ explicit CallbackFunction(CallbackFunction* aCallbackFunction)
+ : CallbackObject(aCallbackFunction) {}
+
+ // See CallbackObject for an explanation of the arguments.
+ CallbackFunction(JSObject* aCallable, JSObject* aCallableGlobal,
+ const FastCallbackConstructor&)
+ : CallbackObject(aCallable, aCallableGlobal, FastCallbackConstructor()) {}
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_CallbackFunction_h
diff --git a/dom/bindings/CallbackInterface.cpp b/dom/bindings/CallbackInterface.cpp
new file mode 100644
index 0000000000..b000ea22d1
--- /dev/null
+++ b/dom/bindings/CallbackInterface.cpp
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/CallbackInterface.h"
+#include "jsapi.h"
+#include "js/CallAndConstruct.h" // JS::IsCallable
+#include "js/CharacterEncoding.h"
+#include "js/PropertyAndElement.h" // JS_GetProperty, JS_GetPropertyById
+#include "mozilla/dom/BindingUtils.h"
+#include "nsPrintfCString.h"
+
+namespace mozilla::dom {
+
+bool CallbackInterface::GetCallableProperty(
+ BindingCallContext& cx, JS::Handle<jsid> aPropId,
+ JS::MutableHandle<JS::Value> aCallable) {
+ JS::Rooted<JSObject*> obj(cx, CallbackKnownNotGray());
+ if (!JS_GetPropertyById(cx, obj, aPropId, aCallable)) {
+ return false;
+ }
+ if (!aCallable.isObject() || !JS::IsCallable(&aCallable.toObject())) {
+ JS::Rooted<JSString*> propId(cx, aPropId.toString());
+ JS::UniqueChars propName = JS_EncodeStringToUTF8(cx, propId);
+ nsPrintfCString description("Property '%s'", propName.get());
+ cx.ThrowErrorMessage<MSG_NOT_CALLABLE>(description.get());
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/CallbackInterface.h b/dom/bindings/CallbackInterface.h
new file mode 100644
index 0000000000..4cb46217c2
--- /dev/null
+++ b/dom/bindings/CallbackInterface.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/**
+ * A common base class for representing WebIDL callback interface types in C++.
+ *
+ * This class implements common functionality like lifetime management,
+ * initialization with the callback object, and setup of the call environment.
+ * Subclasses corresponding to particular callback interface types should
+ * provide methods that actually do the various necessary calls.
+ */
+
+#ifndef mozilla_dom_CallbackInterface_h
+#define mozilla_dom_CallbackInterface_h
+
+#include "mozilla/dom/CallbackObject.h"
+
+namespace mozilla::dom {
+
+class CallbackInterface : public CallbackObject {
+ public:
+ // See CallbackObject for an explanation of the arguments.
+ explicit CallbackInterface(JSContext* aCx, JS::Handle<JSObject*> aCallback,
+ JS::Handle<JSObject*> aCallbackGlobal,
+ nsIGlobalObject* aIncumbentGlobal)
+ : CallbackObject(aCx, aCallback, aCallbackGlobal, aIncumbentGlobal) {}
+
+ // See CallbackObject for an explanation of the arguments.
+ explicit CallbackInterface(JSObject* aCallback, JSObject* aCallbackGlobal,
+ JSObject* aAsyncStack,
+ nsIGlobalObject* aIncumbentGlobal)
+ : CallbackObject(aCallback, aCallbackGlobal, aAsyncStack,
+ aIncumbentGlobal) {}
+
+ protected:
+ bool GetCallableProperty(BindingCallContext& cx, JS::Handle<jsid> aPropId,
+ JS::MutableHandle<JS::Value> aCallable);
+
+ // See CallbackObject for an explanation of the arguments.
+ CallbackInterface(JSObject* aCallback, JSObject* aCallbackGlobal,
+ const FastCallbackConstructor&)
+ : CallbackObject(aCallback, aCallbackGlobal, FastCallbackConstructor()) {}
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_CallbackFunction_h
diff --git a/dom/bindings/CallbackObject.cpp b/dom/bindings/CallbackObject.cpp
new file mode 100644
index 0000000000..dfd798ba23
--- /dev/null
+++ b/dom/bindings/CallbackObject.cpp
@@ -0,0 +1,433 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/CallbackObject.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "jsfriendapi.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptContext.h"
+#include "nsPIDOMWindow.h"
+#include "nsJSUtils.h"
+#include "xpcprivate.h"
+#include "WorkerPrivate.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "WorkerScope.h"
+#include "jsapi.h"
+#include "js/ContextOptions.h"
+#include "nsJSPrincipals.h"
+
+namespace mozilla::dom {
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CallbackObject)
+ NS_INTERFACE_MAP_ENTRY(mozilla::dom::CallbackObject)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(CallbackObject)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(CallbackObject)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(CallbackObject)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CallbackObject)
+ tmp->ClearJSReferences();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncumbentGlobal)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CallbackObject)
+ JSObject* callback = tmp->CallbackPreserveColor();
+
+ if (!aRemovingAllowed) {
+ // If our callback has been cleared, we can't be part of a garbage cycle.
+ return !callback;
+ }
+
+ // mCallback is always wrapped for the CallbackObject's incumbent global. In
+ // the case where the real callback is in a different compartment, we have a
+ // cross-compartment wrapper, and it will automatically be cut when its
+ // compartment is nuked. In the case where it is in the same compartment, we
+ // have a reference to the real function. Since that means there are no
+ // wrappers to cut, we need to check whether the compartment is still alive,
+ // and drop the references if it is not.
+
+ if (MOZ_UNLIKELY(!callback)) {
+ return true;
+ }
+ if (MOZ_LIKELY(tmp->mIncumbentGlobal) &&
+ MOZ_UNLIKELY(js::NukedObjectRealm(tmp->CallbackGlobalPreserveColor()))) {
+ // It's not safe to release our global reference or drop our JS objects at
+ // this point, so defer their finalization until CC is finished.
+ AddForDeferredFinalization(new JSObjectsDropper(tmp));
+ DeferredFinalize(tmp->mIncumbentGlobal.forget().take());
+ return true;
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CallbackObject)
+ return !tmp->mCallback;
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CallbackObject)
+ return !tmp->mCallback;
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CallbackObject)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncumbentGlobal)
+ // If a new member is added here, don't forget to update IsBlackForCC.
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CallbackObject)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallback)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallbackGlobal)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCreationStack)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mIncumbentJSGlobal)
+ // If a new member is added here, don't forget to update IsBlackForCC.
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+void CallbackObject::Trace(JSTracer* aTracer) {
+ JS::TraceEdge(aTracer, &mCallback, "CallbackObject.mCallback");
+ JS::TraceEdge(aTracer, &mCallbackGlobal, "CallbackObject.mCallbackGlobal");
+ JS::TraceEdge(aTracer, &mCreationStack, "CallbackObject.mCreationStack");
+ JS::TraceEdge(aTracer, &mIncumbentJSGlobal,
+ "CallbackObject.mIncumbentJSGlobal");
+}
+
+void CallbackObject::FinishSlowJSInitIfMoreThanOneOwner(JSContext* aCx) {
+ MOZ_ASSERT(mRefCnt.get() > 0);
+ if (mRefCnt.get() > 1) {
+ mozilla::HoldJSObjects(this);
+ if (JS::IsAsyncStackCaptureEnabledForRealm(aCx)) {
+ JS::Rooted<JSObject*> stack(aCx);
+ if (!JS::CaptureCurrentStack(aCx, &stack)) {
+ JS_ClearPendingException(aCx);
+ }
+ mCreationStack = stack;
+ }
+ mIncumbentGlobal = GetIncumbentGlobal();
+ if (mIncumbentGlobal) {
+ // We don't want to expose to JS here (change the color). If someone ever
+ // reads mIncumbentJSGlobal, that will expose. If not, no need to expose
+ // here.
+ mIncumbentJSGlobal = mIncumbentGlobal->GetGlobalJSObjectPreserveColor();
+ }
+ } else {
+ // We can just forget all our stuff.
+ ClearJSReferences();
+ }
+}
+
+JSObject* CallbackObject::Callback(JSContext* aCx) {
+ JSObject* callback = CallbackOrNull();
+ if (!callback) {
+ callback = JS_NewDeadWrapper(aCx);
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(callback);
+ return callback;
+}
+
+void CallbackObject::GetDescription(nsACString& aOutString) {
+ JSObject* wrappedCallback = CallbackOrNull();
+ if (!wrappedCallback) {
+ aOutString.Append("<callback from a nuked compartment>");
+ return;
+ }
+
+ JS::Rooted<JSObject*> unwrappedCallback(
+ RootingCx(), js::CheckedUnwrapStatic(wrappedCallback));
+ if (!unwrappedCallback) {
+ aOutString.Append("<not a function>");
+ return;
+ }
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JSObject*> rootedCallback(cx, unwrappedCallback);
+ JSAutoRealm ar(cx, rootedCallback);
+
+ JS::Rooted<JSFunction*> rootedFunction(cx,
+ JS_GetObjectFunction(rootedCallback));
+ if (!rootedFunction) {
+ aOutString.Append("<not a function>");
+ return;
+ }
+
+ JS::Rooted<JSString*> displayId(cx, JS_GetFunctionDisplayId(rootedFunction));
+ if (displayId) {
+ nsAutoJSString funcNameStr;
+ if (funcNameStr.init(cx, displayId)) {
+ if (funcNameStr.IsEmpty()) {
+ aOutString.Append("<empty name>");
+ } else {
+ AppendUTF16toUTF8(funcNameStr, aOutString);
+ }
+ } else {
+ aOutString.Append("<function name string failed to materialize>");
+ jsapi.ClearException();
+ }
+ } else {
+ aOutString.Append("<anonymous>");
+ }
+
+ JS::Rooted<JSScript*> rootedScript(cx,
+ JS_GetFunctionScript(cx, rootedFunction));
+ if (!rootedScript) {
+ return;
+ }
+
+ aOutString.Append(" (");
+ aOutString.Append(JS_GetScriptFilename(rootedScript));
+ aOutString.Append(":");
+ aOutString.AppendInt(JS_GetScriptBaseLineNumber(cx, rootedScript));
+ aOutString.Append(")");
+}
+
+CallbackObject::CallSetup::CallSetup(CallbackObject* aCallback,
+ ErrorResult& aRv,
+ const char* aExecutionReason,
+ ExceptionHandling aExceptionHandling,
+ JS::Realm* aRealm,
+ bool aIsJSImplementedWebIDL)
+ : mCx(nullptr),
+ mRealm(aRealm),
+ mErrorResult(aRv),
+ mExceptionHandling(aExceptionHandling),
+ mIsMainThread(NS_IsMainThread()) {
+ MOZ_ASSERT_IF(aExceptionHandling == eReportExceptions ||
+ aExceptionHandling == eRethrowExceptions,
+ !aRealm);
+
+ CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
+ if (ccjs) {
+ ccjs->EnterMicroTask();
+ }
+
+ // Compute the caller's subject principal (if necessary) early, before we
+ // do anything that might perturb the relevant state.
+ nsIPrincipal* webIDLCallerPrincipal = nullptr;
+ if (aIsJSImplementedWebIDL) {
+ webIDLCallerPrincipal =
+ nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller();
+ }
+
+ JSObject* wrappedCallback = aCallback->CallbackPreserveColor();
+ if (!wrappedCallback) {
+ aRv.ThrowNotSupportedError(
+ "Cannot execute callback from a nuked compartment.");
+ return;
+ }
+
+ nsIGlobalObject* globalObject = nullptr;
+
+ {
+ // First, find the real underlying callback.
+ JS::Rooted<JSObject*> realCallback(ccjs->RootingCx(),
+ js::UncheckedUnwrap(wrappedCallback));
+
+ // Get the global for this callback. Note that for the case of
+ // JS-implemented WebIDL we never have a window here.
+ nsGlobalWindowInner* win = mIsMainThread && !aIsJSImplementedWebIDL
+ ? xpc::WindowGlobalOrNull(realCallback)
+ : nullptr;
+ if (win) {
+ // We don't want to run script in windows that have been navigated away
+ // from.
+ if (!win->HasActiveDocument()) {
+ aRv.ThrowNotSupportedError(
+ "Refusing to execute function from window whose document is no "
+ "longer active.");
+ return;
+ }
+ globalObject = win;
+ } else {
+ // No DOM Window. Store the global.
+ globalObject = xpc::NativeGlobal(realCallback);
+ MOZ_ASSERT(globalObject);
+ }
+
+ // Make sure to use realCallback to get the global of the callback
+ // object, not the wrapper.
+ if (globalObject->IsScriptForbidden(realCallback, aIsJSImplementedWebIDL)) {
+ aRv.ThrowNotSupportedError(
+ "Refusing to execute function from global in which script is "
+ "disabled.");
+ return;
+ }
+ }
+
+ // Bail out if there's no useful global.
+ if (!globalObject->HasJSGlobal()) {
+ aRv.ThrowNotSupportedError(
+ "Refusing to execute function from global which is being torn down.");
+ return;
+ }
+
+ AutoAllowLegacyScriptExecution exemption;
+ mAutoEntryScript.emplace(globalObject, aExecutionReason, mIsMainThread);
+ mAutoEntryScript->SetWebIDLCallerPrincipal(webIDLCallerPrincipal);
+ nsIGlobalObject* incumbent = aCallback->IncumbentGlobalOrNull();
+ if (incumbent) {
+ // The callback object traces its incumbent JS global, so in general it
+ // should be alive here. However, it's possible that we could run afoul
+ // of the same IPC global weirdness described above, wherein the
+ // nsIGlobalObject has severed its reference to the JS global. Let's just
+ // be safe here, so that nobody has to waste a day debugging gaia-ui tests.
+ if (!incumbent->HasJSGlobal()) {
+ aRv.ThrowNotSupportedError(
+ "Refusing to execute function because our incumbent global is being "
+ "torn down.");
+ return;
+ }
+ mAutoIncumbentScript.emplace(incumbent);
+ }
+
+ JSContext* cx = mAutoEntryScript->cx();
+
+ // Unmark the callable (by invoking CallbackOrNull() and not the
+ // CallbackPreserveColor() variant), and stick it in a Rooted before it can
+ // go gray again.
+ // Nothing before us in this function can trigger a CC, so it's safe to wait
+ // until here it do the unmark. This allows us to construct mRootedCallable
+ // with the cx from mAutoEntryScript, avoiding the cost of finding another
+ // JSContext. (Rooted<> does not care about requests or compartments.)
+ mRootedCallable.emplace(cx, aCallback->CallbackOrNull());
+ mRootedCallableGlobal.emplace(cx, aCallback->CallbackGlobalOrNull());
+
+ mAsyncStack.emplace(cx, aCallback->GetCreationStack());
+ if (*mAsyncStack) {
+ mAsyncStackSetter.emplace(cx, *mAsyncStack, aExecutionReason);
+ }
+
+ // Enter the realm of our callback, so we can actually work with it.
+ //
+ // Note that if the callback is a wrapper, this will not be the same
+ // realm that we ended up in with mAutoEntryScript above, because the
+ // entry point is based off of the unwrapped callback (realCallback).
+ mAr.emplace(cx, *mRootedCallableGlobal);
+
+ // And now we're ready to go.
+ mCx = cx;
+
+ // We don't really have a good error message prefix to use for the
+ // BindingCallContext.
+ mCallContext.emplace(cx, nullptr);
+}
+
+bool CallbackObject::CallSetup::ShouldRethrowException(
+ JS::Handle<JS::Value> aException) {
+ if (mExceptionHandling == eRethrowExceptions) {
+ MOZ_ASSERT(!mRealm);
+ return true;
+ }
+
+ MOZ_ASSERT(mRealm);
+
+ // Now we only want to throw an exception to the caller if the object that was
+ // thrown is in the caller realm (which we stored in mRealm).
+
+ if (!aException.isObject()) {
+ return false;
+ }
+
+ JS::Rooted<JSObject*> obj(mCx, &aException.toObject());
+ obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
+ return js::GetNonCCWObjectRealm(obj) == mRealm;
+}
+
+CallbackObject::CallSetup::~CallSetup() {
+ // To get our nesting right we have to destroy our JSAutoRealm first.
+ // In particular, we want to do this before we try reporting any exceptions,
+ // so we end up reporting them while in the realm of our entry point,
+ // not whatever cross-compartment wrappper mCallback might be.
+ // Be careful: the JSAutoRealm might not have been constructed at all!
+ mAr.reset();
+
+ // Now, if we have a JSContext, report any pending errors on it, unless we
+ // were told to re-throw them.
+ if (mCx) {
+ bool needToDealWithException = mAutoEntryScript->HasException();
+ if ((mRealm && mExceptionHandling == eRethrowContentExceptions) ||
+ mExceptionHandling == eRethrowExceptions) {
+ mErrorResult.MightThrowJSException();
+ if (needToDealWithException) {
+ JS::Rooted<JS::Value> exn(mCx);
+ if (mAutoEntryScript->PeekException(&exn) &&
+ ShouldRethrowException(exn)) {
+ mAutoEntryScript->ClearException();
+ MOZ_ASSERT(!mAutoEntryScript->HasException());
+ mErrorResult.ThrowJSException(mCx, exn);
+ needToDealWithException = false;
+ }
+ }
+ }
+
+ if (needToDealWithException) {
+ // Either we're supposed to report our exceptions, or we're supposed to
+ // re-throw them but we failed to get the exception value. Either way,
+ // we'll just report the pending exception, if any, once ~mAutoEntryScript
+ // runs. Note that we've already run ~mAr, effectively, so we don't have
+ // to worry about ordering here.
+ if (mErrorResult.IsJSContextException()) {
+ // XXXkhuey bug 1117269. When this is fixed, please consider fixing
+ // ThrowExceptionValueIfSafe over in Exceptions.cpp in the same way.
+
+ // IsJSContextException shouldn't be true anymore because we will report
+ // the exception on the JSContext ... so throw something else.
+ mErrorResult.Throw(NS_ERROR_UNEXPECTED);
+ }
+ }
+ }
+
+ mAutoIncumbentScript.reset();
+ mAutoEntryScript.reset();
+
+ // It is important that this is the last thing we do, after leaving the
+ // realm and undoing all our entry/incumbent script changes
+ CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
+ if (ccjs) {
+ ccjs->LeaveMicroTask();
+ }
+}
+
+already_AddRefed<nsISupports> CallbackObjectHolderBase::ToXPCOMCallback(
+ CallbackObject* aCallback, const nsIID& aIID) const {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!aCallback) {
+ return nullptr;
+ }
+
+ // We don't init the AutoJSAPI with our callback because we don't want it
+ // reporting errors to its global's onerror handlers.
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JSObject*> callback(cx, aCallback->CallbackOrNull());
+ if (!callback) {
+ return nullptr;
+ }
+
+ JSAutoRealm ar(cx, aCallback->CallbackGlobalOrNull());
+
+ RefPtr<nsXPCWrappedJS> wrappedJS;
+ nsresult rv = nsXPCWrappedJS::GetNewOrUsed(cx, callback, aIID,
+ getter_AddRefs(wrappedJS));
+ if (NS_FAILED(rv) || !wrappedJS) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsISupports> retval;
+ rv = wrappedJS->QueryInterface(aIID, getter_AddRefs(retval));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return retval.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/CallbackObject.h b/dom/bindings/CallbackObject.h
new file mode 100644
index 0000000000..74d27886a9
--- /dev/null
+++ b/dom/bindings/CallbackObject.h
@@ -0,0 +1,664 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/**
+ * A common base class for representing WebIDL callback function and
+ * callback interface types in C++.
+ *
+ * This class implements common functionality like lifetime
+ * management, initialization with the JS object, and setup of the
+ * call environment. Subclasses are responsible for providing methods
+ * that do the call into JS as needed.
+ */
+
+#ifndef mozilla_dom_CallbackObject_h
+#define mozilla_dom_CallbackObject_h
+
+#include <cstddef>
+#include <cstdint>
+#include <utility>
+#include "js/Exception.h"
+#include "js/RootingAPI.h"
+#include "js/Wrapper.h"
+#include "jsapi.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/BindingCallContext.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsID.h"
+#include "nsIGlobalObject.h"
+#include "nsISupports.h"
+#include "nsISupportsUtils.h"
+#include "nsStringFwd.h"
+
+class JSAutoRealm;
+class JSObject;
+class JSTracer;
+class nsCycleCollectionTraversalCallback;
+struct JSContext;
+
+namespace JS {
+class AutoSetAsyncStackForNewCalls;
+class Realm;
+class Value;
+} // namespace JS
+
+namespace mozilla {
+
+class ErrorResult;
+class PromiseJobRunnable;
+template <class T>
+class OwningNonNull;
+
+namespace dom {
+
+#define DOM_CALLBACKOBJECT_IID \
+ { \
+ 0xbe74c190, 0x6d76, 0x4991, { \
+ 0x84, 0xb9, 0x65, 0x06, 0x99, 0xe6, 0x93, 0x2b \
+ } \
+ }
+
+class CallbackObject : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(DOM_CALLBACKOBJECT_IID)
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(CallbackObject)
+
+ // The caller may pass a global object which will act as an override for the
+ // incumbent script settings object when the callback is invoked (overriding
+ // the entry point computed from aCallback). If no override is required, the
+ // caller should pass null. |aCx| is used to capture the current
+ // stack, which is later used as an async parent when the callback
+ // is invoked. aCx can be nullptr, in which case no stack is
+ // captured.
+ explicit CallbackObject(JSContext* aCx, JS::Handle<JSObject*> aCallback,
+ JS::Handle<JSObject*> aCallbackGlobal,
+ nsIGlobalObject* aIncumbentGlobal) {
+ if (aCx && JS::IsAsyncStackCaptureEnabledForRealm(aCx)) {
+ JS::Rooted<JSObject*> stack(aCx);
+ if (!JS::CaptureCurrentStack(aCx, &stack)) {
+ JS_ClearPendingException(aCx);
+ }
+ Init(aCallback, aCallbackGlobal, stack, aIncumbentGlobal);
+ } else {
+ Init(aCallback, aCallbackGlobal, nullptr, aIncumbentGlobal);
+ }
+ }
+
+ // Instead of capturing the current stack to use as an async parent when the
+ // callback is invoked, the caller can use this overload to pass in a stack
+ // for that purpose.
+ explicit CallbackObject(JSObject* aCallback, JSObject* aCallbackGlobal,
+ JSObject* aAsyncStack,
+ nsIGlobalObject* aIncumbentGlobal) {
+ Init(aCallback, aCallbackGlobal, aAsyncStack, aIncumbentGlobal);
+ }
+
+ // This is guaranteed to be non-null from the time the CallbackObject is
+ // created until JavaScript has had a chance to run. It will only return null
+ // after a JavaScript caller has called nukeSandbox on a Sandbox object and
+ // the cycle collector has had a chance to run, unless Reset() is explicitly
+ // called (see below).
+ //
+ // This means that any native callee which receives a CallbackObject as an
+ // argument can safely rely on the callback being non-null so long as it
+ // doesn't trigger any scripts before it accesses it.
+ JSObject* CallbackOrNull() const {
+ mCallback.exposeToActiveJS();
+ return CallbackPreserveColor();
+ }
+
+ JSObject* CallbackGlobalOrNull() const {
+ mCallbackGlobal.exposeToActiveJS();
+ return mCallbackGlobal;
+ }
+
+ // Like CallbackOrNull(), but will return a new dead proxy object in the
+ // caller's realm if the callback is null.
+ JSObject* Callback(JSContext* aCx);
+
+ JSObject* GetCreationStack() const { return mCreationStack; }
+
+ void MarkForCC() {
+ mCallback.exposeToActiveJS();
+ mCallbackGlobal.exposeToActiveJS();
+ mCreationStack.exposeToActiveJS();
+ }
+
+ /*
+ * This getter does not change the color of the JSObject meaning that the
+ * object returned is not guaranteed to be kept alive past the next CC.
+ */
+ JSObject* CallbackPreserveColor() const { return mCallback.unbarrieredGet(); }
+ JSObject* CallbackGlobalPreserveColor() const {
+ return mCallbackGlobal.unbarrieredGet();
+ }
+
+ /*
+ * If the callback is known to be non-gray, then this method can be
+ * used instead of CallbackOrNull() to avoid the overhead of
+ * ExposeObjectToActiveJS().
+ */
+ JSObject* CallbackKnownNotGray() const {
+ JS::AssertObjectIsNotGray(mCallback);
+ return CallbackPreserveColor();
+ }
+
+ nsIGlobalObject* IncumbentGlobalOrNull() const { return mIncumbentGlobal; }
+
+ enum ExceptionHandling {
+ // Report any exception and don't throw it to the caller code.
+ eReportExceptions,
+ // Throw any exception to the caller code and don't report it.
+ eRethrowExceptions,
+ // Throw an exception to the caller code if the thrown exception is a
+ // binding object for a DOMException from the caller's scope, otherwise
+ // report it.
+ eRethrowContentExceptions
+ };
+
+ // Append a UTF-8 string to aOutString that describes the callback function,
+ // for use in logging or profiler markers.
+ // The string contains the function name and its source location, if
+ // available, in the following format:
+ // "<functionName> (<sourceURL>:<lineNumber>)"
+ void GetDescription(nsACString& aOutString);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this);
+ }
+
+ // Used for cycle collection optimization. Should return true only if all our
+ // outgoing edges are to known-live objects. In that case, there's no point
+ // traversing our edges to them, because we know they can't be collected
+ // anyway.
+ bool IsBlackForCC() const {
+ // Play it safe in case this gets called after unlink.
+ return (!mCallback || !JS::ObjectIsMarkedGray(mCallback)) &&
+ (!mCallbackGlobal || !JS::ObjectIsMarkedGray(mCallbackGlobal)) &&
+ (!mCreationStack || !JS::ObjectIsMarkedGray(mCreationStack)) &&
+ (!mIncumbentJSGlobal ||
+ !JS::ObjectIsMarkedGray(mIncumbentJSGlobal)) &&
+ // mIncumbentGlobal is known-live if we have a known-live
+ // mIncumbentJSGlobal, since mIncumbentJSGlobal will keep a ref to
+ // it. At this point if mIncumbentJSGlobal is not null, it's
+ // known-live.
+ (!mIncumbentGlobal || mIncumbentJSGlobal);
+ }
+
+ protected:
+ virtual ~CallbackObject() { mozilla::DropJSObjects(this); }
+
+ explicit CallbackObject(CallbackObject* aCallbackObject) {
+ Init(aCallbackObject->mCallback, aCallbackObject->mCallbackGlobal,
+ aCallbackObject->mCreationStack, aCallbackObject->mIncumbentGlobal);
+ }
+
+ bool operator==(const CallbackObject& aOther) const {
+ JSObject* wrappedThis = CallbackPreserveColor();
+ JSObject* wrappedOther = aOther.CallbackPreserveColor();
+ if (!wrappedThis || !wrappedOther) {
+ return this == &aOther;
+ }
+
+ JSObject* thisObj = js::UncheckedUnwrap(wrappedThis);
+ JSObject* otherObj = js::UncheckedUnwrap(wrappedOther);
+ return thisObj == otherObj;
+ }
+
+ class JSObjectsDropper final {
+ public:
+ explicit JSObjectsDropper(CallbackObject* aHolder) : mHolder(aHolder) {}
+
+ ~JSObjectsDropper() { mHolder->ClearJSObjects(); }
+
+ private:
+ RefPtr<CallbackObject> mHolder;
+ };
+
+ private:
+ inline void InitNoHold(JSObject* aCallback, JSObject* aCallbackGlobal,
+ JSObject* aCreationStack,
+ nsIGlobalObject* aIncumbentGlobal) {
+ MOZ_ASSERT(aCallback && !mCallback);
+ MOZ_ASSERT(aCallbackGlobal);
+ MOZ_DIAGNOSTIC_ASSERT(JS::GetCompartment(aCallback) ==
+ JS::GetCompartment(aCallbackGlobal));
+ MOZ_ASSERT(JS_IsGlobalObject(aCallbackGlobal));
+ mCallback = aCallback;
+ mCallbackGlobal = aCallbackGlobal;
+ mCreationStack = aCreationStack;
+ if (aIncumbentGlobal) {
+ mIncumbentGlobal = aIncumbentGlobal;
+ // We don't want to expose to JS here (change the color). If someone ever
+ // reads mIncumbentJSGlobal, that will expose. If not, no need to expose
+ // here.
+ mIncumbentJSGlobal = aIncumbentGlobal->GetGlobalJSObjectPreserveColor();
+ }
+ }
+
+ inline void Init(JSObject* aCallback, JSObject* aCallbackGlobal,
+ JSObject* aCreationStack,
+ nsIGlobalObject* aIncumbentGlobal) {
+ // Set script objects before we hold, on the off chance that a GC could
+ // somehow happen in there... (which would be pretty odd, granted).
+ InitNoHold(aCallback, aCallbackGlobal, aCreationStack, aIncumbentGlobal);
+ mozilla::HoldJSObjects(this);
+ }
+
+ // Provide a way to clear this object's pointers to GC things after the
+ // callback has been run. Note that CallbackOrNull() will return null after
+ // this point. This should only be called if the object is known not to be
+ // used again, and no handles (e.g. those returned by CallbackPreserveColor)
+ // are in use.
+ void Reset() { ClearJSReferences(); }
+ friend class mozilla::PromiseJobRunnable;
+
+ inline void ClearJSReferences() {
+ mCallback = nullptr;
+ mCallbackGlobal = nullptr;
+ mCreationStack = nullptr;
+ mIncumbentJSGlobal = nullptr;
+ }
+
+ CallbackObject(const CallbackObject&) = delete;
+ CallbackObject& operator=(const CallbackObject&) = delete;
+
+ protected:
+ void ClearJSObjects() {
+ MOZ_ASSERT_IF(mIncumbentJSGlobal, mCallback);
+ if (mCallback) {
+ ClearJSReferences();
+ }
+ }
+
+ // For use from subclasses that want to be usable with Rooted.
+ void Trace(JSTracer* aTracer);
+
+ // For use from subclasses that want to be traced for a bit then possibly
+ // switch to HoldJSObjects and do other slow JS-related init work we might do.
+ // If we have more than one owner, this will HoldJSObjects and do said slow
+ // init work; otherwise it will just forget all our JS references.
+ void FinishSlowJSInitIfMoreThanOneOwner(JSContext* aCx);
+
+ // Struct used as a way to force a CallbackObject constructor to not call
+ // HoldJSObjects. We're putting it here so that CallbackObject subclasses will
+ // have access to it, but outside code will not.
+ //
+ // Places that use this need to ensure that the callback is traced (e.g. via a
+ // Rooted) until the HoldJSObjects call happens.
+ struct FastCallbackConstructor {};
+
+ // Just like the public version without the FastCallbackConstructor argument,
+ // except for not calling HoldJSObjects and not capturing async stacks (on the
+ // assumption that we will do that last whenever we decide to actually
+ // HoldJSObjects; see FinishSlowJSInitIfMoreThanOneOwner). If you use this,
+ // you MUST ensure that the object is traced until the HoldJSObjects happens!
+ CallbackObject(JSObject* aCallback, JSObject* aCallbackGlobal,
+ const FastCallbackConstructor&) {
+ InitNoHold(aCallback, aCallbackGlobal, nullptr, nullptr);
+ }
+
+ // mCallback is not unwrapped, so it can be a cross-compartment-wrapper.
+ // This is done to ensure that, if JS code can't call a callback f(), or get
+ // its members, directly itself, this code won't call f(), or get its members,
+ // on the code's behalf.
+ JS::Heap<JSObject*> mCallback;
+ // mCallbackGlobal is the global that we were in when we created the
+ // callback. In particular, it is guaranteed to be same-compartment with
+ // aCallback. We store it separately, because we have no way to recover the
+ // global if mCallback is a cross-compartment wrapper.
+ JS::Heap<JSObject*> mCallbackGlobal;
+ JS::Heap<JSObject*> mCreationStack;
+ // Ideally, we'd just hold a reference to the nsIGlobalObject, since that's
+ // what we need to pass to AutoIncumbentScript. Unfortunately, that doesn't
+ // hold the actual JS global alive. So we maintain an additional pointer to
+ // the JS global itself so that we can trace it.
+ //
+ // At some point we should consider trying to make native globals hold their
+ // scripted global alive, at which point we can get rid of the duplication
+ // here.
+ nsCOMPtr<nsIGlobalObject> mIncumbentGlobal;
+ JS::TenuredHeap<JSObject*> mIncumbentJSGlobal;
+
+ class MOZ_STACK_CLASS CallSetup {
+ /**
+ * A class that performs whatever setup we need to safely make a
+ * call while this class is on the stack, After the constructor
+ * returns, the call is safe to make if GetContext() returns
+ * non-null.
+ */
+ public:
+ // If aExceptionHandling == eRethrowContentExceptions then aRealm
+ // needs to be set to the realm in which exceptions will be rethrown.
+ //
+ // If aExceptionHandling == eRethrowExceptions then aRealm may be set
+ // to the realm in which exceptions will be rethrown. In that case
+ // they will only be rethrown if that realm's principal subsumes the
+ // principal of our (unwrapped) callback.
+ CallSetup(CallbackObject* aCallback, ErrorResult& aRv,
+ const char* aExecutionReason,
+ ExceptionHandling aExceptionHandling, JS::Realm* aRealm = nullptr,
+ bool aIsJSImplementedWebIDL = false);
+ MOZ_CAN_RUN_SCRIPT ~CallSetup();
+
+ JSContext* GetContext() const { return mCx; }
+
+ // Safe to call this after the constructor has run without throwing on the
+ // ErrorResult it was handed.
+ BindingCallContext& GetCallContext() { return *mCallContext; }
+
+ private:
+ // We better not get copy-constructed
+ CallSetup(const CallSetup&) = delete;
+
+ bool ShouldRethrowException(JS::Handle<JS::Value> aException);
+
+ // Members which can go away whenever
+ JSContext* mCx;
+
+ // Caller's realm. This will only have a sensible value if
+ // mExceptionHandling == eRethrowContentExceptions.
+ JS::Realm* mRealm;
+
+ // And now members whose construction/destruction order we need to control.
+ Maybe<AutoEntryScript> mAutoEntryScript;
+ Maybe<AutoIncumbentScript> mAutoIncumbentScript;
+
+ Maybe<JS::Rooted<JSObject*>> mRootedCallable;
+ // The global of mRootedCallable.
+ Maybe<JS::Rooted<JSObject*>> mRootedCallableGlobal;
+
+ // Members which are used to set the async stack.
+ Maybe<JS::Rooted<JSObject*>> mAsyncStack;
+ Maybe<JS::AutoSetAsyncStackForNewCalls> mAsyncStackSetter;
+
+ // Can't construct a JSAutoRealm without a JSContext either. Also,
+ // Put mAr after mAutoEntryScript so that we exit the realm before we
+ // pop the script settings stack. Though in practice we'll often manually
+ // order those two things.
+ Maybe<JSAutoRealm> mAr;
+
+ // Our BindingCallContext. This is a Maybe so we can avoid constructing it
+ // until after we have a JSContext to construct it with.
+ Maybe<BindingCallContext> mCallContext;
+
+ // An ErrorResult to possibly re-throw exceptions on and whether
+ // we should re-throw them.
+ ErrorResult& mErrorResult;
+ const ExceptionHandling mExceptionHandling;
+ const bool mIsMainThread;
+ };
+};
+
+template <class WebIDLCallbackT, class XPCOMCallbackT>
+class CallbackObjectHolder;
+
+template <class T, class U>
+void ImplCycleCollectionUnlink(CallbackObjectHolder<T, U>& aField);
+
+class CallbackObjectHolderBase {
+ protected:
+ // Returns null on all failures
+ already_AddRefed<nsISupports> ToXPCOMCallback(CallbackObject* aCallback,
+ const nsIID& aIID) const;
+};
+
+template <class WebIDLCallbackT, class XPCOMCallbackT>
+class CallbackObjectHolder : CallbackObjectHolderBase {
+ /**
+ * A class which stores either a WebIDLCallbackT* or an XPCOMCallbackT*. Both
+ * types must inherit from nsISupports. The pointer that's stored can be
+ * null.
+ *
+ * When storing a WebIDLCallbackT*, mPtrBits is set to the pointer value.
+ * When storing an XPCOMCallbackT*, mPtrBits is the pointer value with low bit
+ * set.
+ */
+ public:
+ explicit CallbackObjectHolder(WebIDLCallbackT* aCallback)
+ : mPtrBits(reinterpret_cast<uintptr_t>(aCallback)) {
+ NS_IF_ADDREF(aCallback);
+ }
+
+ explicit CallbackObjectHolder(XPCOMCallbackT* aCallback)
+ : mPtrBits(reinterpret_cast<uintptr_t>(aCallback) | XPCOMCallbackFlag) {
+ NS_IF_ADDREF(aCallback);
+ }
+
+ CallbackObjectHolder(CallbackObjectHolder&& aOther)
+ : mPtrBits(aOther.mPtrBits) {
+ aOther.mPtrBits = 0;
+ static_assert(sizeof(CallbackObjectHolder) == sizeof(void*),
+ "This object is expected to be as small as a pointer, and it "
+ "is currently passed by value in various places. If it is "
+ "bloating, we may want to pass it by reference then.");
+ }
+
+ CallbackObjectHolder(const CallbackObjectHolder& aOther) = delete;
+
+ CallbackObjectHolder() : mPtrBits(0) {}
+
+ ~CallbackObjectHolder() { UnlinkSelf(); }
+
+ void operator=(WebIDLCallbackT* aCallback) {
+ UnlinkSelf();
+ mPtrBits = reinterpret_cast<uintptr_t>(aCallback);
+ NS_IF_ADDREF(aCallback);
+ }
+
+ void operator=(XPCOMCallbackT* aCallback) {
+ UnlinkSelf();
+ mPtrBits = reinterpret_cast<uintptr_t>(aCallback) | XPCOMCallbackFlag;
+ NS_IF_ADDREF(aCallback);
+ }
+
+ void operator=(CallbackObjectHolder&& aOther) {
+ UnlinkSelf();
+ mPtrBits = aOther.mPtrBits;
+ aOther.mPtrBits = 0;
+ }
+
+ void operator=(const CallbackObjectHolder& aOther) = delete;
+
+ void Reset() { UnlinkSelf(); }
+
+ nsISupports* GetISupports() const {
+ return reinterpret_cast<nsISupports*>(mPtrBits & ~XPCOMCallbackFlag);
+ }
+
+ already_AddRefed<nsISupports> Forget() {
+ // This can be called from random threads. Make sure to not refcount things
+ // in here!
+ nsISupports* supp = GetISupports();
+ mPtrBits = 0;
+ return dont_AddRef(supp);
+ }
+
+ // Boolean conversion operator so people can use this in boolean tests
+ explicit operator bool() const { return GetISupports(); }
+
+ CallbackObjectHolder Clone() const {
+ CallbackObjectHolder result;
+ result.mPtrBits = mPtrBits;
+ NS_IF_ADDREF(GetISupports());
+ return result;
+ }
+
+ // Even if HasWebIDLCallback returns true, GetWebIDLCallback() might still
+ // return null.
+ bool HasWebIDLCallback() const { return !(mPtrBits & XPCOMCallbackFlag); }
+
+ WebIDLCallbackT* GetWebIDLCallback() const {
+ MOZ_ASSERT(HasWebIDLCallback());
+ return reinterpret_cast<WebIDLCallbackT*>(mPtrBits);
+ }
+
+ XPCOMCallbackT* GetXPCOMCallback() const {
+ MOZ_ASSERT(!HasWebIDLCallback());
+ return reinterpret_cast<XPCOMCallbackT*>(mPtrBits & ~XPCOMCallbackFlag);
+ }
+
+ bool operator==(WebIDLCallbackT* aOtherCallback) const {
+ if (!aOtherCallback) {
+ // If other is null, then we must be null to be equal.
+ return !GetISupports();
+ }
+
+ if (!HasWebIDLCallback() || !GetWebIDLCallback()) {
+ // If other is non-null, then we can't be equal if we have a
+ // non-WebIDL callback or a null callback.
+ return false;
+ }
+
+ return *GetWebIDLCallback() == *aOtherCallback;
+ }
+
+ bool operator==(XPCOMCallbackT* aOtherCallback) const {
+ return (!aOtherCallback && !GetISupports()) ||
+ (!HasWebIDLCallback() && GetXPCOMCallback() == aOtherCallback);
+ }
+
+ bool operator==(const CallbackObjectHolder& aOtherCallback) const {
+ if (aOtherCallback.HasWebIDLCallback()) {
+ return *this == aOtherCallback.GetWebIDLCallback();
+ }
+
+ return *this == aOtherCallback.GetXPCOMCallback();
+ }
+
+ // Try to return an XPCOMCallbackT version of this object.
+ already_AddRefed<XPCOMCallbackT> ToXPCOMCallback() const {
+ if (!HasWebIDLCallback()) {
+ RefPtr<XPCOMCallbackT> callback = GetXPCOMCallback();
+ return callback.forget();
+ }
+
+ nsCOMPtr<nsISupports> supp = CallbackObjectHolderBase::ToXPCOMCallback(
+ GetWebIDLCallback(), NS_GET_TEMPLATE_IID(XPCOMCallbackT));
+ if (supp) {
+ // ToXPCOMCallback already did the right QI for us.
+ return supp.forget().downcast<XPCOMCallbackT>();
+ }
+ return nullptr;
+ }
+
+ // Try to return a WebIDLCallbackT version of this object.
+ already_AddRefed<WebIDLCallbackT> ToWebIDLCallback() const {
+ if (HasWebIDLCallback()) {
+ RefPtr<WebIDLCallbackT> callback = GetWebIDLCallback();
+ return callback.forget();
+ }
+ return nullptr;
+ }
+
+ private:
+ static const uintptr_t XPCOMCallbackFlag = 1u;
+
+ friend void ImplCycleCollectionUnlink<WebIDLCallbackT, XPCOMCallbackT>(
+ CallbackObjectHolder& aField);
+
+ void UnlinkSelf() {
+ // NS_IF_RELEASE because we might have been unlinked before
+ nsISupports* ptr = GetISupports();
+ // Clear mPtrBits before the release to prevent reentrance.
+ mPtrBits = 0;
+ NS_IF_RELEASE(ptr);
+ }
+
+ uintptr_t mPtrBits;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(CallbackObject, DOM_CALLBACKOBJECT_IID)
+
+template <class T, class U>
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ CallbackObjectHolder<T, U>& aField, const char* aName,
+ uint32_t aFlags = 0) {
+ if (aField) {
+ CycleCollectionNoteChild(aCallback, aField.GetISupports(), aName, aFlags);
+ }
+}
+
+template <class T, class U>
+void ImplCycleCollectionUnlink(CallbackObjectHolder<T, U>& aField) {
+ aField.UnlinkSelf();
+}
+
+// T is expected to be a RefPtr or OwningNonNull around a CallbackObject
+// subclass. This class is used in bindings to safely handle Fast* callbacks;
+// it ensures that the callback is traced, and that if something is holding onto
+// the callback when we're done with it HoldJSObjects is called.
+//
+// Since we effectively hold a ref to a refcounted thing (like RefPtr or
+// OwningNonNull), we are also MOZ_IS_SMARTPTR_TO_REFCOUNTED for static analysis
+// purposes.
+template <typename T>
+class MOZ_RAII MOZ_IS_SMARTPTR_TO_REFCOUNTED RootedCallback
+ : public JS::Rooted<T> {
+ public:
+ explicit RootedCallback(JSContext* cx) : JS::Rooted<T>(cx), mCx(cx) {}
+
+ // We need a way to make assignment from pointers (how we're normally used)
+ // work.
+ template <typename S>
+ void operator=(S* arg) {
+ this->get().operator=(arg);
+ }
+
+ // But nullptr can't use the above template, because it doesn't know which S
+ // to select. So we need a special overload for nullptr.
+ void operator=(decltype(nullptr) arg) { this->get().operator=(arg); }
+
+ // Codegen relies on being able to do CallbackOrNull() and Callback() on us.
+ JSObject* CallbackOrNull() const { return this->get()->CallbackOrNull(); }
+
+ JSObject* Callback(JSContext* aCx) const {
+ return this->get()->Callback(aCx);
+ }
+
+ ~RootedCallback() {
+ // Ensure that our callback starts holding on to its own JS objects as
+ // needed. We really do need to check that things are initialized even when
+ // T is OwningNonNull, because we might be running before the OwningNonNull
+ // ever got assigned to!
+ if (IsInitialized(this->get())) {
+ this->get()->FinishSlowJSInitIfMoreThanOneOwner(mCx);
+ }
+ }
+
+ private:
+ template <typename U>
+ static bool IsInitialized(U& aArg); // Not implemented
+
+ template <typename U>
+ static bool IsInitialized(RefPtr<U>& aRefPtr) {
+ return aRefPtr;
+ }
+
+ template <typename U>
+ static bool IsInitialized(OwningNonNull<U>& aOwningNonNull) {
+ return aOwningNonNull.isInitialized();
+ }
+
+ JSContext* mCx;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_CallbackObject_h
diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py
new file mode 100644
index 0000000000..809a680d70
--- /dev/null
+++ b/dom/bindings/Codegen.py
@@ -0,0 +1,24278 @@
+# 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/.
+
+# Common codegen classes.
+
+import functools
+import math
+import os
+import re
+import string
+import textwrap
+
+import six
+from Configuration import (
+ Descriptor,
+ MemberIsLegacyUnforgeable,
+ NoSuchDescriptorError,
+ getAllTypes,
+ getTypesFromCallback,
+ getTypesFromDescriptor,
+ getTypesFromDictionary,
+)
+from perfecthash import PerfectHash
+from WebIDL import (
+ BuiltinTypes,
+ IDLAttribute,
+ IDLBuiltinType,
+ IDLDefaultDictionaryValue,
+ IDLDictionary,
+ IDLEmptySequenceValue,
+ IDLInterfaceMember,
+ IDLNullValue,
+ IDLSequenceType,
+ IDLType,
+ IDLUndefinedValue,
+)
+
+AUTOGENERATED_WARNING_COMMENT = (
+ "/* THIS FILE IS AUTOGENERATED BY Codegen.py - DO NOT EDIT */\n\n"
+)
+AUTOGENERATED_WITH_SOURCE_WARNING_COMMENT = (
+ "/* THIS FILE IS AUTOGENERATED FROM %s BY Codegen.py - DO NOT EDIT */\n\n"
+)
+ADDPROPERTY_HOOK_NAME = "_addProperty"
+GETWRAPPERCACHE_HOOK_NAME = "_getWrapperCache"
+FINALIZE_HOOK_NAME = "_finalize"
+OBJECT_MOVED_HOOK_NAME = "_objectMoved"
+CONSTRUCT_HOOK_NAME = "_constructor"
+LEGACYCALLER_HOOK_NAME = "_legacycaller"
+RESOLVE_HOOK_NAME = "_resolve"
+MAY_RESOLVE_HOOK_NAME = "_mayResolve"
+NEW_ENUMERATE_HOOK_NAME = "_newEnumerate"
+ENUM_ENTRY_VARIABLE_NAME = "strings"
+INSTANCE_RESERVED_SLOTS = 1
+
+# This size is arbitrary. It is a power of 2 to make using it as a modulo
+# operand cheap, and is usually around 1/3-1/5th of the set size (sometimes
+# smaller for very large sets).
+GLOBAL_NAMES_PHF_SIZE = 256
+
+
+def memberReservedSlot(member, descriptor):
+ return (
+ "(DOM_INSTANCE_RESERVED_SLOTS + %d)"
+ % member.slotIndices[descriptor.interface.identifier.name]
+ )
+
+
+def memberXrayExpandoReservedSlot(member, descriptor):
+ return (
+ "(xpc::JSSLOT_EXPANDO_COUNT + %d)"
+ % member.slotIndices[descriptor.interface.identifier.name]
+ )
+
+
+def mayUseXrayExpandoSlots(descriptor, attr):
+ assert not attr.getExtendedAttribute("NewObject")
+ # For attributes whose type is a Gecko interface we always use
+ # slots on the reflector for caching. Also, for interfaces that
+ # don't want Xrays we obviously never use the Xray expando slot.
+ return descriptor.wantsXrays and not attr.type.isGeckoInterface()
+
+
+def toStringBool(arg):
+ """
+ Converts IDL/Python Boolean (True/False) to C++ Boolean (true/false)
+ """
+ return str(not not arg).lower()
+
+
+def toBindingNamespace(arg):
+ return arg + "_Binding"
+
+
+def isTypeCopyConstructible(type):
+ # Nullable and sequence stuff doesn't affect copy-constructibility
+ type = type.unroll()
+ return (
+ type.isUndefined()
+ or type.isPrimitive()
+ or type.isString()
+ or type.isEnum()
+ or (type.isUnion() and CGUnionStruct.isUnionCopyConstructible(type))
+ or (
+ type.isDictionary()
+ and CGDictionary.isDictionaryCopyConstructible(type.inner)
+ )
+ or
+ # Interface types are only copy-constructible if they're Gecko
+ # interfaces. SpiderMonkey interfaces are not copy-constructible
+ # because of rooting issues.
+ (type.isInterface() and type.isGeckoInterface())
+ )
+
+
+class CycleCollectionUnsupported(TypeError):
+ def __init__(self, message):
+ TypeError.__init__(self, message)
+
+
+def idlTypeNeedsCycleCollection(type):
+ type = type.unroll() # Takes care of sequences and nullables
+ if (
+ (type.isPrimitive() and type.tag() in builtinNames)
+ or type.isUndefined()
+ or type.isEnum()
+ or type.isString()
+ or type.isAny()
+ or type.isObject()
+ or type.isSpiderMonkeyInterface()
+ ):
+ return False
+ elif type.isCallback() or type.isPromise() or type.isGeckoInterface():
+ return True
+ elif type.isUnion():
+ return any(idlTypeNeedsCycleCollection(t) for t in type.flatMemberTypes)
+ elif type.isRecord():
+ if idlTypeNeedsCycleCollection(type.inner):
+ raise CycleCollectionUnsupported(
+ "Cycle collection for type %s is not supported" % type
+ )
+ return False
+ elif type.isDictionary():
+ return CGDictionary.dictionaryNeedsCycleCollection(type.inner)
+ else:
+ raise CycleCollectionUnsupported(
+ "Don't know whether to cycle-collect type %s" % type
+ )
+
+
+def idlTypeNeedsCallContext(type, descriptor=None, allowTreatNonCallableAsNull=False):
+ """
+ Returns whether the given type needs error reporting via a
+ BindingCallContext for JS-to-C++ conversions. This will happen when the
+ conversion can throw an exception due to logic in the IDL spec or
+ Gecko-specific security checks. In particular, a type needs a
+ BindingCallContext if and only if the JS-to-C++ conversion for that type can
+ end up calling ThrowErrorMessage.
+
+ For some types this depends on the descriptor (e.g. because we do certain
+ checks only for some kinds of interfaces).
+
+ The allowTreatNonCallableAsNull optimization is there so we can avoid
+ generating an unnecessary BindingCallContext for all the event handler
+ attribute setters.
+
+ """
+ while True:
+ if type.isSequence():
+ # Sequences can always throw "not an object"
+ return True
+ if type.nullable():
+ # treatNonObjectAsNull() and treatNonCallableAsNull() are
+ # only sane things to test on nullable types, so do that now.
+ if (
+ allowTreatNonCallableAsNull
+ and type.isCallback()
+ and (type.treatNonObjectAsNull() or type.treatNonCallableAsNull())
+ ):
+ # This can't throw. so never needs a method description.
+ return False
+ type = type.inner
+ else:
+ break
+
+ if type.isUndefined():
+ # Clearly doesn't need a method description; we can only get here from
+ # CGHeaders trying to decide whether to include the method description
+ # header.
+ return False
+ # The float check needs to come before the isPrimitive() check,
+ # because floats are primitives too.
+ if type.isFloat():
+ # Floats can throw if restricted.
+ return not type.isUnrestricted()
+ if type.isPrimitive() and type.tag() in builtinNames:
+ # Numbers can throw if enforcing range.
+ return type.hasEnforceRange()
+ if type.isEnum():
+ # Can throw on invalid value.
+ return True
+ if type.isString():
+ # Can throw if it's a ByteString
+ return type.isByteString()
+ if type.isAny():
+ # JS-implemented interfaces do extra security checks so need a
+ # method description here. If we have no descriptor, this
+ # might be JS-implemented thing, so it will do the security
+ # check and we need the method description.
+ return not descriptor or descriptor.interface.isJSImplemented()
+ if type.isPromise():
+ # JS-to-Promise conversion won't cause us to throw any
+ # specific exceptions, so does not need a method description.
+ return False
+ if (
+ type.isObject()
+ or type.isInterface()
+ or type.isCallback()
+ or type.isDictionary()
+ or type.isRecord()
+ or type.isObservableArray()
+ ):
+ # These can all throw if a primitive is passed in, at the very least.
+ # There are some rare cases when we know we have an object, but those
+ # are not worth the complexity of optimizing for.
+ #
+ # Note that we checked the [LegacyTreatNonObjectAsNull] case already when
+ # unwrapping nullables.
+ return True
+ if type.isUnion():
+ # Can throw if a type not in the union is passed in.
+ return True
+ raise TypeError("Don't know whether type '%s' needs a method description" % type)
+
+
+# TryPreserveWrapper uses the addProperty hook to preserve the wrapper of
+# non-nsISupports cycle collected objects, so if wantsAddProperty is changed
+# to not cover that case then TryPreserveWrapper will need to be changed.
+def wantsAddProperty(desc):
+ return desc.concrete and desc.wrapperCache and not desc.isGlobal()
+
+
+def wantsGetWrapperCache(desc):
+ return (
+ desc.concrete and desc.wrapperCache and not desc.isGlobal() and not desc.proxy
+ )
+
+
+# We'll want to insert the indent at the beginnings of lines, but we
+# don't want to indent empty lines. So only indent lines that have a
+# non-newline character on them.
+lineStartDetector = re.compile("^(?=[^\n#])", re.MULTILINE)
+
+
+def indent(s, indentLevel=2):
+ """
+ Indent C++ code.
+
+ Weird secret feature: this doesn't indent lines that start with # (such as
+ #include lines or #ifdef/#endif).
+ """
+ if s == "":
+ return s
+ return re.sub(lineStartDetector, indentLevel * " ", s)
+
+
+# dedent() and fill() are often called on the same string multiple
+# times. We want to memoize their return values so we don't keep
+# recomputing them all the time.
+def memoize(fn):
+ """
+ Decorator to memoize a function of one argument. The cache just
+ grows without bound.
+ """
+ cache = {}
+
+ @functools.wraps(fn)
+ def wrapper(arg):
+ retval = cache.get(arg)
+ if retval is None:
+ retval = cache[arg] = fn(arg)
+ return retval
+
+ return wrapper
+
+
+@memoize
+def dedent(s):
+ """
+ Remove all leading whitespace from s, and remove a blank line
+ at the beginning.
+ """
+ if s.startswith("\n"):
+ s = s[1:]
+ return textwrap.dedent(s)
+
+
+# This works by transforming the fill()-template to an equivalent
+# string.Template.
+fill_multiline_substitution_re = re.compile(r"( *)\$\*{(\w+)}(\n)?")
+
+
+find_substitutions = re.compile(r"\${")
+
+
+@memoize
+def compile_fill_template(template):
+ """
+ Helper function for fill(). Given the template string passed to fill(),
+ do the reusable part of template processing and return a pair (t,
+ argModList) that can be used every time fill() is called with that
+ template argument.
+
+ argsModList is list of tuples that represent modifications to be
+ made to args. Each modification has, in order: i) the arg name,
+ ii) the modified name, iii) the indent depth.
+ """
+ t = dedent(template)
+ assert t.endswith("\n") or "\n" not in t
+ argModList = []
+
+ def replace(match):
+ """
+ Replaces a line like ' $*{xyz}\n' with '${xyz_n}',
+ where n is the indent depth, and add a corresponding entry to
+ argModList.
+
+ Note that this needs to close over argModList, so it has to be
+ defined inside compile_fill_template().
+ """
+ indentation, name, nl = match.groups()
+ depth = len(indentation)
+
+ # Check that $*{xyz} appears by itself on a line.
+ prev = match.string[: match.start()]
+ if (prev and not prev.endswith("\n")) or nl is None:
+ raise ValueError(
+ "Invalid fill() template: $*{%s} must appear by itself on a line" % name
+ )
+
+ # Now replace this whole line of template with the indented equivalent.
+ modified_name = name + "_" + str(depth)
+ argModList.append((name, modified_name, depth))
+ return "${" + modified_name + "}"
+
+ t = re.sub(fill_multiline_substitution_re, replace, t)
+ if not re.search(find_substitutions, t):
+ raise TypeError("Using fill() when dedent() would do.")
+ return (string.Template(t), argModList)
+
+
+def fill(template, **args):
+ """
+ Convenience function for filling in a multiline template.
+
+ `fill(template, name1=v1, name2=v2)` is a lot like
+ `string.Template(template).substitute({"name1": v1, "name2": v2})`.
+
+ However, it's shorter, and has a few nice features:
+
+ * If `template` is indented, fill() automatically dedents it!
+ This makes code using fill() with Python's multiline strings
+ much nicer to look at.
+
+ * If `template` starts with a blank line, fill() strips it off.
+ (Again, convenient with multiline strings.)
+
+ * fill() recognizes a special kind of substitution
+ of the form `$*{name}`.
+
+ Use this to paste in, and automatically indent, multiple lines.
+ (Mnemonic: The `*` is for "multiple lines").
+
+ A `$*` substitution must appear by itself on a line, with optional
+ preceding indentation (spaces only). The whole line is replaced by the
+ corresponding keyword argument, indented appropriately. If the
+ argument is an empty string, no output is generated, not even a blank
+ line.
+ """
+
+ t, argModList = compile_fill_template(template)
+ # Now apply argModList to args
+ for (name, modified_name, depth) in argModList:
+ if not (args[name] == "" or args[name].endswith("\n")):
+ raise ValueError(
+ "Argument %s with value %r is missing a newline" % (name, args[name])
+ )
+ args[modified_name] = indent(args[name], depth)
+
+ return t.substitute(args)
+
+
+class CGThing:
+ """
+ Abstract base class for things that spit out code.
+ """
+
+ def __init__(self):
+ pass # Nothing for now
+
+ def declare(self):
+ """Produce code for a header file."""
+ assert False # Override me!
+
+ def define(self):
+ """Produce code for a cpp file."""
+ assert False # Override me!
+
+ def deps(self):
+ """Produce the deps for a pp file"""
+ assert False # Override me!
+
+
+class CGStringTable(CGThing):
+ """
+ Generate a function accessor for a WebIDL string table, using the existing
+ concatenated names string and mapping indexes to offsets in that string:
+
+ const char *accessorName(unsigned int index) {
+ static const uint16_t offsets = { ... };
+ return BindingName(offsets[index]);
+ }
+
+ This is more efficient than the more natural:
+
+ const char *table[] = {
+ ...
+ };
+
+ The uint16_t offsets are smaller than the pointer equivalents, and the
+ concatenated string requires no runtime relocations.
+ """
+
+ def __init__(self, accessorName, strings, static=False):
+ CGThing.__init__(self)
+ self.accessorName = accessorName
+ self.strings = strings
+ self.static = static
+
+ def declare(self):
+ if self.static:
+ return ""
+ return "const char *%s(unsigned int aIndex);\n" % self.accessorName
+
+ def define(self):
+ offsets = []
+ for s in self.strings:
+ offsets.append(BindingNamesOffsetEnum(s))
+ return fill(
+ """
+ ${static}const char *${name}(unsigned int aIndex)
+ {
+ static const BindingNamesOffset offsets[] = {
+ $*{offsets}
+ };
+ return BindingName(offsets[aIndex]);
+ }
+ """,
+ static="static " if self.static else "",
+ name=self.accessorName,
+ offsets="".join("BindingNamesOffset::%s,\n" % o for o in offsets),
+ )
+
+
+class CGNativePropertyHooks(CGThing):
+ """
+ Generate a NativePropertyHooks for a given descriptor
+ """
+
+ def __init__(self, descriptor, properties):
+ CGThing.__init__(self)
+ assert descriptor.wantsXrays
+ self.descriptor = descriptor
+ self.properties = properties
+
+ def declare(self):
+ return ""
+
+ def define(self):
+ deleteNamedProperty = "nullptr"
+ if (
+ self.descriptor.concrete
+ and self.descriptor.proxy
+ and not self.descriptor.isMaybeCrossOriginObject()
+ ):
+ resolveOwnProperty = "binding_detail::ResolveOwnProperty"
+ enumerateOwnProperties = "binding_detail::EnumerateOwnProperties"
+ if self.descriptor.needsXrayNamedDeleterHook():
+ deleteNamedProperty = "DeleteNamedProperty"
+ elif self.descriptor.needsXrayResolveHooks():
+ resolveOwnProperty = "ResolveOwnPropertyViaResolve"
+ enumerateOwnProperties = "EnumerateOwnPropertiesViaGetOwnPropertyNames"
+ else:
+ resolveOwnProperty = "nullptr"
+ enumerateOwnProperties = "nullptr"
+ if self.properties.hasNonChromeOnly():
+ regular = "sNativeProperties.Upcast()"
+ else:
+ regular = "nullptr"
+ if self.properties.hasChromeOnly():
+ chrome = "sChromeOnlyNativeProperties.Upcast()"
+ else:
+ chrome = "nullptr"
+ constructorID = "constructors::id::"
+ if self.descriptor.interface.hasInterfaceObject():
+ constructorID += self.descriptor.name
+ else:
+ constructorID += "_ID_Count"
+ prototypeID = "prototypes::id::"
+ if self.descriptor.interface.hasInterfacePrototypeObject():
+ prototypeID += self.descriptor.name
+ else:
+ prototypeID += "_ID_Count"
+
+ if self.descriptor.wantsXrayExpandoClass:
+ expandoClass = "&sXrayExpandoObjectClass"
+ else:
+ expandoClass = "&DefaultXrayExpandoObjectClass"
+
+ return fill(
+ """
+ bool sNativePropertiesInited = false;
+ const NativePropertyHooks sNativePropertyHooks = {
+ ${resolveOwnProperty},
+ ${enumerateOwnProperties},
+ ${deleteNamedProperty},
+ { ${regular}, ${chrome}, &sNativePropertiesInited },
+ ${prototypeID},
+ ${constructorID},
+ ${expandoClass}
+ };
+ """,
+ resolveOwnProperty=resolveOwnProperty,
+ enumerateOwnProperties=enumerateOwnProperties,
+ deleteNamedProperty=deleteNamedProperty,
+ regular=regular,
+ chrome=chrome,
+ prototypeID=prototypeID,
+ constructorID=constructorID,
+ expandoClass=expandoClass,
+ )
+
+
+def NativePropertyHooks(descriptor):
+ return (
+ "&sEmptyNativePropertyHooks"
+ if not descriptor.wantsXrays
+ else "&sNativePropertyHooks"
+ )
+
+
+def DOMClass(descriptor):
+ protoList = ["prototypes::id::" + proto for proto in descriptor.prototypeNameChain]
+ # Pad out the list to the right length with _ID_Count so we
+ # guarantee that all the lists are the same length. _ID_Count
+ # is never the ID of any prototype, so it's safe to use as
+ # padding.
+ protoList.extend(
+ ["prototypes::id::_ID_Count"]
+ * (descriptor.config.maxProtoChainLength - len(protoList))
+ )
+
+ if descriptor.interface.isSerializable():
+ serializer = "Serialize"
+ else:
+ serializer = "nullptr"
+
+ if wantsGetWrapperCache(descriptor):
+ wrapperCacheGetter = GETWRAPPERCACHE_HOOK_NAME
+ else:
+ wrapperCacheGetter = "nullptr"
+
+ if descriptor.hasOrdinaryObjectPrototype:
+ getProto = "JS::GetRealmObjectPrototypeHandle"
+ else:
+ getProto = "GetProtoObjectHandle"
+
+ return fill(
+ """
+ { ${protoChain} },
+ std::is_base_of_v<nsISupports, ${nativeType}>,
+ ${hooks},
+ FindAssociatedGlobalForNative<${nativeType}>::Get,
+ ${getProto},
+ GetCCParticipant<${nativeType}>::Get(),
+ ${serializer},
+ ${wrapperCacheGetter}
+ """,
+ protoChain=", ".join(protoList),
+ nativeType=descriptor.nativeType,
+ hooks=NativePropertyHooks(descriptor),
+ serializer=serializer,
+ wrapperCacheGetter=wrapperCacheGetter,
+ getProto=getProto,
+ )
+
+
+def InstanceReservedSlots(descriptor):
+ slots = INSTANCE_RESERVED_SLOTS + descriptor.interface.totalMembersInSlots
+ if descriptor.isMaybeCrossOriginObject():
+ # We need a slot for the cross-origin holder too.
+ if descriptor.interface.hasChildInterfaces():
+ raise TypeError(
+ "We don't support non-leaf cross-origin interfaces "
+ "like %s" % descriptor.interface.identifier.name
+ )
+ slots += 1
+ return slots
+
+
+class CGDOMJSClass(CGThing):
+ """
+ Generate a DOMJSClass for a given descriptor
+ """
+
+ def __init__(self, descriptor):
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+
+ def declare(self):
+ return ""
+
+ def define(self):
+ callHook = (
+ LEGACYCALLER_HOOK_NAME
+ if self.descriptor.operations["LegacyCaller"]
+ else "nullptr"
+ )
+ objectMovedHook = (
+ OBJECT_MOVED_HOOK_NAME if self.descriptor.wrapperCache else "nullptr"
+ )
+ slotCount = InstanceReservedSlots(self.descriptor)
+ classFlags = "JSCLASS_IS_DOMJSCLASS | JSCLASS_FOREGROUND_FINALIZE | "
+ if self.descriptor.isGlobal():
+ classFlags += (
+ "JSCLASS_DOM_GLOBAL | JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(DOM_GLOBAL_SLOTS)"
+ )
+ traceHook = "JS_GlobalObjectTraceHook"
+ reservedSlots = "JSCLASS_GLOBAL_APPLICATION_SLOTS"
+ else:
+ classFlags += "JSCLASS_HAS_RESERVED_SLOTS(%d)" % slotCount
+ traceHook = "nullptr"
+ reservedSlots = slotCount
+ if self.descriptor.interface.hasProbablyShortLivingWrapper():
+ if not self.descriptor.wrapperCache:
+ raise TypeError(
+ "Need a wrapper cache to support nursery "
+ "allocation of DOM objects"
+ )
+ classFlags += " | JSCLASS_SKIP_NURSERY_FINALIZE"
+
+ if self.descriptor.interface.getExtendedAttribute("NeedResolve"):
+ resolveHook = RESOLVE_HOOK_NAME
+ mayResolveHook = MAY_RESOLVE_HOOK_NAME
+ newEnumerateHook = NEW_ENUMERATE_HOOK_NAME
+ elif self.descriptor.isGlobal():
+ resolveHook = "mozilla::dom::ResolveGlobal"
+ mayResolveHook = "mozilla::dom::MayResolveGlobal"
+ newEnumerateHook = "mozilla::dom::EnumerateGlobal"
+ else:
+ resolveHook = "nullptr"
+ mayResolveHook = "nullptr"
+ newEnumerateHook = "nullptr"
+
+ return fill(
+ """
+ static const JSClassOps sClassOps = {
+ ${addProperty}, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* enumerate */
+ ${newEnumerate}, /* newEnumerate */
+ ${resolve}, /* resolve */
+ ${mayResolve}, /* mayResolve */
+ ${finalize}, /* finalize */
+ ${call}, /* call */
+ nullptr, /* construct */
+ ${trace}, /* trace */
+ };
+
+ static const js::ClassExtension sClassExtension = {
+ ${objectMoved} /* objectMovedOp */
+ };
+
+ static const DOMJSClass sClass = {
+ { "${name}",
+ ${flags},
+ &sClassOps,
+ JS_NULL_CLASS_SPEC,
+ &sClassExtension,
+ JS_NULL_OBJECT_OPS
+ },
+ $*{descriptor}
+ };
+ static_assert(${instanceReservedSlots} == DOM_INSTANCE_RESERVED_SLOTS,
+ "Must have the right minimal number of reserved slots.");
+ static_assert(${reservedSlots} >= ${slotCount},
+ "Must have enough reserved slots.");
+ """,
+ name=self.descriptor.interface.getClassName(),
+ flags=classFlags,
+ addProperty=ADDPROPERTY_HOOK_NAME
+ if wantsAddProperty(self.descriptor)
+ else "nullptr",
+ newEnumerate=newEnumerateHook,
+ resolve=resolveHook,
+ mayResolve=mayResolveHook,
+ finalize=FINALIZE_HOOK_NAME,
+ call=callHook,
+ trace=traceHook,
+ objectMoved=objectMovedHook,
+ descriptor=DOMClass(self.descriptor),
+ instanceReservedSlots=INSTANCE_RESERVED_SLOTS,
+ reservedSlots=reservedSlots,
+ slotCount=slotCount,
+ )
+
+
+class CGDOMProxyJSClass(CGThing):
+ """
+ Generate a DOMJSClass for a given proxy descriptor
+ """
+
+ def __init__(self, descriptor):
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+
+ def declare(self):
+ return ""
+
+ def define(self):
+ slotCount = InstanceReservedSlots(self.descriptor)
+ # We need one reserved slot (DOM_OBJECT_SLOT).
+ flags = ["JSCLASS_IS_DOMJSCLASS", "JSCLASS_HAS_RESERVED_SLOTS(%d)" % slotCount]
+ # We don't use an IDL annotation for JSCLASS_EMULATES_UNDEFINED because
+ # we don't want people ever adding that to any interface other than
+ # HTMLAllCollection. So just hardcode it here.
+ if self.descriptor.interface.identifier.name == "HTMLAllCollection":
+ flags.append("JSCLASS_EMULATES_UNDEFINED")
+ return fill(
+ """
+ static const DOMJSClass sClass = {
+ PROXY_CLASS_DEF("${name}",
+ ${flags}),
+ $*{descriptor}
+ };
+ """,
+ name=self.descriptor.interface.identifier.name,
+ flags=" | ".join(flags),
+ descriptor=DOMClass(self.descriptor),
+ )
+
+
+class CGXrayExpandoJSClass(CGThing):
+ """
+ Generate a JSClass for an Xray expando object. This is only
+ needed if we have members in slots (for [Cached] or [StoreInSlot]
+ stuff).
+ """
+
+ def __init__(self, descriptor):
+ assert descriptor.interface.totalMembersInSlots != 0
+ assert descriptor.wantsXrays
+ assert descriptor.wantsXrayExpandoClass
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+
+ def declare(self):
+ return ""
+
+ def define(self):
+ return fill(
+ """
+ // This may allocate too many slots, because we only really need
+ // slots for our non-interface-typed members that we cache. But
+ // allocating slots only for those would make the slot index
+ // computations much more complicated, so let's do this the simple
+ // way for now.
+ DEFINE_XRAY_EXPANDO_CLASS(static, sXrayExpandoObjectClass, ${memberSlots});
+ """,
+ memberSlots=self.descriptor.interface.totalMembersInSlots,
+ )
+
+
+def PrototypeIDAndDepth(descriptor):
+ prototypeID = "prototypes::id::"
+ if descriptor.interface.hasInterfacePrototypeObject():
+ prototypeID += descriptor.interface.identifier.name
+ depth = "PrototypeTraits<%s>::Depth" % prototypeID
+ else:
+ prototypeID += "_ID_Count"
+ depth = "0"
+ return (prototypeID, depth)
+
+
+def InterfacePrototypeObjectProtoGetter(descriptor):
+ """
+ Returns a tuple with two elements:
+
+ 1) The name of the function to call to get the prototype to use for the
+ interface prototype object as a JSObject*.
+
+ 2) The name of the function to call to get the prototype to use for the
+ interface prototype object as a JS::Handle<JSObject*> or None if no
+ such function exists.
+ """
+ parentProtoName = descriptor.parentPrototypeName
+ if descriptor.hasNamedPropertiesObject:
+ protoGetter = "GetNamedPropertiesObject"
+ protoHandleGetter = None
+ elif parentProtoName is None:
+ if descriptor.interface.getExtendedAttribute("ExceptionClass"):
+ protoGetter = "JS::GetRealmErrorPrototype"
+ elif descriptor.interface.isIteratorInterface():
+ protoGetter = "JS::GetRealmIteratorPrototype"
+ elif descriptor.interface.isAsyncIteratorInterface():
+ protoGetter = "JS::GetRealmAsyncIteratorPrototype"
+ else:
+ protoGetter = "JS::GetRealmObjectPrototype"
+ protoHandleGetter = None
+ else:
+ prefix = toBindingNamespace(parentProtoName)
+ protoGetter = prefix + "::GetProtoObject"
+ protoHandleGetter = prefix + "::GetProtoObjectHandle"
+
+ return (protoGetter, protoHandleGetter)
+
+
+class CGPrototypeJSClass(CGThing):
+ def __init__(self, descriptor, properties):
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+ self.properties = properties
+
+ def declare(self):
+ # We're purely for internal consumption
+ return ""
+
+ def define(self):
+ prototypeID, depth = PrototypeIDAndDepth(self.descriptor)
+ slotCount = "DOM_INTERFACE_PROTO_SLOTS_BASE"
+ # Globals handle unforgeables directly in Wrap() instead of
+ # via a holder.
+ if (
+ self.descriptor.hasLegacyUnforgeableMembers
+ and not self.descriptor.isGlobal()
+ ):
+ slotCount += (
+ " + 1 /* slot for the JSObject holding the unforgeable properties */"
+ )
+ (protoGetter, _) = InterfacePrototypeObjectProtoGetter(self.descriptor)
+ type = (
+ "eGlobalInterfacePrototype"
+ if self.descriptor.isGlobal()
+ else "eInterfacePrototype"
+ )
+ return fill(
+ """
+ static const DOMIfaceAndProtoJSClass sPrototypeClass = {
+ {
+ "${name}Prototype",
+ JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(${slotCount}),
+ JS_NULL_CLASS_OPS,
+ JS_NULL_CLASS_SPEC,
+ JS_NULL_CLASS_EXT,
+ JS_NULL_OBJECT_OPS
+ },
+ ${type},
+ false,
+ ${prototypeID},
+ ${depth},
+ ${hooks},
+ nullptr,
+ ${protoGetter}
+ };
+ """,
+ name=self.descriptor.interface.getClassName(),
+ slotCount=slotCount,
+ type=type,
+ hooks=NativePropertyHooks(self.descriptor),
+ prototypeID=prototypeID,
+ depth=depth,
+ protoGetter=protoGetter,
+ )
+
+
+def InterfaceObjectProtoGetter(descriptor, forXrays=False):
+ """
+ Returns a tuple with two elements:
+
+ 1) The name of the function to call to get the prototype to use for the
+ interface object as a JSObject*.
+
+ 2) The name of the function to call to get the prototype to use for the
+ interface prototype as a JS::Handle<JSObject*> or None if no such
+ function exists.
+ """
+ parentInterface = descriptor.interface.parent
+ if parentInterface:
+ assert not descriptor.interface.isNamespace()
+ parentIfaceName = parentInterface.identifier.name
+ parentDesc = descriptor.getDescriptor(parentIfaceName)
+ prefix = toBindingNamespace(parentDesc.name)
+ protoGetter = prefix + "::GetConstructorObject"
+ protoHandleGetter = prefix + "::GetConstructorObjectHandle"
+ elif descriptor.interface.isNamespace():
+ if forXrays or not descriptor.interface.getExtendedAttribute("ProtoObjectHack"):
+ protoGetter = "JS::GetRealmObjectPrototype"
+ else:
+ protoGetter = "GetHackedNamespaceProtoObject"
+ protoHandleGetter = None
+ else:
+ protoGetter = "JS::GetRealmFunctionPrototype"
+ protoHandleGetter = None
+ return (protoGetter, protoHandleGetter)
+
+
+class CGInterfaceObjectJSClass(CGThing):
+ def __init__(self, descriptor, properties):
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+ self.properties = properties
+
+ def declare(self):
+ # We're purely for internal consumption
+ return ""
+
+ def define(self):
+ if self.descriptor.interface.ctor():
+ assert not self.descriptor.interface.isNamespace()
+ ctorname = CONSTRUCT_HOOK_NAME
+ elif self.descriptor.interface.isNamespace():
+ ctorname = "nullptr"
+ else:
+ ctorname = "ThrowingConstructor"
+ needsHasInstance = self.descriptor.interface.hasInterfacePrototypeObject()
+
+ prototypeID, depth = PrototypeIDAndDepth(self.descriptor)
+ slotCount = "DOM_INTERFACE_SLOTS_BASE"
+ if len(self.descriptor.interface.legacyFactoryFunctions) > 0:
+ slotCount += " + %i /* slots for the legacy factory functions */" % len(
+ self.descriptor.interface.legacyFactoryFunctions
+ )
+ (protoGetter, _) = InterfaceObjectProtoGetter(self.descriptor, forXrays=True)
+
+ if ctorname == "ThrowingConstructor":
+ ret = ""
+ classOpsPtr = "&sBoringInterfaceObjectClassClassOps"
+ elif ctorname == "nullptr":
+ ret = ""
+ classOpsPtr = "JS_NULL_CLASS_OPS"
+ else:
+ ret = fill(
+ """
+ static const JSClassOps sInterfaceObjectClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* enumerate */
+ nullptr, /* newEnumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ nullptr, /* finalize */
+ ${ctorname}, /* call */
+ ${ctorname}, /* construct */
+ nullptr, /* trace */
+ };
+
+ """,
+ ctorname=ctorname,
+ )
+ classOpsPtr = "&sInterfaceObjectClassOps"
+
+ if self.descriptor.interface.isNamespace():
+ classString = self.descriptor.interface.getExtendedAttribute("ClassString")
+ if classString is None:
+ classString = self.descriptor.interface.identifier.name
+ else:
+ classString = classString[0]
+ funToString = "nullptr"
+ objectOps = "JS_NULL_OBJECT_OPS"
+ else:
+ classString = "Function"
+ funToString = (
+ '"function %s() {\\n [native code]\\n}"'
+ % self.descriptor.interface.identifier.name
+ )
+ # We need non-default ObjectOps so we can actually make
+ # use of our funToString.
+ objectOps = "&sInterfaceObjectClassObjectOps"
+
+ ret = ret + fill(
+ """
+ static const DOMIfaceAndProtoJSClass sInterfaceObjectClass = {
+ {
+ "${classString}",
+ JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(${slotCount}),
+ ${classOpsPtr},
+ JS_NULL_CLASS_SPEC,
+ JS_NULL_CLASS_EXT,
+ ${objectOps}
+ },
+ ${type},
+ ${needsHasInstance},
+ ${prototypeID},
+ ${depth},
+ ${hooks},
+ ${funToString},
+ ${protoGetter}
+ };
+ """,
+ classString=classString,
+ slotCount=slotCount,
+ classOpsPtr=classOpsPtr,
+ hooks=NativePropertyHooks(self.descriptor),
+ objectOps=objectOps,
+ type="eNamespace"
+ if self.descriptor.interface.isNamespace()
+ else "eInterface",
+ needsHasInstance=toStringBool(needsHasInstance),
+ prototypeID=prototypeID,
+ depth=depth,
+ funToString=funToString,
+ protoGetter=protoGetter,
+ )
+ return ret
+
+
+class CGList(CGThing):
+ """
+ Generate code for a list of GCThings. Just concatenates them together, with
+ an optional joiner string. "\n" is a common joiner.
+ """
+
+ def __init__(self, children, joiner=""):
+ CGThing.__init__(self)
+ # Make a copy of the kids into a list, because if someone passes in a
+ # generator we won't be able to both declare and define ourselves, or
+ # define ourselves more than once!
+ self.children = list(children)
+ self.joiner = joiner
+
+ def append(self, child):
+ self.children.append(child)
+
+ def prepend(self, child):
+ self.children.insert(0, child)
+
+ def extend(self, kids):
+ self.children.extend(kids)
+
+ def join(self, iterable):
+ return self.joiner.join(s for s in iterable if len(s) > 0)
+
+ def declare(self):
+ return self.join(
+ child.declare() for child in self.children if child is not None
+ )
+
+ def define(self):
+ return self.join(child.define() for child in self.children if child is not None)
+
+ def deps(self):
+ deps = set()
+ for child in self.children:
+ if child is None:
+ continue
+ deps = deps.union(child.deps())
+ return deps
+
+ def __len__(self):
+ return len(self.children)
+
+
+class CGGeneric(CGThing):
+ """
+ A class that spits out a fixed string into the codegen. Can spit out a
+ separate string for the declaration too.
+ """
+
+ def __init__(self, define="", declare=""):
+ self.declareText = declare
+ self.defineText = define
+
+ def declare(self):
+ return self.declareText
+
+ def define(self):
+ return self.defineText
+
+ def deps(self):
+ return set()
+
+
+class CGIndenter(CGThing):
+ """
+ A class that takes another CGThing and generates code that indents that
+ CGThing by some number of spaces. The default indent is two spaces.
+ """
+
+ def __init__(self, child, indentLevel=2, declareOnly=False):
+ assert isinstance(child, CGThing)
+ CGThing.__init__(self)
+ self.child = child
+ self.indentLevel = indentLevel
+ self.declareOnly = declareOnly
+
+ def declare(self):
+ return indent(self.child.declare(), self.indentLevel)
+
+ def define(self):
+ defn = self.child.define()
+ if self.declareOnly:
+ return defn
+ else:
+ return indent(defn, self.indentLevel)
+
+
+class CGWrapper(CGThing):
+ """
+ Generic CGThing that wraps other CGThings with pre and post text.
+ """
+
+ def __init__(
+ self,
+ child,
+ pre="",
+ post="",
+ declarePre=None,
+ declarePost=None,
+ definePre=None,
+ definePost=None,
+ declareOnly=False,
+ defineOnly=False,
+ reindent=False,
+ ):
+ CGThing.__init__(self)
+ self.child = child
+ self.declarePre = declarePre or pre
+ self.declarePost = declarePost or post
+ self.definePre = definePre or pre
+ self.definePost = definePost or post
+ self.declareOnly = declareOnly
+ self.defineOnly = defineOnly
+ self.reindent = reindent
+
+ def declare(self):
+ if self.defineOnly:
+ return ""
+ decl = self.child.declare()
+ if self.reindent:
+ decl = self.reindentString(decl, self.declarePre)
+ return self.declarePre + decl + self.declarePost
+
+ def define(self):
+ if self.declareOnly:
+ return ""
+ defn = self.child.define()
+ if self.reindent:
+ defn = self.reindentString(defn, self.definePre)
+ return self.definePre + defn + self.definePost
+
+ @staticmethod
+ def reindentString(stringToIndent, widthString):
+ # We don't use lineStartDetector because we don't want to
+ # insert whitespace at the beginning of our _first_ line.
+ # Use the length of the last line of width string, in case
+ # it is a multiline string.
+ lastLineWidth = len(widthString.splitlines()[-1])
+ return stripTrailingWhitespace(
+ stringToIndent.replace("\n", "\n" + (" " * lastLineWidth))
+ )
+
+ def deps(self):
+ return self.child.deps()
+
+
+class CGIfWrapper(CGList):
+ def __init__(self, child, condition):
+ CGList.__init__(
+ self,
+ [
+ CGWrapper(
+ CGGeneric(condition), pre="if (", post=") {\n", reindent=True
+ ),
+ CGIndenter(child),
+ CGGeneric("}\n"),
+ ],
+ )
+
+
+class CGIfElseWrapper(CGList):
+ def __init__(self, condition, ifTrue, ifFalse):
+ CGList.__init__(
+ self,
+ [
+ CGWrapper(
+ CGGeneric(condition), pre="if (", post=") {\n", reindent=True
+ ),
+ CGIndenter(ifTrue),
+ CGGeneric("} else {\n"),
+ CGIndenter(ifFalse),
+ CGGeneric("}\n"),
+ ],
+ )
+
+
+class CGElseChain(CGThing):
+ """
+ Concatenate if statements in an if-else-if-else chain.
+ """
+
+ def __init__(self, children):
+ self.children = [c for c in children if c is not None]
+
+ def declare(self):
+ assert False
+
+ def define(self):
+ if not self.children:
+ return ""
+ s = self.children[0].define()
+ assert s.endswith("\n")
+ for child in self.children[1:]:
+ code = child.define()
+ assert code.startswith("if") or code.startswith("{")
+ assert code.endswith("\n")
+ s = s.rstrip() + " else " + code
+ return s
+
+
+class CGTemplatedType(CGWrapper):
+ def __init__(self, templateName, child, isConst=False, isReference=False):
+ if isinstance(child, list):
+ child = CGList(child, ", ")
+ const = "const " if isConst else ""
+ pre = "%s%s<" % (const, templateName)
+ ref = "&" if isReference else ""
+ post = ">%s" % ref
+ CGWrapper.__init__(self, child, pre=pre, post=post)
+
+
+class CGNamespace(CGThing):
+ """
+ Generates namespace block that wraps other CGThings.
+ """
+
+ def __init__(self, namespace, child):
+ CGThing.__init__(self)
+ self.child = child
+ self.pre = "namespace %s {\n" % namespace
+ self.post = "} // namespace %s\n" % namespace
+
+ def declare(self):
+ decl = self.child.declare()
+ if len(decl.strip()) == 0:
+ return ""
+ return self.pre + decl + self.post
+
+ def define(self):
+ defn = self.child.define()
+ if len(defn.strip()) == 0:
+ return ""
+ return self.pre + defn + self.post
+
+ def deps(self):
+ return self.child.deps()
+
+ @staticmethod
+ def build(namespaces, child):
+ """
+ Static helper method to build multiple wrapped namespaces.
+ """
+ if not namespaces:
+ return CGWrapper(child)
+ return CGNamespace("::".join(namespaces), child)
+
+
+class CGIncludeGuard(CGWrapper):
+ """
+ Generates include guards for a header.
+ """
+
+ def __init__(self, prefix, child):
+ """|prefix| is the filename without the extension."""
+ define = "mozilla_dom_%s_h" % prefix
+ CGWrapper.__init__(
+ self,
+ child,
+ declarePre="#ifndef %s\n#define %s\n\n" % (define, define),
+ declarePost="\n#endif // %s\n" % define,
+ )
+
+
+class CGHeaders(CGWrapper):
+ """
+ Generates the appropriate include statements.
+ """
+
+ def __init__(
+ self,
+ descriptors,
+ dictionaries,
+ callbacks,
+ callbackDescriptors,
+ declareIncludes,
+ defineIncludes,
+ prefix,
+ child,
+ config=None,
+ jsImplementedDescriptors=[],
+ ):
+ """
+ Builds a set of includes to cover |descriptors|.
+
+ Also includes the files in |declareIncludes| in the header
+ file and the files in |defineIncludes| in the .cpp.
+
+ |prefix| contains the basename of the file that we generate include
+ statements for.
+ """
+
+ # Determine the filenames for which we need headers.
+ interfaceDeps = [d.interface for d in descriptors]
+ ancestors = []
+ for iface in interfaceDeps:
+ if iface.parent:
+ # We're going to need our parent's prototype, to use as the
+ # prototype of our prototype object.
+ ancestors.append(iface.parent)
+ # And if we have an interface object, we'll need the nearest
+ # ancestor with an interface object too, so we can use its
+ # interface object as the proto of our interface object.
+ if iface.hasInterfaceObject():
+ parent = iface.parent
+ while parent and not parent.hasInterfaceObject():
+ parent = parent.parent
+ if parent:
+ ancestors.append(parent)
+ interfaceDeps.extend(ancestors)
+
+ # Include parent interface headers needed for default toJSON code.
+ jsonInterfaceParents = []
+ for desc in descriptors:
+ if not desc.hasDefaultToJSON:
+ continue
+ parent = desc.interface.parent
+ while parent:
+ parentDesc = desc.getDescriptor(parent.identifier.name)
+ if parentDesc.hasDefaultToJSON:
+ jsonInterfaceParents.append(parentDesc.interface)
+ parent = parent.parent
+ interfaceDeps.extend(jsonInterfaceParents)
+
+ bindingIncludes = set(self.getDeclarationFilename(d) for d in interfaceDeps)
+
+ # Grab all the implementation declaration files we need.
+ implementationIncludes = set(
+ d.headerFile for d in descriptors if d.needsHeaderInclude()
+ )
+
+ # Now find all the things we'll need as arguments because we
+ # need to wrap or unwrap them.
+ bindingHeaders = set()
+ declareIncludes = set(declareIncludes)
+
+ def addHeadersForType(typeAndPossibleOriginType):
+ """
+ Add the relevant headers for this type. We use its origin type, if
+ passed, to decide what to do with interface types.
+ """
+ t, originType = typeAndPossibleOriginType
+ isFromDictionary = originType and originType.isDictionary()
+ isFromCallback = originType and originType.isCallback()
+ # Dictionaries have members that need to be actually
+ # declared, not just forward-declared.
+ # Callbacks have nullable union arguments that need to be actually
+ # declared, not just forward-declared.
+ if isFromDictionary:
+ headerSet = declareIncludes
+ elif isFromCallback and t.nullable() and t.isUnion():
+ headerSet = declareIncludes
+ else:
+ headerSet = bindingHeaders
+ # Strip off outer layers and add headers they might require. (This
+ # is conservative: only nullable non-pointer types need Nullable.h;
+ # only sequences or observable arrays outside unions need
+ # ForOfIterator.h; only functions that return, and attributes that
+ # are, sequences or observable arrays in interfaces need Array.h, &c.)
+ unrolled = t
+ while True:
+ if idlTypeNeedsCallContext(unrolled):
+ bindingHeaders.add("mozilla/dom/BindingCallContext.h")
+ if unrolled.nullable():
+ headerSet.add("mozilla/dom/Nullable.h")
+ elif unrolled.isSequence() or unrolled.isObservableArray():
+ bindingHeaders.add("js/Array.h")
+ bindingHeaders.add("js/ForOfIterator.h")
+ if unrolled.isObservableArray():
+ bindingHeaders.add("mozilla/dom/ObservableArrayProxyHandler.h")
+ else:
+ break
+ unrolled = unrolled.inner
+ if unrolled.isUnion():
+ headerSet.add(self.getUnionDeclarationFilename(config, unrolled))
+ for t in unrolled.flatMemberTypes:
+ addHeadersForType((t, None))
+ elif unrolled.isPromise():
+ # See comment in the isInterface() case for why we add
+ # Promise.h to headerSet, not bindingHeaders.
+ headerSet.add("mozilla/dom/Promise.h")
+ # We need ToJSValue to do the Promise to JS conversion.
+ bindingHeaders.add("mozilla/dom/ToJSValue.h")
+ elif unrolled.isInterface():
+ if unrolled.isSpiderMonkeyInterface():
+ bindingHeaders.add("jsfriendapi.h")
+ if jsImplementedDescriptors:
+ # Since we can't forward-declare typed array types
+ # (because they're typedefs), we have to go ahead and
+ # just include their header if we need to have functions
+ # taking references to them declared in that header.
+ headerSet = declareIncludes
+ headerSet.add("mozilla/dom/TypedArray.h")
+ else:
+ try:
+ typeDesc = config.getDescriptor(unrolled.inner.identifier.name)
+ except NoSuchDescriptorError:
+ return
+ # Dictionaries with interface members rely on the
+ # actual class definition of that interface member
+ # being visible in the binding header, because they
+ # store them in RefPtr and have inline
+ # constructors/destructors.
+ #
+ # XXXbz maybe dictionaries with interface members
+ # should just have out-of-line constructors and
+ # destructors?
+ headerSet.add(typeDesc.headerFile)
+ elif unrolled.isDictionary():
+ headerSet.add(self.getDeclarationFilename(unrolled.inner))
+ # And if it needs rooting, we need RootedDictionary too
+ if typeNeedsRooting(unrolled):
+ headerSet.add("mozilla/dom/RootedDictionary.h")
+ elif unrolled.isCallback():
+ headerSet.add(self.getDeclarationFilename(unrolled.callback))
+ elif unrolled.isFloat() and not unrolled.isUnrestricted():
+ # Restricted floats are tested for finiteness
+ bindingHeaders.add("mozilla/FloatingPoint.h")
+ bindingHeaders.add("mozilla/dom/PrimitiveConversions.h")
+ elif unrolled.isEnum():
+ filename = self.getDeclarationFilename(unrolled.inner)
+ declareIncludes.add(filename)
+ elif unrolled.isPrimitive():
+ bindingHeaders.add("mozilla/dom/PrimitiveConversions.h")
+ elif unrolled.isRecord():
+ if isFromDictionary or jsImplementedDescriptors:
+ declareIncludes.add("mozilla/dom/Record.h")
+ else:
+ bindingHeaders.add("mozilla/dom/Record.h")
+ # Also add headers for the type the record is
+ # parametrized over, if needed.
+ addHeadersForType((t.inner, originType if isFromDictionary else None))
+
+ for t in getAllTypes(
+ descriptors + callbackDescriptors, dictionaries, callbacks
+ ):
+ addHeadersForType(t)
+
+ def addHeaderForFunc(func, desc):
+ if func is None:
+ return
+ # Include the right class header, which we can only do
+ # if this is a class member function.
+ if desc is not None and not desc.headerIsDefault:
+ # An explicit header file was provided, assume that we know
+ # what we're doing.
+ return
+
+ if "::" in func:
+ # Strip out the function name and convert "::" to "/"
+ bindingHeaders.add("/".join(func.split("::")[:-1]) + ".h")
+
+ # Now for non-callback descriptors make sure we include any
+ # headers needed by Func declarations and other things like that.
+ for desc in descriptors:
+ # If this is an iterator or an async iterator interface generated
+ # for a separate iterable interface, skip generating type includes,
+ # as we have what we need in IterableIterator.h
+ if (
+ desc.interface.isIteratorInterface()
+ or desc.interface.isAsyncIteratorInterface()
+ ):
+ continue
+
+ for m in desc.interface.members:
+ addHeaderForFunc(PropertyDefiner.getStringAttr(m, "Func"), desc)
+ staticTypeOverride = PropertyDefiner.getStringAttr(
+ m, "StaticClassOverride"
+ )
+ if staticTypeOverride:
+ bindingHeaders.add("/".join(staticTypeOverride.split("::")) + ".h")
+ # getExtendedAttribute() returns a list, extract the entry.
+ funcList = desc.interface.getExtendedAttribute("Func")
+ if funcList is not None:
+ addHeaderForFunc(funcList[0], desc)
+
+ if desc.interface.maplikeOrSetlikeOrIterable:
+ # 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/setlike/iterable, since they'll be needed for
+ # convenience functions
+ if desc.interface.maplikeOrSetlikeOrIterable.hasKeyType():
+ addHeadersForType(
+ (desc.interface.maplikeOrSetlikeOrIterable.keyType, None)
+ )
+ if desc.interface.maplikeOrSetlikeOrIterable.hasValueType():
+ addHeadersForType(
+ (desc.interface.maplikeOrSetlikeOrIterable.valueType, None)
+ )
+
+ for d in dictionaries:
+ if d.parent:
+ declareIncludes.add(self.getDeclarationFilename(d.parent))
+ bindingHeaders.add(self.getDeclarationFilename(d))
+ for m in d.members:
+ addHeaderForFunc(PropertyDefiner.getStringAttr(m, "Func"), None)
+ # No need to worry about Func on members of ancestors, because that
+ # will happen automatically in whatever files those ancestors live
+ # in.
+
+ for c in callbacks:
+ bindingHeaders.add(self.getDeclarationFilename(c))
+
+ for c in callbackDescriptors:
+ bindingHeaders.add(self.getDeclarationFilename(c.interface))
+
+ if len(callbacks) != 0:
+ # We need CallbackFunction to serve as our parent class
+ declareIncludes.add("mozilla/dom/CallbackFunction.h")
+ # And we need ToJSValue.h so we can wrap "this" objects
+ declareIncludes.add("mozilla/dom/ToJSValue.h")
+
+ if len(callbackDescriptors) != 0 or len(jsImplementedDescriptors) != 0:
+ # We need CallbackInterface to serve as our parent class
+ declareIncludes.add("mozilla/dom/CallbackInterface.h")
+ # And we need ToJSValue.h so we can wrap "this" objects
+ declareIncludes.add("mozilla/dom/ToJSValue.h")
+
+ # Also need to include the headers for ancestors of
+ # JS-implemented interfaces.
+ for jsImplemented in jsImplementedDescriptors:
+ jsParent = jsImplemented.interface.parent
+ if jsParent:
+ parentDesc = jsImplemented.getDescriptor(jsParent.identifier.name)
+ declareIncludes.add(parentDesc.jsImplParentHeader)
+
+ # Now make sure we're not trying to include the header from inside itself
+ declareIncludes.discard(prefix + ".h")
+
+ # Let the machinery do its thing.
+ def _includeString(includes):
+ def headerName(include):
+ # System headers are specified inside angle brackets.
+ if include.startswith("<"):
+ return include
+ # Non-system headers need to be placed in quotes.
+ return '"%s"' % include
+
+ return "".join(["#include %s\n" % headerName(i) for i in includes]) + "\n"
+
+ CGWrapper.__init__(
+ self,
+ child,
+ declarePre=_includeString(sorted(declareIncludes)),
+ definePre=_includeString(
+ sorted(
+ set(defineIncludes)
+ | bindingIncludes
+ | bindingHeaders
+ | implementationIncludes
+ )
+ ),
+ )
+
+ @staticmethod
+ def getDeclarationFilename(decl):
+ # Use our local version of the header, not the exported one, so that
+ # test bindings, which don't export, will work correctly.
+ basename = os.path.basename(decl.filename())
+ return basename.replace(".webidl", "Binding.h")
+
+ @staticmethod
+ def getUnionDeclarationFilename(config, unionType):
+ assert unionType.isUnion()
+ assert unionType.unroll() == unionType
+ # If a union is "defined" in multiple files, it goes in UnionTypes.h.
+ if len(config.filenamesPerUnion[unionType.name]) > 1:
+ return "mozilla/dom/UnionTypes.h"
+ # If a union is defined by a built-in typedef, it also goes in
+ # UnionTypes.h.
+ assert len(config.filenamesPerUnion[unionType.name]) == 1
+ if "<unknown>" in config.filenamesPerUnion[unionType.name]:
+ return "mozilla/dom/UnionTypes.h"
+ return CGHeaders.getDeclarationFilename(unionType)
+
+
+def SortedDictValues(d):
+ """
+ Returns a list of values from the dict sorted by key.
+ """
+ return [v for k, v in sorted(d.items())]
+
+
+def UnionsForFile(config, webIDLFile):
+ """
+ Returns a list of union types for all union types that are only used in
+ webIDLFile. If webIDLFile is None this will return the list of tuples for
+ union types that are used in more than one WebIDL file.
+ """
+ return config.unionsPerFilename.get(webIDLFile, [])
+
+
+def UnionTypes(unionTypes, config):
+ """
+ The unionTypes argument should be a list of union types. This is typically
+ the list generated by UnionsForFile.
+
+ Returns a tuple containing a set of header filenames to include in
+ the header for the types in unionTypes, a set of header filenames to
+ include in the implementation file for the types in unionTypes, a set
+ of tuples containing a type declaration and a boolean if the type is a
+ struct for member types of the union, a list of traverse methods,
+ unlink methods and a list of union types. These last three lists only
+ contain unique union types.
+ """
+
+ headers = set()
+ implheaders = set()
+ declarations = set()
+ unionStructs = dict()
+ traverseMethods = dict()
+ unlinkMethods = dict()
+
+ for t in unionTypes:
+ name = str(t)
+ if name not in unionStructs:
+ unionStructs[name] = t
+
+ def addHeadersForType(f):
+ if f.nullable():
+ headers.add("mozilla/dom/Nullable.h")
+ isSequence = f.isSequence()
+ if isSequence:
+ # Dealing with sequences requires for-of-compatible
+ # iteration.
+ implheaders.add("js/ForOfIterator.h")
+ # Sequences can always throw "not an object" exceptions.
+ implheaders.add("mozilla/dom/BindingCallContext.h")
+ if typeNeedsRooting(f):
+ headers.add("mozilla/dom/RootedSequence.h")
+ f = f.unroll()
+ if idlTypeNeedsCallContext(f):
+ implheaders.add("mozilla/dom/BindingCallContext.h")
+ if f.isPromise():
+ headers.add("mozilla/dom/Promise.h")
+ # We need ToJSValue to do the Promise to JS conversion.
+ headers.add("mozilla/dom/ToJSValue.h")
+ elif f.isInterface():
+ if f.isSpiderMonkeyInterface():
+ headers.add("js/RootingAPI.h")
+ headers.add("js/Value.h")
+ headers.add("mozilla/dom/TypedArray.h")
+ else:
+ try:
+ typeDesc = config.getDescriptor(f.inner.identifier.name)
+ except NoSuchDescriptorError:
+ return
+ if typeDesc.interface.isCallback() or isSequence:
+ # Callback interfaces always use strong refs, so
+ # we need to include the right header to be able
+ # to Release() in our inlined code.
+ #
+ # Similarly, sequences always contain strong
+ # refs, so we'll need the header to handler
+ # those.
+ headers.add(typeDesc.headerFile)
+ elif typeDesc.interface.identifier.name == "WindowProxy":
+ # In UnionTypes.h we need to see the declaration of the
+ # WindowProxyHolder that we use to store the WindowProxy, so
+ # we have its sizeof and know how big to make our union.
+ headers.add(typeDesc.headerFile)
+ else:
+ declarations.add((typeDesc.nativeType, False))
+ implheaders.add(typeDesc.headerFile)
+ elif f.isDictionary():
+ # For a dictionary, we need to see its declaration in
+ # UnionTypes.h so we have its sizeof and know how big to
+ # make our union.
+ headers.add(CGHeaders.getDeclarationFilename(f.inner))
+ # And if it needs rooting, we need RootedDictionary too
+ if typeNeedsRooting(f):
+ headers.add("mozilla/dom/RootedDictionary.h")
+ elif f.isFloat() and not f.isUnrestricted():
+ # Restricted floats are tested for finiteness
+ implheaders.add("mozilla/FloatingPoint.h")
+ implheaders.add("mozilla/dom/PrimitiveConversions.h")
+ elif f.isEnum():
+ # Need to see the actual definition of the enum,
+ # unfortunately.
+ headers.add(CGHeaders.getDeclarationFilename(f.inner))
+ elif f.isPrimitive():
+ implheaders.add("mozilla/dom/PrimitiveConversions.h")
+ elif f.isCallback():
+ # Callbacks always use strong refs, so we need to include
+ # the right header to be able to Release() in our inlined
+ # code.
+ headers.add(CGHeaders.getDeclarationFilename(f.callback))
+ elif f.isRecord():
+ headers.add("mozilla/dom/Record.h")
+ # And add headers for the type we're parametrized over
+ addHeadersForType(f.inner)
+ # And if it needs rooting, we need RootedRecord too
+ if typeNeedsRooting(f):
+ headers.add("mozilla/dom/RootedRecord.h")
+
+ implheaders.add(CGHeaders.getUnionDeclarationFilename(config, t))
+ for f in t.flatMemberTypes:
+ assert not f.nullable()
+ addHeadersForType(f)
+
+ if idlTypeNeedsCycleCollection(t):
+ declarations.add(
+ ("mozilla::dom::%s" % CGUnionStruct.unionTypeName(t, True), False)
+ )
+ traverseMethods[name] = CGCycleCollectionTraverseForOwningUnionMethod(t)
+ unlinkMethods[name] = CGCycleCollectionUnlinkForOwningUnionMethod(t)
+
+ # The order of items in CGList is important.
+ # Since the union structs friend the unlinkMethods, the forward-declaration
+ # for these methods should come before the class declaration. Otherwise
+ # some compilers treat the friend declaration as a forward-declaration in
+ # the class scope.
+ return (
+ headers,
+ implheaders,
+ declarations,
+ SortedDictValues(traverseMethods),
+ SortedDictValues(unlinkMethods),
+ SortedDictValues(unionStructs),
+ )
+
+
+class Argument:
+ """
+ A class for outputting the type and name of an argument
+ """
+
+ def __init__(self, argType, name, default=None):
+ self.argType = argType
+ self.name = name
+ self.default = default
+
+ def declare(self):
+ string = self.argType + " " + self.name
+ if self.default is not None:
+ string += " = " + self.default
+ return string
+
+ def define(self):
+ return self.argType + " " + self.name
+
+
+class CGAbstractMethod(CGThing):
+ """
+ An abstract class for generating code for a method. Subclasses
+ should override definition_body to create the actual code.
+
+ descriptor is the descriptor for the interface the method is associated with
+
+ name is the name of the method as a string
+
+ returnType is the IDLType of the return value
+
+ args is a list of Argument objects
+
+ inline should be True to generate an inline method, whose body is
+ part of the declaration.
+
+ alwaysInline should be True to generate an inline method annotated with
+ MOZ_ALWAYS_INLINE.
+
+ static should be True to generate a static method, which only has
+ a definition.
+
+ If templateArgs is not None it should be a list of strings containing
+ template arguments, and the function will be templatized using those
+ arguments.
+
+ canRunScript should be True to generate a MOZ_CAN_RUN_SCRIPT annotation.
+
+ signatureOnly should be True to only declare the signature (either in
+ the header, or if static is True in the cpp file).
+ """
+
+ def __init__(
+ self,
+ descriptor,
+ name,
+ returnType,
+ args,
+ inline=False,
+ alwaysInline=False,
+ static=False,
+ templateArgs=None,
+ canRunScript=False,
+ signatureOnly=False,
+ ):
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+ self.name = name
+ self.returnType = returnType
+ self.args = args
+ self.inline = inline
+ self.alwaysInline = alwaysInline
+ self.static = static
+ self.templateArgs = templateArgs
+ self.canRunScript = canRunScript
+ self.signatureOnly = signatureOnly
+
+ def _argstring(self, declare):
+ return ", ".join([a.declare() if declare else a.define() for a in self.args])
+
+ def _template(self):
+ if self.templateArgs is None:
+ return ""
+ return "template <%s>\n" % ", ".join(self.templateArgs)
+
+ def _decorators(self):
+ decorators = []
+ if self.canRunScript:
+ decorators.append("MOZ_CAN_RUN_SCRIPT")
+ if self.alwaysInline:
+ decorators.append("MOZ_ALWAYS_INLINE")
+ elif self.inline:
+ decorators.append("inline")
+ if self.static:
+ decorators.append("static")
+ decorators.append(self.returnType)
+ maybeNewline = " " if self.inline else "\n"
+ return " ".join(decorators) + maybeNewline
+
+ def signature(self):
+ return "%s%s%s(%s);\n" % (
+ self._template(),
+ self._decorators(),
+ self.name,
+ self._argstring(True),
+ )
+
+ def declare(self):
+ if self.static:
+ return ""
+ if self.inline:
+ return self._define(True)
+ return self.signature()
+
+ def indent_body(self, body):
+ """
+ Indent the code returned by self.definition_body(). Most classes
+ simply indent everything two spaces. This is here for
+ CGRegisterProtos, which needs custom indentation.
+ """
+ return indent(body)
+
+ def _define(self, fromDeclare=False):
+ return (
+ self.definition_prologue(fromDeclare)
+ + self.indent_body(self.definition_body())
+ + self.definition_epilogue()
+ )
+
+ def define(self):
+ if self.signatureOnly:
+ if self.static:
+ # self.static makes us not output anything in the header, so output the signature here.
+ return self.signature()
+ return ""
+ return "" if (self.inline and not self.static) else self._define()
+
+ def definition_prologue(self, fromDeclare):
+ error_reporting_label = self.error_reporting_label()
+ if error_reporting_label:
+ # We're going to want a BindingCallContext. Rename our JSContext*
+ # arg accordingly.
+ i = 0
+ while i < len(self.args):
+ arg = self.args[i]
+ if arg.argType == "JSContext*":
+ cxname = arg.name
+ self.args[i] = Argument(arg.argType, "cx_", arg.default)
+ break
+ i += 1
+ if i == len(self.args):
+ raise TypeError("Must have a JSContext* to create a BindingCallContext")
+
+ prologue = "%s%s%s(%s)\n{\n" % (
+ self._template(),
+ self._decorators(),
+ self.name,
+ self._argstring(fromDeclare),
+ )
+ if error_reporting_label:
+ prologue += indent(
+ fill(
+ """
+ BindingCallContext ${cxname}(cx_, "${label}");
+ """,
+ cxname=cxname,
+ label=error_reporting_label,
+ )
+ )
+
+ profiler_label = self.auto_profiler_label()
+ if profiler_label:
+ prologue += indent(profiler_label) + "\n"
+
+ return prologue
+
+ def definition_epilogue(self):
+ return "}\n"
+
+ def definition_body(self):
+ assert False # Override me!
+
+ """
+ Override this method to return a pair of (descriptive string, name of a
+ JSContext* variable) in order to generate a profiler label for this method.
+ """
+
+ def auto_profiler_label(self):
+ return None # Override me!
+
+ """
+ Override this method to return a string to be used as the label for a
+ BindingCallContext. If this does not return None, one of the arguments of
+ this method must be of type 'JSContext*'. Its name will be replaced with
+ 'cx_' and a BindingCallContext named 'cx' will be instantiated with the
+ given label.
+ """
+
+ def error_reporting_label(self):
+ return None # Override me!
+
+
+class CGAbstractStaticMethod(CGAbstractMethod):
+ """
+ Abstract base class for codegen of implementation-only (no
+ declaration) static methods.
+ """
+
+ def __init__(self, descriptor, name, returnType, args, canRunScript=False):
+ CGAbstractMethod.__init__(
+ self,
+ descriptor,
+ name,
+ returnType,
+ args,
+ inline=False,
+ static=True,
+ canRunScript=canRunScript,
+ )
+
+
+class CGAbstractClassHook(CGAbstractStaticMethod):
+ """
+ Meant for implementing JSClass hooks, like Finalize or Trace. Does very raw
+ 'this' unwrapping as it assumes that the unwrapped type is always known.
+ """
+
+ def __init__(self, descriptor, name, returnType, args):
+ CGAbstractStaticMethod.__init__(self, descriptor, name, returnType, args)
+
+ def definition_body_prologue(self):
+ return "%s* self = UnwrapPossiblyNotInitializedDOMObject<%s>(obj);\n" % (
+ self.descriptor.nativeType,
+ self.descriptor.nativeType,
+ )
+
+ def definition_body(self):
+ return self.definition_body_prologue() + self.generate_code()
+
+ def generate_code(self):
+ assert False # Override me!
+
+
+class CGAddPropertyHook(CGAbstractClassHook):
+ """
+ A hook for addProperty, used to preserve our wrapper from GC.
+ """
+
+ def __init__(self, descriptor):
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "obj"),
+ Argument("JS::Handle<jsid>", "id"),
+ Argument("JS::Handle<JS::Value>", "val"),
+ ]
+ CGAbstractClassHook.__init__(
+ self, descriptor, ADDPROPERTY_HOOK_NAME, "bool", args
+ )
+
+ def generate_code(self):
+ assert self.descriptor.wrapperCache
+ # This hook is also called by TryPreserveWrapper on non-nsISupports
+ # cycle collected objects, so if addProperty is ever changed to do
+ # anything more or less than preserve the wrapper, TryPreserveWrapper
+ # will need to be changed.
+ return dedent(
+ """
+ // We don't want to preserve if we don't have a wrapper, and we
+ // obviously can't preserve if we're not initialized.
+ if (self && self->GetWrapperPreserveColor()) {
+ PreserveWrapper(self);
+ }
+ return true;
+ """
+ )
+
+
+class CGGetWrapperCacheHook(CGAbstractClassHook):
+ """
+ A hook for GetWrapperCache, used by HasReleasedWrapper to get the
+ nsWrapperCache pointer for a non-nsISupports object.
+ """
+
+ def __init__(self, descriptor):
+ args = [Argument("JS::Handle<JSObject*>", "obj")]
+ CGAbstractClassHook.__init__(
+ self, descriptor, GETWRAPPERCACHE_HOOK_NAME, "nsWrapperCache*", args
+ )
+
+ def generate_code(self):
+ assert self.descriptor.wrapperCache
+ return dedent(
+ """
+ return self;
+ """
+ )
+
+
+def finalizeHook(descriptor, hookName, gcx, obj):
+ finalize = "JS::SetReservedSlot(%s, DOM_OBJECT_SLOT, JS::UndefinedValue());\n" % obj
+ if descriptor.interface.getExtendedAttribute("LegacyOverrideBuiltIns"):
+ finalize += fill(
+ """
+ // Either our proxy created an expando object or not. If it did,
+ // then we would have preserved ourselves, and hence if we're going
+ // away so is our C++ object and we should reset its expando value.
+ // It's possible that in this situation the C++ object's reflector
+ // pointer has been nulled out, but if not it's pointing to us. If
+ // our proxy did _not_ create an expando object then it's possible
+ // that we're no longer the reflector for our C++ object (and
+ // incremental finalization is finally getting to us), and that in
+ // the meantime the new reflector has created an expando object.
+ // In that case we do NOT want to clear the expando pointer in the
+ // C++ object.
+ //
+ // It's important to do this before we ClearWrapper, of course.
+ JSObject* reflector = self->GetWrapperMaybeDead();
+ if (!reflector || reflector == ${obj}) {
+ self->mExpandoAndGeneration.expando = JS::UndefinedValue();
+ }
+ """,
+ obj=obj,
+ )
+ for m in descriptor.interface.members:
+ if m.isAttr() and m.type.isObservableArray():
+ finalize += fill(
+ """
+ {
+ JS::Value val = JS::GetReservedSlot(obj, ${slot});
+ if (!val.isUndefined()) {
+ JSObject* obj = &val.toObject();
+ js::SetProxyReservedSlot(obj, OBSERVABLE_ARRAY_DOM_INTERFACE_SLOT, JS::UndefinedValue());
+ }
+ }
+ """,
+ slot=memberReservedSlot(m, descriptor),
+ )
+ if descriptor.wrapperCache:
+ finalize += "ClearWrapper(self, self, %s);\n" % obj
+ if descriptor.isGlobal():
+ finalize += "mozilla::dom::FinalizeGlobal(%s, %s);\n" % (gcx, obj)
+ finalize += fill(
+ """
+ if (size_t mallocBytes = BindingJSObjectMallocBytes(self)) {
+ JS::RemoveAssociatedMemory(${obj}, mallocBytes,
+ JS::MemoryUse::DOMBinding);
+ }
+ """,
+ obj=obj,
+ )
+ finalize += "AddForDeferredFinalization<%s>(self);\n" % descriptor.nativeType
+ return CGIfWrapper(CGGeneric(finalize), "self")
+
+
+class CGClassFinalizeHook(CGAbstractClassHook):
+ """
+ A hook for finalize, used to release our native object.
+ """
+
+ def __init__(self, descriptor):
+ args = [Argument("JS::GCContext*", "gcx"), Argument("JSObject*", "obj")]
+ CGAbstractClassHook.__init__(self, descriptor, FINALIZE_HOOK_NAME, "void", args)
+
+ def generate_code(self):
+ return finalizeHook(
+ self.descriptor, self.name, self.args[0].name, self.args[1].name
+ ).define()
+
+
+def objectMovedHook(descriptor, hookName, obj, old):
+ assert descriptor.wrapperCache
+ return fill(
+ """
+ if (self) {
+ UpdateWrapper(self, self, ${obj}, ${old});
+ }
+
+ return 0;
+ """,
+ obj=obj,
+ old=old,
+ )
+
+
+class CGClassObjectMovedHook(CGAbstractClassHook):
+ """
+ A hook for objectMovedOp, used to update the wrapper cache when an object it
+ is holding moves.
+ """
+
+ def __init__(self, descriptor):
+ args = [Argument("JSObject*", "obj"), Argument("JSObject*", "old")]
+ CGAbstractClassHook.__init__(
+ self, descriptor, OBJECT_MOVED_HOOK_NAME, "size_t", args
+ )
+
+ def generate_code(self):
+ return objectMovedHook(
+ self.descriptor, self.name, self.args[0].name, self.args[1].name
+ )
+
+
+def JSNativeArguments():
+ return [
+ Argument("JSContext*", "cx"),
+ Argument("unsigned", "argc"),
+ Argument("JS::Value*", "vp"),
+ ]
+
+
+class CGClassConstructor(CGAbstractStaticMethod):
+ """
+ JS-visible constructor for our objects
+ """
+
+ def __init__(self, descriptor, ctor, name=CONSTRUCT_HOOK_NAME):
+ CGAbstractStaticMethod.__init__(
+ self, descriptor, name, "bool", JSNativeArguments()
+ )
+ self._ctor = ctor
+
+ def define(self):
+ if not self._ctor:
+ return ""
+ return CGAbstractStaticMethod.define(self)
+
+ def definition_body(self):
+ return self.generate_code()
+
+ def generate_code(self):
+ if self._ctor.isHTMLConstructor():
+ # We better have a prototype object. Otherwise our proto
+ # id won't make sense.
+ assert self.descriptor.interface.hasInterfacePrototypeObject()
+ # We also better have a constructor object, if this is
+ # getting called!
+ assert self.descriptor.interface.hasInterfaceObject()
+ # We can't just pass null for the CreateInterfaceObjects callback,
+ # because our newTarget might be in a different compartment, in
+ # which case we'll need to look up constructor objects in that
+ # compartment.
+ return fill(
+ """
+ return HTMLConstructor(cx, argc, vp,
+ constructors::id::${name},
+ prototypes::id::${name},
+ CreateInterfaceObjects);
+ """,
+ name=self.descriptor.name,
+ )
+
+ # If the interface is already SecureContext, notify getConditionList to skip that check,
+ # because the constructor won't be exposed in non-secure contexts to start with.
+ alreadySecureContext = self.descriptor.interface.getExtendedAttribute(
+ "SecureContext"
+ )
+
+ # We want to throw if any of the conditions returned by getConditionList are false.
+ conditionsCheck = ""
+ rawConditions = getRawConditionList(
+ self._ctor, "cx", "obj", alreadySecureContext
+ )
+ if len(rawConditions) > 0:
+ notConditions = " ||\n".join("!" + cond for cond in rawConditions)
+ failedCheckAction = CGGeneric("return ThrowingConstructor(cx, argc, vp);\n")
+ conditionsCheck = (
+ CGIfWrapper(failedCheckAction, notConditions).define() + "\n"
+ )
+
+ # Additionally, we want to throw if a caller does a bareword invocation
+ # of a constructor without |new|.
+ ctorName = GetConstructorNameForReporting(self.descriptor, self._ctor)
+
+ preamble = fill(
+ """
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JS::Rooted<JSObject*> obj(cx, &args.callee());
+ $*{conditionsCheck}
+ if (!args.isConstructing()) {
+ return ThrowConstructorWithoutNew(cx, "${ctorName}");
+ }
+
+ JS::Rooted<JSObject*> desiredProto(cx);
+ if (!GetDesiredProto(cx, args,
+ prototypes::id::${name},
+ CreateInterfaceObjects,
+ &desiredProto)) {
+ return false;
+ }
+ """,
+ conditionsCheck=conditionsCheck,
+ ctorName=ctorName,
+ name=self.descriptor.name,
+ )
+
+ name = self._ctor.identifier.name
+ nativeName = MakeNativeName(self.descriptor.binaryNameFor(name, True))
+ callGenerator = CGMethodCall(
+ nativeName, True, self.descriptor, self._ctor, isConstructor=True
+ )
+ return preamble + "\n" + callGenerator.define()
+
+ def auto_profiler_label(self):
+ return fill(
+ """
+ AUTO_PROFILER_LABEL_DYNAMIC_FAST(
+ "${ctorName}", "constructor", DOM, cx,
+ uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS));
+ """,
+ ctorName=GetConstructorNameForReporting(self.descriptor, self._ctor),
+ )
+
+ def error_reporting_label(self):
+ return CGSpecializedMethod.error_reporting_label_helper(
+ self.descriptor, self._ctor, isConstructor=True
+ )
+
+
+def LegacyFactoryFunctionName(m):
+ return "_" + m.identifier.name
+
+
+class CGLegacyFactoryFunctions(CGThing):
+ def __init__(self, descriptor):
+ self.descriptor = descriptor
+ CGThing.__init__(self)
+
+ def declare(self):
+ return ""
+
+ def define(self):
+ if len(self.descriptor.interface.legacyFactoryFunctions) == 0:
+ return ""
+
+ constructorID = "constructors::id::"
+ if self.descriptor.interface.hasInterfaceObject():
+ constructorID += self.descriptor.name
+ else:
+ constructorID += "_ID_Count"
+
+ namedConstructors = ""
+ for n in self.descriptor.interface.legacyFactoryFunctions:
+ namedConstructors += (
+ '{ "%s", { %s, &sLegacyFactoryFunctionNativePropertyHooks }, %i },\n'
+ % (n.identifier.name, LegacyFactoryFunctionName(n), methodLength(n))
+ )
+
+ return fill(
+ """
+ bool sLegacyFactoryFunctionNativePropertiesInited = true;
+ const NativePropertyHooks sLegacyFactoryFunctionNativePropertyHooks = {
+ nullptr,
+ nullptr,
+ nullptr,
+ { nullptr, nullptr, &sLegacyFactoryFunctionNativePropertiesInited },
+ prototypes::id::${name},
+ ${constructorID},
+ nullptr
+ };
+
+ static const LegacyFactoryFunction namedConstructors[] = {
+ $*{namedConstructors}
+ { nullptr, { nullptr, nullptr }, 0 }
+ };
+ """,
+ name=self.descriptor.name,
+ constructorID=constructorID,
+ namedConstructors=namedConstructors,
+ )
+
+
+def isChromeOnly(m):
+ return m.getExtendedAttribute("ChromeOnly")
+
+
+def prefIdentifier(pref):
+ return pref.replace(".", "_").replace("-", "_")
+
+
+def prefHeader(pref):
+ return "mozilla/StaticPrefs_%s.h" % pref.partition(".")[0]
+
+
+class MemberCondition:
+ """
+ An object representing the condition for a member to actually be
+ exposed. Any of the arguments can be None. If not
+ None, they should have the following types:
+
+ pref: The name of the preference.
+ func: The name of the function.
+ secureContext: A bool indicating whether a secure context is required.
+ nonExposedGlobals: A set of names of globals. Can be empty, in which case
+ it's treated the same way as None.
+ trial: The name of the origin trial.
+ """
+
+ def __init__(
+ self,
+ pref=None,
+ func=None,
+ secureContext=False,
+ nonExposedGlobals=None,
+ trial=None,
+ ):
+ assert pref is None or isinstance(pref, str)
+ assert func is None or isinstance(func, str)
+ assert trial is None or isinstance(trial, str)
+ assert isinstance(secureContext, bool)
+ assert nonExposedGlobals is None or isinstance(nonExposedGlobals, set)
+ self.pref = pref
+ if self.pref:
+ identifier = prefIdentifier(self.pref)
+ self.prefFuncIndex = "WebIDLPrefIndex::" + identifier
+ else:
+ self.prefFuncIndex = "WebIDLPrefIndex::NoPref"
+
+ self.secureContext = secureContext
+
+ def toFuncPtr(val):
+ if val is None:
+ return "nullptr"
+ return "&" + val
+
+ self.func = toFuncPtr(func)
+
+ if nonExposedGlobals:
+ # Nonempty set
+ self.nonExposedGlobals = " | ".join(
+ map(lambda g: "GlobalNames::%s" % g, sorted(nonExposedGlobals))
+ )
+ else:
+ self.nonExposedGlobals = "0"
+
+ if trial:
+ self.trial = "OriginTrial::" + trial
+ else:
+ self.trial = "OriginTrial(0)"
+
+ def __eq__(self, other):
+ return (
+ self.pref == other.pref
+ and self.func == other.func
+ and self.secureContext == other.secureContext
+ and self.nonExposedGlobals == other.nonExposedGlobals
+ and self.trial == other.trial
+ )
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def hasDisablers(self):
+ return (
+ self.pref is not None
+ or self.secureContext
+ or self.func != "nullptr"
+ or self.nonExposedGlobals != "0"
+ or self.trial != "OriginTrial(0)"
+ )
+
+
+class PropertyDefiner:
+ """
+ A common superclass for defining things on prototype objects.
+
+ Subclasses should implement generateArray to generate the actual arrays of
+ things we're defining. They should also set self.chrome to the list of
+ things only exposed to chrome and self.regular to the list of things exposed
+ to both chrome and web pages.
+ """
+
+ def __init__(self, descriptor, name):
+ self.descriptor = descriptor
+ self.name = name
+
+ def hasChromeOnly(self):
+ return len(self.chrome) > 0
+
+ def hasNonChromeOnly(self):
+ return len(self.regular) > 0
+
+ def variableName(self, chrome):
+ if chrome:
+ if self.hasChromeOnly():
+ return "sChrome" + self.name
+ else:
+ if self.hasNonChromeOnly():
+ return "s" + self.name
+ return "nullptr"
+
+ def usedForXrays(self):
+ return self.descriptor.wantsXrays
+
+ def length(self, chrome):
+ return len(self.chrome) if chrome else len(self.regular)
+
+ def __str__(self):
+ # We only need to generate id arrays for things that will end
+ # up used via ResolveProperty or EnumerateProperties.
+ str = self.generateArray(self.regular, self.variableName(False))
+ if self.hasChromeOnly():
+ str += self.generateArray(self.chrome, self.variableName(True))
+ return str
+
+ @staticmethod
+ def getStringAttr(member, name):
+ attr = member.getExtendedAttribute(name)
+ if attr is None:
+ return None
+ # It's a list of strings
+ assert len(attr) == 1
+ assert attr[0] is not None
+ return attr[0]
+
+ @staticmethod
+ def getControllingCondition(interfaceMember, descriptor):
+ interface = descriptor.interface
+ nonExposureSet = interface.exposureSet - interfaceMember.exposureSet
+
+ trial = PropertyDefiner.getStringAttr(interfaceMember, "Trial")
+ if trial and interface.identifier.name in ["Window", "Document"]:
+ raise TypeError(
+ "[Trial] not yet supported for %s.%s, see bug 1757935"
+ % (interface.identifier.name, interfaceMember.identifier.name)
+ )
+
+ return MemberCondition(
+ PropertyDefiner.getStringAttr(interfaceMember, "Pref"),
+ PropertyDefiner.getStringAttr(interfaceMember, "Func"),
+ interfaceMember.getExtendedAttribute("SecureContext") is not None,
+ nonExposureSet,
+ trial,
+ )
+
+ @staticmethod
+ def generatePrefableArrayValues(
+ array,
+ descriptor,
+ specFormatter,
+ specTerminator,
+ getCondition,
+ getDataTuple,
+ switchToCondition=None,
+ ):
+ """
+ This method generates an array of spec entries for interface members. It returns
+ a tuple containing the array of spec entries and the maximum of the number of
+ spec entries per condition.
+
+ array is an array of interface members.
+
+ descriptor is the descriptor for the interface that array contains members of.
+
+ specFormatter is a function that takes a single argument, a tuple,
+ and returns a string, a spec array entry.
+
+ specTerminator is a terminator for the spec array (inserted every time
+ our controlling pref changes and at the end of the array).
+
+ getCondition is a callback function that takes an array entry and
+ returns the corresponding MemberCondition.
+
+ getDataTuple is a callback function that takes an array entry and
+ returns a tuple suitable to be passed to specFormatter.
+
+ switchToCondition is a function that takes a MemberCondition and an array of
+ previously generated spec entries. If None is passed for this function then all
+ the interface members should return the same value from getCondition.
+ """
+
+ def unsupportedSwitchToCondition(condition, specs):
+ # If no specs have been added yet then this is just the first call to
+ # switchToCondition that we call to avoid putting a specTerminator at the
+ # front of the list.
+ if len(specs) == 0:
+ return
+ raise "Not supported"
+
+ if switchToCondition is None:
+ switchToCondition = unsupportedSwitchToCondition
+
+ specs = []
+ numSpecsInCurPrefable = 0
+ maxNumSpecsInPrefable = 0
+
+ # So we won't put a specTerminator at the very front of the list:
+ lastCondition = getCondition(array[0], descriptor)
+
+ switchToCondition(lastCondition, specs)
+
+ for member in array:
+ curCondition = getCondition(member, descriptor)
+ if lastCondition != curCondition:
+ # Terminate previous list
+ specs.append(specTerminator)
+ if numSpecsInCurPrefable > maxNumSpecsInPrefable:
+ maxNumSpecsInPrefable = numSpecsInCurPrefable
+ numSpecsInCurPrefable = 0
+ # And switch to our new condition
+ switchToCondition(curCondition, specs)
+ lastCondition = curCondition
+ # And the actual spec
+ specs.append(specFormatter(getDataTuple(member, descriptor)))
+ numSpecsInCurPrefable += 1
+ if numSpecsInCurPrefable > maxNumSpecsInPrefable:
+ maxNumSpecsInPrefable = numSpecsInCurPrefable
+ specs.append(specTerminator)
+
+ return (specs, maxNumSpecsInPrefable)
+
+ def generatePrefableArray(
+ self,
+ array,
+ name,
+ specFormatter,
+ specTerminator,
+ specType,
+ getCondition,
+ getDataTuple,
+ ):
+ """
+ This method generates our various arrays.
+
+ array is an array of interface members as passed to generateArray
+
+ name is the name as passed to generateArray
+
+ specFormatter is a function that takes a single argument, a tuple,
+ and returns a string, a spec array entry
+
+ specTerminator is a terminator for the spec array (inserted every time
+ our controlling pref changes and at the end of the array)
+
+ specType is the actual typename of our spec
+
+ getCondition is a callback function that takes an array entry and
+ returns the corresponding MemberCondition.
+
+ getDataTuple is a callback function that takes an array entry and
+ returns a tuple suitable to be passed to specFormatter.
+ """
+
+ # We want to generate a single list of specs, but with specTerminator
+ # inserted at every point where the pref name controlling the member
+ # changes. That will make sure the order of the properties as exposed
+ # on the interface and interface prototype objects does not change when
+ # pref control is added to members while still allowing us to define all
+ # the members in the smallest number of JSAPI calls.
+ assert len(array) != 0
+
+ disablers = []
+ prefableSpecs = []
+
+ disablersTemplate = dedent(
+ """
+ static const PrefableDisablers %s_disablers%d = {
+ %s, %s, %s, %s, %s
+ };
+ """
+ )
+ prefableWithDisablersTemplate = " { &%s_disablers%d, &%s_specs[%d] }"
+ prefableWithoutDisablersTemplate = " { nullptr, &%s_specs[%d] }"
+
+ def switchToCondition(condition, specs):
+ # Set up pointers to the new sets of specs inside prefableSpecs
+ if condition.hasDisablers():
+ prefableSpecs.append(
+ prefableWithDisablersTemplate % (name, len(specs), name, len(specs))
+ )
+ disablers.append(
+ disablersTemplate
+ % (
+ name,
+ len(specs),
+ condition.prefFuncIndex,
+ condition.nonExposedGlobals,
+ toStringBool(condition.secureContext),
+ condition.trial,
+ condition.func,
+ )
+ )
+ else:
+ prefableSpecs.append(
+ prefableWithoutDisablersTemplate % (name, len(specs))
+ )
+
+ specs, maxNumSpecsInPrefable = self.generatePrefableArrayValues(
+ array,
+ self.descriptor,
+ specFormatter,
+ specTerminator,
+ getCondition,
+ getDataTuple,
+ switchToCondition,
+ )
+ prefableSpecs.append(" { nullptr, nullptr }")
+
+ specType = "const " + specType
+ arrays = fill(
+ """
+ static ${specType} ${name}_specs[] = {
+ ${specs}
+ };
+
+ ${disablers}
+ static const Prefable<${specType}> ${name}[] = {
+ ${prefableSpecs}
+ };
+
+ """,
+ specType=specType,
+ name=name,
+ disablers="\n".join(disablers),
+ specs=",\n".join(specs),
+ prefableSpecs=",\n".join(prefableSpecs),
+ )
+
+ if self.usedForXrays():
+ arrays = fill(
+ """
+ $*{arrays}
+ static_assert(${numPrefableSpecs} <= 1ull << NUM_BITS_PROPERTY_INFO_PREF_INDEX,
+ "We have a prefable index that is >= (1 << NUM_BITS_PROPERTY_INFO_PREF_INDEX)");
+ static_assert(${maxNumSpecsInPrefable} <= 1ull << NUM_BITS_PROPERTY_INFO_SPEC_INDEX,
+ "We have a spec index that is >= (1 << NUM_BITS_PROPERTY_INFO_SPEC_INDEX)");
+
+ """,
+ arrays=arrays,
+ # Minus 1 because there's a list terminator in prefableSpecs.
+ numPrefableSpecs=len(prefableSpecs) - 1,
+ maxNumSpecsInPrefable=maxNumSpecsInPrefable,
+ )
+
+ return arrays
+
+
+# The length of a method is the minimum of the lengths of the
+# argument lists of all its overloads.
+def overloadLength(arguments):
+ i = len(arguments)
+ while i > 0 and arguments[i - 1].optional:
+ i -= 1
+ return i
+
+
+def methodLength(method):
+ signatures = method.signatures()
+ return min(overloadLength(arguments) for retType, arguments in signatures)
+
+
+def clearableCachedAttrs(descriptor):
+ return (
+ m
+ for m in descriptor.interface.members
+ if m.isAttr() and
+ # Constants should never need clearing!
+ m.dependsOn != "Nothing" and m.slotIndices is not None
+ )
+
+
+def MakeClearCachedValueNativeName(member):
+ return "ClearCached%sValue" % MakeNativeName(member.identifier.name)
+
+
+def IDLToCIdentifier(name):
+ return name.replace("-", "_")
+
+
+def EnumerabilityFlags(member):
+ if member.getExtendedAttribute("NonEnumerable"):
+ return "0"
+ return "JSPROP_ENUMERATE"
+
+
+class MethodDefiner(PropertyDefiner):
+ """
+ A class for defining methods on a prototype object.
+ """
+
+ def __init__(self, descriptor, name, crossOriginOnly, static, unforgeable=False):
+ assert not (static and unforgeable)
+ PropertyDefiner.__init__(self, descriptor, name)
+
+ # FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=772822
+ # We should be able to check for special operations without an
+ # identifier. For now we check if the name starts with __
+
+ # Ignore non-static methods for interfaces without a proto object
+ if descriptor.interface.hasInterfacePrototypeObject() or static:
+ methods = [
+ m
+ for m in descriptor.interface.members
+ if m.isMethod()
+ and m.isStatic() == static
+ and MemberIsLegacyUnforgeable(m, descriptor) == unforgeable
+ and (
+ not crossOriginOnly or m.getExtendedAttribute("CrossOriginCallable")
+ )
+ and not m.isIdentifierLess()
+ and not m.getExtendedAttribute("Unexposed")
+ ]
+ else:
+ methods = []
+ self.chrome = []
+ self.regular = []
+ for m in methods:
+ method = self.methodData(m, descriptor)
+
+ if m.isStatic():
+ method["nativeName"] = CppKeywords.checkMethodName(
+ IDLToCIdentifier(m.identifier.name)
+ )
+
+ if isChromeOnly(m):
+ self.chrome.append(method)
+ else:
+ self.regular.append(method)
+
+ # 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
+ )
+
+ # Check whether we need to output an @@iterator due to having an indexed
+ # getter. We only do this while outputting non-static and
+ # non-unforgeable methods, since the @@iterator function will be
+ # neither.
+ if not static and not unforgeable and descriptor.supportsIndexedProperties():
+ 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,
+ "selfHostedName": "$ArrayValues",
+ "length": 0,
+ "flags": "0", # Not enumerable, per spec.
+ "condition": MemberCondition(),
+ }
+ )
+
+ # Generate the keys/values/entries aliases for value iterables.
+ maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable
+ if (
+ not static
+ and not unforgeable
+ and maplikeOrSetlikeOrIterable
+ and maplikeOrSetlikeOrIterable.isIterable()
+ and maplikeOrSetlikeOrIterable.isValueIterator()
+ ):
+ # Add our keys/values/entries/forEach
+ self.regular.append(
+ {
+ "name": "keys",
+ "methodInfo": False,
+ "selfHostedName": "ArrayKeys",
+ "length": 0,
+ "flags": "JSPROP_ENUMERATE",
+ "condition": PropertyDefiner.getControllingCondition(
+ maplikeOrSetlikeOrIterable, descriptor
+ ),
+ }
+ )
+ self.regular.append(
+ {
+ "name": "values",
+ "methodInfo": False,
+ "selfHostedName": "$ArrayValues",
+ "length": 0,
+ "flags": "JSPROP_ENUMERATE",
+ "condition": PropertyDefiner.getControllingCondition(
+ maplikeOrSetlikeOrIterable, descriptor
+ ),
+ }
+ )
+ self.regular.append(
+ {
+ "name": "entries",
+ "methodInfo": False,
+ "selfHostedName": "ArrayEntries",
+ "length": 0,
+ "flags": "JSPROP_ENUMERATE",
+ "condition": PropertyDefiner.getControllingCondition(
+ maplikeOrSetlikeOrIterable, descriptor
+ ),
+ }
+ )
+ self.regular.append(
+ {
+ "name": "forEach",
+ "methodInfo": False,
+ "selfHostedName": "ArrayForEach",
+ "length": 1,
+ "flags": "JSPROP_ENUMERATE",
+ "condition": PropertyDefiner.getControllingCondition(
+ maplikeOrSetlikeOrIterable, descriptor
+ ),
+ }
+ )
+
+ if not static:
+ stringifier = descriptor.operations["Stringifier"]
+ if stringifier and unforgeable == MemberIsLegacyUnforgeable(
+ stringifier, descriptor
+ ):
+ toStringDesc = {
+ "name": GetWebExposedName(stringifier, descriptor),
+ "nativeName": stringifier.identifier.name,
+ "length": 0,
+ "flags": "JSPROP_ENUMERATE",
+ "condition": PropertyDefiner.getControllingCondition(
+ stringifier, descriptor
+ ),
+ }
+ if isChromeOnly(stringifier):
+ self.chrome.append(toStringDesc)
+ else:
+ self.regular.append(toStringDesc)
+ if unforgeable and descriptor.interface.getExtendedAttribute(
+ "LegacyUnforgeable"
+ ):
+ # Synthesize our valueOf method
+ self.regular.append(
+ {
+ "name": "valueOf",
+ "selfHostedName": "Object_valueOf",
+ "methodInfo": False,
+ "length": 0,
+ "flags": "0", # readonly/permanent added automatically.
+ "condition": MemberCondition(),
+ }
+ )
+
+ if descriptor.interface.isJSImplemented():
+ if static:
+ if descriptor.interface.hasInterfaceObject():
+ self.chrome.append(
+ {
+ "name": "_create",
+ "nativeName": ("%s::_Create" % descriptor.name),
+ "methodInfo": False,
+ "length": 2,
+ "flags": "0",
+ "condition": MemberCondition(),
+ }
+ )
+
+ self.unforgeable = unforgeable
+
+ if static:
+ if not descriptor.interface.hasInterfaceObject():
+ # static methods go on the interface object
+ assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
+ else:
+ if not descriptor.interface.hasInterfacePrototypeObject():
+ # non-static methods go on the interface prototype object
+ assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
+
+ @staticmethod
+ def methodData(m, descriptor, overrideFlags=None):
+ return {
+ "name": m.identifier.name,
+ "methodInfo": not m.isStatic(),
+ "length": methodLength(m),
+ "flags": EnumerabilityFlags(m)
+ if (overrideFlags is None)
+ else overrideFlags,
+ "condition": PropertyDefiner.getControllingCondition(m, descriptor),
+ "allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable"),
+ "returnsPromise": m.returnsPromise(),
+ "hasIteratorAlias": "@@iterator" in m.aliases,
+ }
+
+ @staticmethod
+ def formatSpec(fields):
+ if fields[0].startswith("@@"):
+ fields = (fields[0][2:],) + fields[1:]
+ return " JS_SYM_FNSPEC(%s, %s, %s, %s, %s, %s)" % fields
+ return ' JS_FNSPEC("%s", %s, %s, %s, %s, %s)' % fields
+
+ @staticmethod
+ def specData(m, descriptor, unforgeable=False):
+ def flags(m, unforgeable):
+ unforgeable = " | JSPROP_PERMANENT | JSPROP_READONLY" if unforgeable else ""
+ return m["flags"] + unforgeable
+
+ if "selfHostedName" in m:
+ selfHostedName = '"%s"' % m["selfHostedName"]
+ assert not m.get("methodInfo", True)
+ accessor = "nullptr"
+ jitinfo = "nullptr"
+ else:
+ selfHostedName = "nullptr"
+ # 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):
+ if m.get("returnsPromise", False):
+ exceptionPolicy = "ConvertExceptionsToPromises"
+ else:
+ exceptionPolicy = "ThrowExceptions"
+
+ # Cast this in case the methodInfo is a
+ # JSTypedMethodJitInfo.
+ jitinfo = (
+ "reinterpret_cast<const JSJitInfo*>(&%s_methodinfo)" % accessor
+ )
+ if m.get("allowCrossOriginThis", False):
+ accessor = (
+ "(GenericMethod<CrossOriginThisPolicy, %s>)" % exceptionPolicy
+ )
+ elif descriptor.interface.hasDescendantWithCrossOriginMembers:
+ accessor = (
+ "(GenericMethod<MaybeCrossOriginObjectThisPolicy, %s>)"
+ % exceptionPolicy
+ )
+ elif descriptor.interface.isOnGlobalProtoChain():
+ accessor = (
+ "(GenericMethod<MaybeGlobalThisPolicy, %s>)" % exceptionPolicy
+ )
+ else:
+ accessor = "(GenericMethod<NormalThisPolicy, %s>)" % exceptionPolicy
+ else:
+ if m.get("returnsPromise", False):
+ jitinfo = "&%s_methodinfo" % accessor
+ accessor = "StaticMethodPromiseWrapper"
+ else:
+ jitinfo = "nullptr"
+
+ return (
+ m["name"],
+ accessor,
+ jitinfo,
+ m["length"],
+ flags(m, unforgeable),
+ selfHostedName,
+ )
+
+ @staticmethod
+ def condition(m, d):
+ return m["condition"]
+
+ def generateArray(self, array, name):
+ if len(array) == 0:
+ return ""
+
+ return self.generatePrefableArray(
+ array,
+ name,
+ self.formatSpec,
+ " JS_FS_END",
+ "JSFunctionSpec",
+ self.condition,
+ functools.partial(self.specData, unforgeable=self.unforgeable),
+ )
+
+
+class AttrDefiner(PropertyDefiner):
+ def __init__(self, descriptor, name, crossOriginOnly, static, unforgeable=False):
+ assert not (static and unforgeable)
+ PropertyDefiner.__init__(self, descriptor, name)
+ self.name = name
+ # Ignore non-static attributes for interfaces without a proto object
+ if descriptor.interface.hasInterfacePrototypeObject() or static:
+ idlAttrs = [
+ m
+ for m in descriptor.interface.members
+ if m.isAttr()
+ and m.isStatic() == static
+ and MemberIsLegacyUnforgeable(m, descriptor) == unforgeable
+ and (
+ not crossOriginOnly
+ or m.getExtendedAttribute("CrossOriginReadable")
+ or m.getExtendedAttribute("CrossOriginWritable")
+ )
+ ]
+ else:
+ idlAttrs = []
+
+ attributes = []
+ for attr in idlAttrs:
+ attributes.extend(self.attrData(attr, unforgeable))
+ self.chrome = [m for m in attributes if isChromeOnly(m["attr"])]
+ self.regular = [m for m in attributes if not isChromeOnly(m["attr"])]
+ self.static = static
+
+ if static:
+ if not descriptor.interface.hasInterfaceObject():
+ # static attributes go on the interface object
+ assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
+ else:
+ if not descriptor.interface.hasInterfacePrototypeObject():
+ # non-static attributes go on the interface prototype object
+ assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
+
+ @staticmethod
+ def attrData(attr, unforgeable=False, overrideFlags=None):
+ if overrideFlags is None:
+ permanent = " | JSPROP_PERMANENT" if unforgeable else ""
+ flags = EnumerabilityFlags(attr) + permanent
+ else:
+ flags = overrideFlags
+ return (
+ {"name": name, "attr": attr, "flags": flags}
+ for name in [attr.identifier.name] + attr.bindingAliases
+ )
+
+ @staticmethod
+ def condition(m, d):
+ return PropertyDefiner.getControllingCondition(m["attr"], d)
+
+ @staticmethod
+ def specData(entry, descriptor, static=False, crossOriginOnly=False):
+ def getter(attr):
+ if crossOriginOnly and not attr.getExtendedAttribute("CrossOriginReadable"):
+ return "nullptr, nullptr"
+ if static:
+ if attr.type.isPromise():
+ raise TypeError(
+ "Don't know how to handle "
+ "static Promise-returning "
+ "attribute %s.%s" % (descriptor.name, attr.identifier.name)
+ )
+ accessor = "get_" + IDLToCIdentifier(attr.identifier.name)
+ jitinfo = "nullptr"
+ else:
+ if attr.type.isPromise():
+ exceptionPolicy = "ConvertExceptionsToPromises"
+ else:
+ exceptionPolicy = "ThrowExceptions"
+
+ if attr.hasLegacyLenientThis():
+ if attr.getExtendedAttribute("CrossOriginReadable"):
+ raise TypeError(
+ "Can't handle lenient cross-origin "
+ "readable attribute %s.%s"
+ % (descriptor.name, attr.identifier.name)
+ )
+ if descriptor.interface.hasDescendantWithCrossOriginMembers:
+ accessor = (
+ "GenericGetter<MaybeCrossOriginObjectLenientThisPolicy, %s>"
+ % exceptionPolicy
+ )
+ else:
+ accessor = (
+ "GenericGetter<LenientThisPolicy, %s>" % exceptionPolicy
+ )
+ elif attr.getExtendedAttribute("CrossOriginReadable"):
+ accessor = (
+ "GenericGetter<CrossOriginThisPolicy, %s>" % exceptionPolicy
+ )
+ elif descriptor.interface.hasDescendantWithCrossOriginMembers:
+ accessor = (
+ "GenericGetter<MaybeCrossOriginObjectThisPolicy, %s>"
+ % exceptionPolicy
+ )
+ elif descriptor.interface.isOnGlobalProtoChain():
+ accessor = (
+ "GenericGetter<MaybeGlobalThisPolicy, %s>" % exceptionPolicy
+ )
+ else:
+ accessor = "GenericGetter<NormalThisPolicy, %s>" % exceptionPolicy
+ jitinfo = "&%s_getterinfo" % IDLToCIdentifier(attr.identifier.name)
+ return "%s, %s" % (accessor, jitinfo)
+
+ def setter(attr):
+ if (
+ attr.readonly
+ and attr.getExtendedAttribute("PutForwards") is None
+ and attr.getExtendedAttribute("Replaceable") is None
+ and attr.getExtendedAttribute("LegacyLenientSetter") is None
+ ):
+ return "nullptr, nullptr"
+ if crossOriginOnly and not attr.getExtendedAttribute("CrossOriginWritable"):
+ return "nullptr, nullptr"
+ if static:
+ accessor = "set_" + IDLToCIdentifier(attr.identifier.name)
+ jitinfo = "nullptr"
+ else:
+ if attr.hasLegacyLenientThis():
+ if attr.getExtendedAttribute("CrossOriginWritable"):
+ raise TypeError(
+ "Can't handle lenient cross-origin "
+ "writable attribute %s.%s"
+ % (descriptor.name, attr.identifier.name)
+ )
+ if descriptor.interface.hasDescendantWithCrossOriginMembers:
+ accessor = (
+ "GenericSetter<MaybeCrossOriginObjectLenientThisPolicy>"
+ )
+ else:
+ accessor = "GenericSetter<LenientThisPolicy>"
+ elif attr.getExtendedAttribute("CrossOriginWritable"):
+ accessor = "GenericSetter<CrossOriginThisPolicy>"
+ elif descriptor.interface.hasDescendantWithCrossOriginMembers:
+ accessor = "GenericSetter<MaybeCrossOriginObjectThisPolicy>"
+ elif descriptor.interface.isOnGlobalProtoChain():
+ accessor = "GenericSetter<MaybeGlobalThisPolicy>"
+ else:
+ accessor = "GenericSetter<NormalThisPolicy>"
+ jitinfo = "&%s_setterinfo" % IDLToCIdentifier(attr.identifier.name)
+ return "%s, %s" % (accessor, jitinfo)
+
+ name, attr, flags = entry["name"], entry["attr"], entry["flags"]
+ return (name, flags, getter(attr), setter(attr))
+
+ @staticmethod
+ def formatSpec(fields):
+ return ' JSPropertySpec::nativeAccessors("%s", %s, %s, %s)' % fields
+
+ def generateArray(self, array, name):
+ if len(array) == 0:
+ return ""
+
+ return self.generatePrefableArray(
+ array,
+ name,
+ self.formatSpec,
+ " JS_PS_END",
+ "JSPropertySpec",
+ self.condition,
+ functools.partial(self.specData, static=self.static),
+ )
+
+
+class ConstDefiner(PropertyDefiner):
+ """
+ A class for definining constants on the interface object
+ """
+
+ def __init__(self, descriptor, name):
+ PropertyDefiner.__init__(self, descriptor, name)
+ self.name = name
+ constants = [m for m in descriptor.interface.members if m.isConst()]
+ self.chrome = [m for m in constants if isChromeOnly(m)]
+ self.regular = [m for m in constants if not isChromeOnly(m)]
+
+ def generateArray(self, array, name):
+ if len(array) == 0:
+ return ""
+
+ def specData(const, descriptor):
+ return (const.identifier.name, convertConstIDLValueToJSVal(const.value))
+
+ return self.generatePrefableArray(
+ array,
+ name,
+ lambda fields: ' { "%s", %s }' % fields,
+ " { 0, JS::UndefinedValue() }",
+ "ConstantSpec",
+ PropertyDefiner.getControllingCondition,
+ specData,
+ )
+
+
+class PropertyArrays:
+ def __init__(self, descriptor, crossOriginOnly=False):
+ self.staticMethods = MethodDefiner(
+ descriptor, "StaticMethods", crossOriginOnly, static=True
+ )
+ self.staticAttrs = AttrDefiner(
+ descriptor, "StaticAttributes", crossOriginOnly, static=True
+ )
+ self.methods = MethodDefiner(
+ descriptor, "Methods", crossOriginOnly, static=False
+ )
+ self.attrs = AttrDefiner(
+ descriptor, "Attributes", crossOriginOnly, static=False
+ )
+ self.unforgeableMethods = MethodDefiner(
+ descriptor,
+ "UnforgeableMethods",
+ crossOriginOnly,
+ static=False,
+ unforgeable=True,
+ )
+ self.unforgeableAttrs = AttrDefiner(
+ descriptor,
+ "UnforgeableAttributes",
+ crossOriginOnly,
+ static=False,
+ unforgeable=True,
+ )
+ self.consts = ConstDefiner(descriptor, "Constants")
+
+ @staticmethod
+ def arrayNames():
+ return [
+ "staticMethods",
+ "staticAttrs",
+ "methods",
+ "attrs",
+ "unforgeableMethods",
+ "unforgeableAttrs",
+ "consts",
+ ]
+
+ def hasChromeOnly(self):
+ return any(getattr(self, a).hasChromeOnly() for a in self.arrayNames())
+
+ def hasNonChromeOnly(self):
+ return any(getattr(self, a).hasNonChromeOnly() for a in self.arrayNames())
+
+ def __str__(self):
+ define = ""
+ for array in self.arrayNames():
+ define += str(getattr(self, array))
+ return define
+
+
+class CGConstDefinition(CGThing):
+ """
+ Given a const member of an interface, return the C++ static const definition
+ for the member. Should be part of the interface namespace in the header
+ file.
+ """
+
+ def __init__(self, member):
+ assert (
+ member.isConst()
+ and member.value.type.isPrimitive()
+ and not member.value.type.nullable()
+ )
+
+ name = CppKeywords.checkMethodName(IDLToCIdentifier(member.identifier.name))
+ tag = member.value.type.tag()
+ value = member.value.value
+ if tag == IDLType.Tags.bool:
+ value = toStringBool(member.value.value)
+ self.const = "static const %s %s = %s;" % (builtinNames[tag], name, value)
+
+ def declare(self):
+ return self.const
+
+ def define(self):
+ return ""
+
+ def deps(self):
+ return []
+
+
+class CGNativeProperties(CGList):
+ def __init__(self, descriptor, properties):
+ def generateNativeProperties(name, chrome):
+ def check(p):
+ return p.hasChromeOnly() if chrome else p.hasNonChromeOnly()
+
+ nativePropsInts = []
+ nativePropsPtrs = []
+ nativePropsDuos = []
+
+ duosOffset = 0
+ idsOffset = 0
+ for array in properties.arrayNames():
+ propertyArray = getattr(properties, array)
+ if check(propertyArray):
+ varName = propertyArray.variableName(chrome)
+ bitfields = "true, %d /* %s */" % (duosOffset, varName)
+ duosOffset += 1
+ nativePropsInts.append(CGGeneric(bitfields))
+
+ if propertyArray.usedForXrays():
+ ids = "&%s_propertyInfos[%d]" % (name, idsOffset)
+ idsOffset += propertyArray.length(chrome)
+ else:
+ ids = "nullptr"
+ duo = "{ %s, %s }" % (varName, ids)
+ nativePropsDuos.append(CGGeneric(duo))
+ else:
+ bitfields = "false, 0"
+ nativePropsInts.append(CGGeneric(bitfields))
+
+ iteratorAliasIndex = -1
+ for index, item in enumerate(properties.methods.regular):
+ if item.get("hasIteratorAlias"):
+ iteratorAliasIndex = index
+ break
+ nativePropsInts.append(CGGeneric(str(iteratorAliasIndex)))
+
+ nativePropsDuos = [
+ CGWrapper(
+ CGIndenter(CGList(nativePropsDuos, ",\n")), pre="{\n", post="\n}"
+ )
+ ]
+
+ pre = "static const NativePropertiesN<%d> %s = {\n" % (duosOffset, name)
+ post = "\n};\n"
+ if descriptor.wantsXrays:
+ pre = fill(
+ """
+ static uint16_t ${name}_sortedPropertyIndices[${size}];
+ static PropertyInfo ${name}_propertyInfos[${size}];
+
+ $*{pre}
+ """,
+ name=name,
+ size=idsOffset,
+ pre=pre,
+ )
+ if iteratorAliasIndex > 0:
+ # The iteratorAliasMethodIndex is a signed integer, so the
+ # max value it can store is 2^(nbits-1)-1.
+ post = fill(
+ """
+ $*{post}
+ static_assert(${iteratorAliasIndex} < 1ull << (CHAR_BIT * sizeof(${name}.iteratorAliasMethodIndex) - 1),
+ "We have an iterator alias index that is oversized");
+ """,
+ post=post,
+ iteratorAliasIndex=iteratorAliasIndex,
+ name=name,
+ )
+ post = fill(
+ """
+ $*{post}
+ static_assert(${propertyInfoCount} < 1ull << (CHAR_BIT * sizeof(${name}.propertyInfoCount)),
+ "We have a property info count that is oversized");
+ """,
+ post=post,
+ propertyInfoCount=idsOffset,
+ name=name,
+ )
+ nativePropsInts.append(CGGeneric("%d" % idsOffset))
+ nativePropsPtrs.append(CGGeneric("%s_sortedPropertyIndices" % name))
+ else:
+ nativePropsInts.append(CGGeneric("0"))
+ nativePropsPtrs.append(CGGeneric("nullptr"))
+ nativeProps = nativePropsInts + nativePropsPtrs + nativePropsDuos
+ return CGWrapper(CGIndenter(CGList(nativeProps, ",\n")), pre=pre, post=post)
+
+ nativeProperties = []
+ if properties.hasNonChromeOnly():
+ nativeProperties.append(
+ generateNativeProperties("sNativeProperties", False)
+ )
+ if properties.hasChromeOnly():
+ nativeProperties.append(
+ generateNativeProperties("sChromeOnlyNativeProperties", True)
+ )
+
+ CGList.__init__(self, nativeProperties, "\n")
+
+ def declare(self):
+ return ""
+
+ def define(self):
+ return CGList.define(self)
+
+
+class CGCollectJSONAttributesMethod(CGAbstractMethod):
+ """
+ Generate the CollectJSONAttributes method for an interface descriptor
+ """
+
+ def __init__(self, descriptor, toJSONMethod):
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "obj"),
+ Argument("%s*" % descriptor.nativeType, "self"),
+ Argument("JS::Rooted<JSObject*>&", "result"),
+ ]
+ CGAbstractMethod.__init__(
+ self, descriptor, "CollectJSONAttributes", "bool", args, canRunScript=True
+ )
+ self.toJSONMethod = toJSONMethod
+
+ def definition_body(self):
+ ret = ""
+ interface = self.descriptor.interface
+ toJSONCondition = PropertyDefiner.getControllingCondition(
+ self.toJSONMethod, self.descriptor
+ )
+ needUnwrappedObj = False
+ for m in interface.members:
+ if m.isAttr() and not m.isStatic() and m.type.isJSONType():
+ getAndDefine = fill(
+ """
+ JS::Rooted<JS::Value> temp(cx);
+ if (!get_${name}(cx, obj, self, JSJitGetterCallArgs(&temp))) {
+ return false;
+ }
+ if (!JS_DefineProperty(cx, result, "${name}", temp, JSPROP_ENUMERATE)) {
+ return false;
+ }
+ """,
+ name=IDLToCIdentifier(m.identifier.name),
+ )
+ # Make sure we don't include things which are supposed to be
+ # disabled. Things that either don't have disablers or whose
+ # disablers match the disablers for our toJSON method can't
+ # possibly be disabled, but other things might be.
+ condition = PropertyDefiner.getControllingCondition(m, self.descriptor)
+ if condition.hasDisablers() and condition != toJSONCondition:
+ needUnwrappedObj = True
+ ret += fill(
+ """
+ // This is unfortunately a linear scan through sAttributes, but we
+ // only do it for things which _might_ be disabled, which should
+ // help keep the performance problems down.
+ if (IsGetterEnabled(cx, unwrappedObj, (JSJitGetterOp)get_${name}, sAttributes)) {
+ $*{getAndDefine}
+ }
+ """,
+ name=IDLToCIdentifier(m.identifier.name),
+ getAndDefine=getAndDefine,
+ )
+ else:
+ ret += fill(
+ """
+ { // scope for "temp"
+ $*{getAndDefine}
+ }
+ """,
+ getAndDefine=getAndDefine,
+ )
+ ret += "return true;\n"
+
+ if needUnwrappedObj:
+ # If we started allowing cross-origin objects here, we'd need to
+ # use CheckedUnwrapDynamic and figure out whether it makes sense.
+ # But in practice no one is trying to add toJSON methods to those,
+ # so let's just guard against it.
+ assert not self.descriptor.isMaybeCrossOriginObject()
+ ret = fill(
+ """
+ JS::Rooted<JSObject*> unwrappedObj(cx, js::CheckedUnwrapStatic(obj));
+ if (!unwrappedObj) {
+ // How did that happen? We managed to get called with that
+ // object as "this"! Just give up on sanity.
+ return false;
+ }
+
+ $*{ret}
+ """,
+ ret=ret,
+ )
+
+ return ret
+
+
+class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
+ """
+ Generate the CreateInterfaceObjects method for an interface descriptor.
+
+ properties should be a PropertyArrays instance.
+ """
+
+ def __init__(
+ self, descriptor, properties, haveUnscopables, haveLegacyWindowAliases, static
+ ):
+ args = [
+ Argument("JSContext*", "aCx"),
+ Argument("JS::Handle<JSObject*>", "aGlobal"),
+ Argument("ProtoAndIfaceCache&", "aProtoAndIfaceCache"),
+ Argument("bool", "aDefineOnGlobal"),
+ ]
+ CGAbstractMethod.__init__(
+ self, descriptor, "CreateInterfaceObjects", "void", args, static=static
+ )
+ self.properties = properties
+ self.haveUnscopables = haveUnscopables
+ self.haveLegacyWindowAliases = haveLegacyWindowAliases
+
+ def definition_body(self):
+ (protoGetter, protoHandleGetter) = InterfacePrototypeObjectProtoGetter(
+ self.descriptor
+ )
+ if protoHandleGetter is None:
+ parentProtoType = "Rooted"
+ getParentProto = "aCx, " + protoGetter
+ else:
+ parentProtoType = "Handle"
+ getParentProto = protoHandleGetter
+ getParentProto = getParentProto + "(aCx)"
+
+ (protoGetter, protoHandleGetter) = InterfaceObjectProtoGetter(self.descriptor)
+ if protoHandleGetter is None:
+ getConstructorProto = "aCx, " + protoGetter
+ constructorProtoType = "Rooted"
+ else:
+ getConstructorProto = protoHandleGetter
+ constructorProtoType = "Handle"
+ getConstructorProto += "(aCx)"
+
+ needInterfaceObject = self.descriptor.interface.hasInterfaceObject()
+ needInterfacePrototypeObject = (
+ self.descriptor.interface.hasInterfacePrototypeObject()
+ )
+
+ # if we don't need to create anything, why are we generating this?
+ assert needInterfaceObject or needInterfacePrototypeObject
+
+ getParentProto = fill(
+ """
+ JS::${type}<JSObject*> parentProto(${getParentProto});
+ if (!parentProto) {
+ return;
+ }
+ """,
+ type=parentProtoType,
+ getParentProto=getParentProto,
+ )
+
+ getConstructorProto = fill(
+ """
+ JS::${type}<JSObject*> constructorProto(${getConstructorProto});
+ if (!constructorProto) {
+ return;
+ }
+ """,
+ type=constructorProtoType,
+ getConstructorProto=getConstructorProto,
+ )
+
+ if self.descriptor.interface.ctor():
+ constructArgs = methodLength(self.descriptor.interface.ctor())
+ isConstructorChromeOnly = isChromeOnly(self.descriptor.interface.ctor())
+ else:
+ constructArgs = 0
+ isConstructorChromeOnly = False
+ if len(self.descriptor.interface.legacyFactoryFunctions) > 0:
+ namedConstructors = "namedConstructors"
+ else:
+ namedConstructors = "nullptr"
+
+ if needInterfacePrototypeObject:
+ protoClass = "&sPrototypeClass.mBase"
+ protoCache = (
+ "&aProtoAndIfaceCache.EntrySlotOrCreate(prototypes::id::%s)"
+ % self.descriptor.name
+ )
+ parentProto = "parentProto"
+ getParentProto = CGGeneric(getParentProto)
+ else:
+ protoClass = "nullptr"
+ protoCache = "nullptr"
+ parentProto = "nullptr"
+ getParentProto = None
+
+ if needInterfaceObject:
+ interfaceClass = "&sInterfaceObjectClass.mBase"
+ interfaceCache = (
+ "&aProtoAndIfaceCache.EntrySlotOrCreate(constructors::id::%s)"
+ % self.descriptor.name
+ )
+ getConstructorProto = CGGeneric(getConstructorProto)
+ constructorProto = "constructorProto"
+ else:
+ # We don't have slots to store the legacy factory functions.
+ assert len(self.descriptor.interface.legacyFactoryFunctions) == 0
+ interfaceClass = "nullptr"
+ interfaceCache = "nullptr"
+ getConstructorProto = None
+ constructorProto = "nullptr"
+
+ isGlobal = self.descriptor.isGlobal() is not None
+ if self.properties.hasNonChromeOnly():
+ properties = "sNativeProperties.Upcast()"
+ else:
+ properties = "nullptr"
+ if self.properties.hasChromeOnly():
+ chromeProperties = "sChromeOnlyNativeProperties.Upcast()"
+ else:
+ chromeProperties = "nullptr"
+
+ # We use getClassName here. This should be the right thing to pass as
+ # the name argument to CreateInterfaceObjects. This is generally the
+ # interface identifier, except for the synthetic interfaces created for
+ # the default iterator objects. If needInterfaceObject is true then
+ # we'll use the name to install a property on the global object, so
+ # there shouldn't be any spaces in the name.
+ name = self.descriptor.interface.getClassName()
+ assert not (needInterfaceObject and " " in name)
+
+ call = fill(
+ """
+ JS::Heap<JSObject*>* protoCache = ${protoCache};
+ JS::Heap<JSObject*>* interfaceCache = ${interfaceCache};
+ dom::CreateInterfaceObjects(aCx, aGlobal, ${parentProto},
+ ${protoClass}, protoCache,
+ ${constructorProto}, ${interfaceClass}, ${constructArgs}, ${isConstructorChromeOnly}, ${namedConstructors},
+ interfaceCache,
+ ${properties},
+ ${chromeProperties},
+ "${name}", aDefineOnGlobal,
+ ${unscopableNames},
+ ${isGlobal},
+ ${legacyWindowAliases},
+ ${isNamespace});
+ """,
+ protoClass=protoClass,
+ parentProto=parentProto,
+ protoCache=protoCache,
+ constructorProto=constructorProto,
+ interfaceClass=interfaceClass,
+ constructArgs=constructArgs,
+ isConstructorChromeOnly=toStringBool(isConstructorChromeOnly),
+ namedConstructors=namedConstructors,
+ interfaceCache=interfaceCache,
+ properties=properties,
+ chromeProperties=chromeProperties,
+ name=name,
+ unscopableNames="unscopableNames" if self.haveUnscopables else "nullptr",
+ isGlobal=toStringBool(isGlobal),
+ legacyWindowAliases="legacyWindowAliases"
+ if self.haveLegacyWindowAliases
+ else "nullptr",
+ isNamespace=toStringBool(self.descriptor.interface.isNamespace()),
+ )
+
+ # If we fail after here, we must clear interface and prototype caches
+ # using this code: intermediate failure must not expose the interface in
+ # partially-constructed state. Note that every case after here needs an
+ # interface prototype object.
+ failureCode = dedent(
+ """
+ *protoCache = nullptr;
+ if (interfaceCache) {
+ *interfaceCache = nullptr;
+ }
+ return;
+ """
+ )
+
+ needProtoVar = False
+
+ aliasedMembers = [
+ m for m in self.descriptor.interface.members if m.isMethod() and m.aliases
+ ]
+ if aliasedMembers:
+ assert needInterfacePrototypeObject
+
+ def defineAlias(alias):
+ if alias == "@@iterator" or alias == "@@asyncIterator":
+ name = alias[2:]
+
+ symbolJSID = (
+ "JS::GetWellKnownSymbolKey(aCx, JS::SymbolCode::%s)" % name
+ )
+ prop = "%sId" % name
+ getSymbolJSID = CGGeneric(
+ fill(
+ "JS::Rooted<jsid> ${prop}(aCx, ${symbolJSID});",
+ prop=prop,
+ symbolJSID=symbolJSID,
+ )
+ )
+ defineFn = "JS_DefinePropertyById"
+ enumFlags = "0" # Not enumerable, per spec.
+ elif alias.startswith("@@"):
+ raise TypeError(
+ "Can't handle any well-known Symbol other than @@iterator and @@asyncIterator"
+ )
+ else:
+ getSymbolJSID = None
+ defineFn = "JS_DefineProperty"
+ prop = '"%s"' % alias
+ # XXX If we ever create non-enumerable properties that can
+ # be aliased, we should consider making the aliases
+ # match the enumerability of the property being aliased.
+ enumFlags = "JSPROP_ENUMERATE"
+ return CGList(
+ [
+ getSymbolJSID,
+ CGGeneric(
+ fill(
+ """
+ if (!${defineFn}(aCx, proto, ${prop}, aliasedVal, ${enumFlags})) {
+ $*{failureCode}
+ }
+ """,
+ defineFn=defineFn,
+ prop=prop,
+ enumFlags=enumFlags,
+ failureCode=failureCode,
+ )
+ ),
+ ],
+ "\n",
+ )
+
+ def defineAliasesFor(m):
+ return CGList(
+ [
+ CGGeneric(
+ fill(
+ """
+ if (!JS_GetProperty(aCx, proto, \"${prop}\", &aliasedVal)) {
+ $*{failureCode}
+ }
+ """,
+ failureCode=failureCode,
+ prop=m.identifier.name,
+ )
+ )
+ ]
+ + [defineAlias(alias) for alias in sorted(m.aliases)]
+ )
+
+ defineAliases = CGList(
+ [
+ CGGeneric(
+ dedent(
+ """
+ // Set up aliases on the interface prototype object we just created.
+ """
+ )
+ ),
+ CGGeneric("JS::Rooted<JS::Value> aliasedVal(aCx);\n\n"),
+ ]
+ + [
+ defineAliasesFor(m)
+ for m in sorted(aliasedMembers, key=lambda m: m.identifier.name)
+ ]
+ )
+ needProtoVar = True
+ else:
+ defineAliases = None
+
+ # Globals handle unforgeables directly in Wrap() instead of
+ # via a holder.
+ if (
+ self.descriptor.hasLegacyUnforgeableMembers
+ and not self.descriptor.isGlobal()
+ ):
+ assert needInterfacePrototypeObject
+
+ # We want to use the same JSClass and prototype as the object we'll
+ # end up defining the unforgeable properties on in the end, so that
+ # we can use JS_InitializePropertiesFromCompatibleNativeObject to do
+ # a fast copy. In the case of proxies that's null, because the
+ # expando object is a vanilla object, but in the case of other DOM
+ # objects it's whatever our class is.
+ if self.descriptor.proxy:
+ holderClass = "nullptr"
+ holderProto = "nullptr"
+ else:
+ holderClass = "sClass.ToJSClass()"
+ holderProto = "proto"
+ needProtoVar = True
+ createUnforgeableHolder = CGGeneric(
+ fill(
+ """
+ JS::Rooted<JSObject*> unforgeableHolder(
+ aCx, JS_NewObjectWithoutMetadata(aCx, ${holderClass}, ${holderProto}));
+ if (!unforgeableHolder) {
+ $*{failureCode}
+ }
+ """,
+ holderProto=holderProto,
+ holderClass=holderClass,
+ failureCode=failureCode,
+ )
+ )
+ defineUnforgeables = InitUnforgeablePropertiesOnHolder(
+ self.descriptor, self.properties, failureCode
+ )
+ createUnforgeableHolder = CGList(
+ [createUnforgeableHolder, defineUnforgeables]
+ )
+
+ installUnforgeableHolder = CGGeneric(
+ dedent(
+ """
+ if (*protoCache) {
+ JS::SetReservedSlot(*protoCache, DOM_INTERFACE_PROTO_SLOTS_BASE,
+ JS::ObjectValue(*unforgeableHolder));
+ }
+ """
+ )
+ )
+
+ unforgeableHolderSetup = CGList(
+ [createUnforgeableHolder, installUnforgeableHolder], "\n"
+ )
+ else:
+ unforgeableHolderSetup = None
+
+ # FIXME Unclear whether this is needed for hasOrdinaryObjectPrototype
+ if (
+ self.descriptor.interface.isOnGlobalProtoChain()
+ and needInterfacePrototypeObject
+ and not self.descriptor.hasOrdinaryObjectPrototype
+ ):
+ makeProtoPrototypeImmutable = CGGeneric(
+ fill(
+ """
+ {
+ bool succeeded;
+ if (!JS_SetImmutablePrototype(aCx, proto, &succeeded)) {
+ $*{failureCode}
+ }
+
+ MOZ_ASSERT(succeeded,
+ "making a fresh prototype object's [[Prototype]] "
+ "immutable can internally fail, but it should "
+ "never be unsuccessful");
+ }
+ """,
+ protoCache=protoCache,
+ failureCode=failureCode,
+ )
+ )
+ needProtoVar = True
+ else:
+ makeProtoPrototypeImmutable = None
+
+ if needProtoVar:
+ defineProtoVar = CGGeneric(
+ fill(
+ """
+ JS::AssertObjectIsNotGray(*protoCache);
+ JS::Handle<JSObject*> proto = JS::Handle<JSObject*>::fromMarkedLocation(protoCache->address());
+ if (!proto) {
+ $*{failureCode}
+ }
+ """,
+ failureCode=failureCode,
+ )
+ )
+ else:
+ defineProtoVar = None
+ return CGList(
+ [
+ getParentProto,
+ getConstructorProto,
+ CGGeneric(call),
+ defineProtoVar,
+ defineAliases,
+ unforgeableHolderSetup,
+ makeProtoPrototypeImmutable,
+ ],
+ "\n",
+ ).define()
+
+
+class CGGetProtoObjectHandleMethod(CGAbstractMethod):
+ """
+ A method for getting the interface prototype object.
+ """
+
+ def __init__(self, descriptor, static, signatureOnly=False):
+ CGAbstractMethod.__init__(
+ self,
+ descriptor,
+ "GetProtoObjectHandle",
+ "JS::Handle<JSObject*>",
+ [Argument("JSContext*", "aCx")],
+ inline=True,
+ static=static,
+ signatureOnly=signatureOnly,
+ )
+
+ def definition_body(self):
+ return fill(
+ """
+ /* Get the interface prototype object for this class. This will create the
+ object as needed. */
+ return GetPerInterfaceObjectHandle(aCx, prototypes::id::${name},
+ &CreateInterfaceObjects,
+ /* aDefineOnGlobal = */ true);
+
+ """,
+ name=self.descriptor.name,
+ )
+
+
+class CGGetProtoObjectMethod(CGAbstractMethod):
+ """
+ A method for getting the interface prototype object.
+ """
+
+ def __init__(self, descriptor):
+ CGAbstractMethod.__init__(
+ self,
+ descriptor,
+ "GetProtoObject",
+ "JSObject*",
+ [Argument("JSContext*", "aCx")],
+ )
+
+ def definition_body(self):
+ return "return GetProtoObjectHandle(aCx);\n"
+
+
+class CGGetConstructorObjectHandleMethod(CGAbstractMethod):
+ """
+ A method for getting the interface constructor object.
+ """
+
+ def __init__(self, descriptor):
+ CGAbstractMethod.__init__(
+ self,
+ descriptor,
+ "GetConstructorObjectHandle",
+ "JS::Handle<JSObject*>",
+ [
+ Argument("JSContext*", "aCx"),
+ Argument("bool", "aDefineOnGlobal", "true"),
+ ],
+ inline=True,
+ )
+
+ def definition_body(self):
+ return fill(
+ """
+ /* Get the interface object for this class. This will create the object as
+ needed. */
+
+ return GetPerInterfaceObjectHandle(aCx, constructors::id::${name},
+ &CreateInterfaceObjects,
+ aDefineOnGlobal);
+ """,
+ name=self.descriptor.name,
+ )
+
+
+class CGGetConstructorObjectMethod(CGAbstractMethod):
+ """
+ A method for getting the interface constructor object.
+ """
+
+ def __init__(self, descriptor):
+ CGAbstractMethod.__init__(
+ self,
+ descriptor,
+ "GetConstructorObject",
+ "JSObject*",
+ [Argument("JSContext*", "aCx")],
+ )
+
+ def definition_body(self):
+ return "return GetConstructorObjectHandle(aCx);\n"
+
+
+class CGGetNamedPropertiesObjectMethod(CGAbstractStaticMethod):
+ def __init__(self, descriptor):
+ args = [Argument("JSContext*", "aCx")]
+ CGAbstractStaticMethod.__init__(
+ self, descriptor, "GetNamedPropertiesObject", "JSObject*", args
+ )
+
+ def definition_body(self):
+ parentProtoName = self.descriptor.parentPrototypeName
+ if parentProtoName is None:
+ getParentProto = ""
+ parentProto = "nullptr"
+ else:
+ getParentProto = fill(
+ """
+ JS::Rooted<JSObject*> parentProto(aCx, ${parent}::GetProtoObjectHandle(aCx));
+ if (!parentProto) {
+ return nullptr;
+ }
+ """,
+ parent=toBindingNamespace(parentProtoName),
+ )
+ parentProto = "parentProto"
+ return fill(
+ """
+ /* Make sure our global is sane. Hopefully we can remove this sometime */
+ JSObject* global = JS::CurrentGlobalOrNull(aCx);
+ if (!(JS::GetClass(global)->flags & JSCLASS_DOM_GLOBAL)) {
+ return nullptr;
+ }
+
+ /* Check to see whether the named properties object has already been created */
+ ProtoAndIfaceCache& protoAndIfaceCache = *GetProtoAndIfaceCache(global);
+
+ JS::Heap<JSObject*>& namedPropertiesObject = protoAndIfaceCache.EntrySlotOrCreate(namedpropertiesobjects::id::${ifaceName});
+ if (!namedPropertiesObject) {
+ $*{getParentProto}
+ namedPropertiesObject = ${nativeType}::CreateNamedPropertiesObject(aCx, ${parentProto});
+ DebugOnly<const DOMIfaceAndProtoJSClass*> clasp =
+ DOMIfaceAndProtoJSClass::FromJSClass(JS::GetClass(namedPropertiesObject));
+ MOZ_ASSERT(clasp->mType == eNamedPropertiesObject,
+ "Expected ${nativeType}::CreateNamedPropertiesObject to return a named properties object");
+ MOZ_ASSERT(clasp->mNativeHooks,
+ "The named properties object for ${nativeType} should have NativePropertyHooks.");
+ MOZ_ASSERT(!clasp->mNativeHooks->mResolveOwnProperty,
+ "Shouldn't resolve the properties of the named properties object for ${nativeType} for Xrays.");
+ MOZ_ASSERT(!clasp->mNativeHooks->mEnumerateOwnProperties,
+ "Shouldn't enumerate the properties of the named properties object for ${nativeType} for Xrays.");
+ }
+ return namedPropertiesObject.get();
+ """,
+ getParentProto=getParentProto,
+ ifaceName=self.descriptor.name,
+ parentProto=parentProto,
+ nativeType=self.descriptor.nativeType,
+ )
+
+
+def getRawConditionList(idlobj, cxName, objName, ignoreSecureContext=False):
+ """
+ Get the list of conditions for idlobj (to be used in "is this enabled"
+ checks). This will be returned as a CGList with " &&\n" as the separator,
+ for readability.
+
+ objName is the name of the object that we're working with, because some of
+ our test functions want that.
+
+ ignoreSecureContext is used only for constructors in which the WebIDL interface
+ itself is already marked as [SecureContext]. There is no need to do the work twice.
+ """
+ conditions = []
+ pref = idlobj.getExtendedAttribute("Pref")
+ if pref:
+ assert isinstance(pref, list) and len(pref) == 1
+ conditions.append("StaticPrefs::%s()" % prefIdentifier(pref[0]))
+ if isChromeOnly(idlobj):
+ conditions.append("nsContentUtils::ThreadsafeIsSystemCaller(%s)" % cxName)
+ func = idlobj.getExtendedAttribute("Func")
+ if func:
+ assert isinstance(func, list) and len(func) == 1
+ conditions.append("%s(%s, %s)" % (func[0], cxName, objName))
+ trial = idlobj.getExtendedAttribute("Trial")
+ if trial:
+ assert isinstance(trial, list) and len(trial) == 1
+ conditions.append(
+ "OriginTrials::IsEnabled(%s, %s, OriginTrial::%s)"
+ % (cxName, objName, trial[0])
+ )
+ if not ignoreSecureContext and idlobj.getExtendedAttribute("SecureContext"):
+ conditions.append(
+ "mozilla::dom::IsSecureContextOrObjectIsFromSecureContext(%s, %s)"
+ % (cxName, objName)
+ )
+ return conditions
+
+
+def getConditionList(idlobj, cxName, objName, ignoreSecureContext=False):
+ """
+ Get the list of conditions from getRawConditionList
+ See comment on getRawConditionList above for more info about arguments.
+
+ The return value is a possibly-empty conjunctive CGList of conditions.
+ """
+ conditions = getRawConditionList(idlobj, cxName, objName, ignoreSecureContext)
+ return CGList((CGGeneric(cond) for cond in conditions), " &&\n")
+
+
+class CGConstructorEnabled(CGAbstractMethod):
+ """
+ A method for testing whether we should be exposing this interface object.
+ This can perform various tests depending on what conditions are specified
+ on the interface.
+ """
+
+ def __init__(self, descriptor):
+ CGAbstractMethod.__init__(
+ self,
+ descriptor,
+ "ConstructorEnabled",
+ "bool",
+ [Argument("JSContext*", "aCx"), Argument("JS::Handle<JSObject*>", "aObj")],
+ )
+
+ def definition_body(self):
+ body = CGList([], "\n")
+
+ iface = self.descriptor.interface
+
+ if not iface.isExposedInWindow():
+ exposedInWindowCheck = dedent(
+ """
+ MOZ_ASSERT(!NS_IsMainThread(), "Why did we even get called?");
+ """
+ )
+ body.append(CGGeneric(exposedInWindowCheck))
+
+ if iface.isExposedInSomeButNotAllWorkers():
+ workerGlobals = sorted(iface.getWorkerExposureSet())
+ workerCondition = CGList(
+ (
+ CGGeneric('strcmp(name, "%s")' % workerGlobal)
+ for workerGlobal in workerGlobals
+ ),
+ " && ",
+ )
+ exposedInWorkerCheck = fill(
+ """
+ const char* name = JS::GetClass(aObj)->name;
+ if (${workerCondition}) {
+ return false;
+ }
+ """,
+ workerCondition=workerCondition.define(),
+ )
+ exposedInWorkerCheck = CGGeneric(exposedInWorkerCheck)
+ if iface.isExposedInWindow():
+ exposedInWorkerCheck = CGIfWrapper(
+ exposedInWorkerCheck, "!NS_IsMainThread()"
+ )
+ body.append(exposedInWorkerCheck)
+
+ conditions = getConditionList(iface, "aCx", "aObj")
+
+ # We should really have some conditions
+ assert len(body) or len(conditions)
+
+ conditionsWrapper = ""
+ if len(conditions):
+ conditionsWrapper = CGWrapper(
+ conditions, pre="return ", post=";\n", reindent=True
+ )
+ else:
+ conditionsWrapper = CGGeneric("return true;\n")
+
+ body.append(conditionsWrapper)
+ return body.define()
+
+
+def StructuredCloneTag(name):
+ return "SCTAG_DOM_%s" % name.upper()
+
+
+class CGSerializer(CGAbstractStaticMethod):
+ """
+ Implementation of serialization for things marked [Serializable].
+ This gets stored in our DOMJSClass, so it can be static.
+
+ The caller is expected to pass in the object whose DOMJSClass it
+ used to get the serializer.
+ """
+
+ def __init__(self, descriptor):
+ args = [
+ Argument("JSContext*", "aCx"),
+ Argument("JSStructuredCloneWriter*", "aWriter"),
+ Argument("JS::Handle<JSObject*>", "aObj"),
+ ]
+ CGAbstractStaticMethod.__init__(self, descriptor, "Serialize", "bool", args)
+
+ def definition_body(self):
+ return fill(
+ """
+ MOZ_ASSERT(IsDOMObject(aObj), "Non-DOM object passed");
+ MOZ_ASSERT(GetDOMClass(aObj)->mSerializer == &Serialize,
+ "Wrong object passed");
+ return JS_WriteUint32Pair(aWriter, ${tag}, 0) &&
+ UnwrapDOMObject<${type}>(aObj)->WriteStructuredClone(aCx, aWriter);
+ """,
+ tag=StructuredCloneTag(self.descriptor.name),
+ type=self.descriptor.nativeType,
+ )
+
+
+class CGDeserializer(CGAbstractMethod):
+ """
+ Implementation of deserialization for things marked [Serializable].
+ This will need to be accessed from WebIDLSerializable, so can't be static.
+ """
+
+ def __init__(self, descriptor):
+ args = [
+ Argument("JSContext*", "aCx"),
+ Argument("nsIGlobalObject*", "aGlobal"),
+ Argument("JSStructuredCloneReader*", "aReader"),
+ ]
+ CGAbstractMethod.__init__(self, descriptor, "Deserialize", "JSObject*", args)
+
+ def definition_body(self):
+ # WrapObject has different signatures depending on whether
+ # the object is wrappercached.
+ if self.descriptor.wrapperCache:
+ wrapCall = dedent(
+ """
+ result = obj->WrapObject(aCx, nullptr);
+ if (!result) {
+ return nullptr;
+ }
+ """
+ )
+ else:
+ wrapCall = dedent(
+ """
+ if (!obj->WrapObject(aCx, nullptr, &result)) {
+ return nullptr;
+ }
+ """
+ )
+
+ return fill(
+ """
+ // Protect the result from a moving GC in ~RefPtr
+ JS::Rooted<JSObject*> result(aCx);
+ { // Scope for the RefPtr
+ RefPtr<${type}> obj = ${type}::ReadStructuredClone(aCx, aGlobal, aReader);
+ if (!obj) {
+ return nullptr;
+ }
+ $*{wrapCall}
+ }
+ return result;
+ """,
+ type=self.descriptor.nativeType,
+ wrapCall=wrapCall,
+ )
+
+
+def CreateBindingJSObject(descriptor):
+ objDecl = "BindingJSObjectCreator<%s> creator(aCx);\n" % descriptor.nativeType
+
+ # We don't always need to root obj, but there are a variety
+ # of cases where we do, so for simplicity, just always root it.
+ if descriptor.proxy:
+ if descriptor.interface.getExtendedAttribute("LegacyOverrideBuiltIns"):
+ assert not descriptor.isMaybeCrossOriginObject()
+ create = dedent(
+ """
+ aObject->mExpandoAndGeneration.expando.setUndefined();
+ JS::Rooted<JS::Value> expandoValue(aCx, JS::PrivateValue(&aObject->mExpandoAndGeneration));
+ creator.CreateProxyObject(aCx, &sClass.mBase, DOMProxyHandler::getInstance(),
+ proto, /* aLazyProto = */ false, aObject,
+ expandoValue, aReflector);
+ """
+ )
+ else:
+ if descriptor.isMaybeCrossOriginObject():
+ proto = "nullptr"
+ lazyProto = "true"
+ else:
+ proto = "proto"
+ lazyProto = "false"
+ create = fill(
+ """
+ creator.CreateProxyObject(aCx, &sClass.mBase, DOMProxyHandler::getInstance(),
+ ${proto}, /* aLazyProto = */ ${lazyProto},
+ aObject, JS::UndefinedHandleValue, aReflector);
+ """,
+ proto=proto,
+ lazyProto=lazyProto,
+ )
+ else:
+ create = dedent(
+ """
+ creator.CreateObject(aCx, sClass.ToJSClass(), proto, aObject, aReflector);
+ """
+ )
+ return (
+ objDecl
+ + create
+ + dedent(
+ """
+ if (!aReflector) {
+ return false;
+ }
+ """
+ )
+ )
+
+
+def InitUnforgeablePropertiesOnHolder(
+ descriptor, properties, failureCode, holderName="unforgeableHolder"
+):
+ """
+ Define the unforgeable properties on the unforgeable holder for
+ the interface represented by descriptor.
+
+ properties is a PropertyArrays instance.
+
+ """
+ assert (
+ properties.unforgeableAttrs.hasNonChromeOnly()
+ or properties.unforgeableAttrs.hasChromeOnly()
+ or properties.unforgeableMethods.hasNonChromeOnly()
+ or properties.unforgeableMethods.hasChromeOnly()
+ )
+
+ unforgeables = []
+
+ defineUnforgeableAttrs = fill(
+ """
+ if (!DefineLegacyUnforgeableAttributes(aCx, ${holderName}, %s)) {
+ $*{failureCode}
+ }
+ """,
+ failureCode=failureCode,
+ holderName=holderName,
+ )
+ defineUnforgeableMethods = fill(
+ """
+ if (!DefineLegacyUnforgeableMethods(aCx, ${holderName}, %s)) {
+ $*{failureCode}
+ }
+ """,
+ failureCode=failureCode,
+ holderName=holderName,
+ )
+
+ unforgeableMembers = [
+ (defineUnforgeableAttrs, properties.unforgeableAttrs),
+ (defineUnforgeableMethods, properties.unforgeableMethods),
+ ]
+ for (template, array) in unforgeableMembers:
+ if array.hasNonChromeOnly():
+ unforgeables.append(CGGeneric(template % array.variableName(False)))
+ if array.hasChromeOnly():
+ unforgeables.append(
+ CGIfWrapper(
+ CGGeneric(template % array.variableName(True)),
+ "nsContentUtils::ThreadsafeIsSystemCaller(aCx)",
+ )
+ )
+
+ if descriptor.interface.getExtendedAttribute("LegacyUnforgeable"):
+ # We do our undefined toPrimitive here, not as a regular property
+ # because we don't have a concept of value props anywhere in IDL.
+ unforgeables.append(
+ CGGeneric(
+ fill(
+ """
+ JS::Rooted<JS::PropertyKey> toPrimitive(aCx,
+ JS::GetWellKnownSymbolKey(aCx, JS::SymbolCode::toPrimitive));
+ if (!JS_DefinePropertyById(aCx, ${holderName}, toPrimitive,
+ JS::UndefinedHandleValue,
+ JSPROP_READONLY | JSPROP_PERMANENT)) {
+ $*{failureCode}
+ }
+ """,
+ failureCode=failureCode,
+ holderName=holderName,
+ )
+ )
+ )
+
+ return CGWrapper(CGList(unforgeables), pre="\n")
+
+
+def CopyUnforgeablePropertiesToInstance(descriptor, failureCode):
+ """
+ Copy the unforgeable properties from the unforgeable holder for
+ this interface to the instance object we have.
+ """
+ assert not descriptor.isGlobal()
+
+ if not descriptor.hasLegacyUnforgeableMembers:
+ return ""
+
+ copyCode = [
+ CGGeneric(
+ dedent(
+ """
+ // Important: do unforgeable property setup after we have handed
+ // over ownership of the C++ object to obj as needed, so that if
+ // we fail and it ends up GCed it won't have problems in the
+ // finalizer trying to drop its ownership of the C++ object.
+ """
+ )
+ )
+ ]
+
+ # For proxies, we want to define on the expando object, not directly on the
+ # reflector, so we can make sure we don't get confused by named getters.
+ if descriptor.proxy:
+ copyCode.append(
+ CGGeneric(
+ fill(
+ """
+ JS::Rooted<JSObject*> expando(aCx,
+ DOMProxyHandler::EnsureExpandoObject(aCx, aReflector));
+ if (!expando) {
+ $*{failureCode}
+ }
+ """,
+ failureCode=failureCode,
+ )
+ )
+ )
+ obj = "expando"
+ else:
+ obj = "aReflector"
+
+ copyCode.append(
+ CGGeneric(
+ fill(
+ """
+ JS::Rooted<JSObject*> unforgeableHolder(aCx,
+ &JS::GetReservedSlot(canonicalProto, DOM_INTERFACE_PROTO_SLOTS_BASE).toObject());
+ if (!JS_InitializePropertiesFromCompatibleNativeObject(aCx, ${obj}, unforgeableHolder)) {
+ $*{failureCode}
+ }
+ """,
+ obj=obj,
+ failureCode=failureCode,
+ )
+ )
+ )
+
+ return CGWrapper(CGList(copyCode), pre="\n").define()
+
+
+def AssertInheritanceChain(descriptor):
+ # We can skip the reinterpret_cast check for the descriptor's nativeType
+ # if aObject is a pointer of that type.
+ asserts = fill(
+ """
+ static_assert(std::is_same_v<decltype(aObject), ${nativeType}*>);
+ """,
+ nativeType=descriptor.nativeType,
+ )
+ iface = descriptor.interface
+ while iface.parent:
+ iface = iface.parent
+ desc = descriptor.getDescriptor(iface.identifier.name)
+ asserts += (
+ "MOZ_ASSERT(static_cast<%s*>(aObject) == \n"
+ " reinterpret_cast<%s*>(aObject),\n"
+ ' "Multiple inheritance for %s is broken.");\n'
+ % (desc.nativeType, desc.nativeType, desc.nativeType)
+ )
+ asserts += "MOZ_ASSERT(ToSupportsIsCorrect(aObject));\n"
+ return asserts
+
+
+def InitMemberSlots(descriptor, failureCode):
+ """
+ Initialize member slots on our JS object if we're supposed to have some.
+
+ Note that this is called after the SetWrapper() call in the
+ wrapperCache case, since that can affect how our getters behave
+ and we plan to invoke them here. So if we fail, we need to
+ ClearWrapper.
+ """
+ if not descriptor.interface.hasMembersInSlots():
+ return ""
+ return fill(
+ """
+ if (!UpdateMemberSlots(aCx, aReflector, aObject)) {
+ $*{failureCode}
+ }
+ """,
+ failureCode=failureCode,
+ )
+
+
+def DeclareProto(descriptor, noGivenProto=False):
+ """
+ Declare the canonicalProto and proto we have for our wrapping operation.
+ """
+ getCanonical = dedent(
+ """
+ JS::Handle<JSObject*> ${canonicalProto} = GetProtoObjectHandle(aCx);
+ if (!${canonicalProto}) {
+ return false;
+ }
+ """
+ )
+
+ if noGivenProto:
+ return fill(getCanonical, canonicalProto="proto")
+
+ getCanonical = fill(getCanonical, canonicalProto="canonicalProto")
+
+ preamble = getCanonical + dedent(
+ """
+ JS::Rooted<JSObject*> proto(aCx);
+ """
+ )
+ if descriptor.isMaybeCrossOriginObject():
+ return preamble + dedent(
+ """
+ MOZ_ASSERT(!aGivenProto,
+ "Shouldn't have constructors on cross-origin objects");
+ // Set proto to canonicalProto to avoid preserving our wrapper if
+ // we don't have to.
+ proto = canonicalProto;
+ """
+ )
+
+ return preamble + dedent(
+ """
+ if (aGivenProto) {
+ proto = aGivenProto;
+ // Unfortunately, while aGivenProto was in the compartment of aCx
+ // coming in, we changed compartments to that of "parent" so may need
+ // to wrap the proto here.
+ if (js::GetContextCompartment(aCx) != JS::GetCompartment(proto)) {
+ if (!JS_WrapObject(aCx, &proto)) {
+ return false;
+ }
+ }
+ } else {
+ proto = canonicalProto;
+ }
+ """
+ )
+
+
+class CGWrapWithCacheMethod(CGAbstractMethod):
+ """
+ Create a wrapper JSObject for a given native that implements nsWrapperCache.
+ """
+
+ def __init__(self, descriptor):
+ assert descriptor.interface.hasInterfacePrototypeObject()
+ args = [
+ Argument("JSContext*", "aCx"),
+ Argument(descriptor.nativeType + "*", "aObject"),
+ Argument("nsWrapperCache*", "aCache"),
+ Argument("JS::Handle<JSObject*>", "aGivenProto"),
+ Argument("JS::MutableHandle<JSObject*>", "aReflector"),
+ ]
+ CGAbstractMethod.__init__(self, descriptor, "Wrap", "bool", args)
+
+ def definition_body(self):
+ failureCode = dedent(
+ """
+ aCache->ReleaseWrapper(aObject);
+ aCache->ClearWrapper();
+ return false;
+ """
+ )
+
+ if self.descriptor.proxy:
+ finalize = "DOMProxyHandler::getInstance()->finalize"
+ else:
+ finalize = FINALIZE_HOOK_NAME
+
+ return fill(
+ """
+ static_assert(!std::is_base_of_v<NonRefcountedDOMObject, ${nativeType}>,
+ "Shouldn't have wrappercached things that are not refcounted.");
+ $*{assertInheritance}
+ MOZ_ASSERT_IF(aGivenProto, js::IsObjectInContextCompartment(aGivenProto, aCx));
+ MOZ_ASSERT(!aCache->GetWrapper(),
+ "You should probably not be using Wrap() directly; use "
+ "GetOrCreateDOMReflector instead");
+
+ MOZ_ASSERT(ToSupportsIsOnPrimaryInheritanceChain(aObject, aCache),
+ "nsISupports must be on our primary inheritance chain");
+
+ // If the wrapper cache contains a dead reflector then finalize that
+ // now, ensuring that the finalizer for the old reflector always
+ // runs before the new reflector is created and attached. This
+ // avoids the awkward situation where there are multiple reflector
+ // objects that contain pointers to the same native.
+
+ if (JSObject* oldReflector = aCache->GetWrapperMaybeDead()) {
+ ${finalize}(nullptr /* unused */, oldReflector);
+ MOZ_ASSERT(!aCache->GetWrapperMaybeDead());
+ }
+
+ JS::Rooted<JSObject*> global(aCx, FindAssociatedGlobal(aCx, aObject->GetParentObject()));
+ if (!global) {
+ return false;
+ }
+ MOZ_ASSERT(JS_IsGlobalObject(global));
+ JS::AssertObjectIsNotGray(global);
+
+ // That might have ended up wrapping us already, due to the wonders
+ // of XBL. Check for that, and bail out as needed.
+ aReflector.set(aCache->GetWrapper());
+ if (aReflector) {
+ #ifdef DEBUG
+ AssertReflectorHasGivenProto(aCx, aReflector, aGivenProto);
+ #endif // DEBUG
+ return true;
+ }
+
+ JSAutoRealm ar(aCx, global);
+ $*{declareProto}
+
+ $*{createObject}
+
+ aCache->SetWrapper(aReflector);
+ $*{unforgeable}
+ $*{slots}
+ creator.InitializationSucceeded();
+
+ MOZ_ASSERT(aCache->GetWrapperPreserveColor() &&
+ aCache->GetWrapperPreserveColor() == aReflector);
+ // If proto != canonicalProto, we have to preserve our wrapper;
+ // otherwise we won't be able to properly recreate it later, since
+ // we won't know what proto to use. Note that we don't check
+ // aGivenProto here, since it's entirely possible (and even
+ // somewhat common) to have a non-null aGivenProto which is the
+ // same as canonicalProto.
+ if (proto != canonicalProto) {
+ PreserveWrapper(aObject);
+ }
+
+ return true;
+ """,
+ nativeType=self.descriptor.nativeType,
+ assertInheritance=AssertInheritanceChain(self.descriptor),
+ declareProto=DeclareProto(self.descriptor),
+ createObject=CreateBindingJSObject(self.descriptor),
+ unforgeable=CopyUnforgeablePropertiesToInstance(
+ self.descriptor, failureCode
+ ),
+ slots=InitMemberSlots(self.descriptor, failureCode),
+ finalize=finalize,
+ )
+
+
+class CGWrapMethod(CGAbstractMethod):
+ def __init__(self, descriptor):
+ # XXX can we wrap if we don't have an interface prototype object?
+ assert descriptor.interface.hasInterfacePrototypeObject()
+ args = [
+ Argument("JSContext*", "aCx"),
+ Argument("T*", "aObject"),
+ Argument("JS::Handle<JSObject*>", "aGivenProto"),
+ ]
+ CGAbstractMethod.__init__(
+ self,
+ descriptor,
+ "Wrap",
+ "JSObject*",
+ args,
+ inline=True,
+ templateArgs=["class T"],
+ )
+
+ def definition_body(self):
+ return dedent(
+ """
+ JS::Rooted<JSObject*> reflector(aCx);
+ return Wrap(aCx, aObject, aObject, aGivenProto, &reflector) ? reflector.get() : nullptr;
+ """
+ )
+
+
+class CGWrapNonWrapperCacheMethod(CGAbstractMethod):
+ """
+ Create a wrapper JSObject for a given native that does not implement
+ nsWrapperCache.
+ """
+
+ def __init__(self, descriptor, static=False, signatureOnly=False):
+ # XXX can we wrap if we don't have an interface prototype object?
+ assert descriptor.interface.hasInterfacePrototypeObject()
+ self.noGivenProto = (
+ descriptor.interface.isIteratorInterface()
+ or descriptor.interface.isAsyncIteratorInterface()
+ )
+ args = [
+ Argument("JSContext*", "aCx"),
+ Argument(descriptor.nativeType + "*", "aObject"),
+ ]
+ if not self.noGivenProto:
+ args.append(Argument("JS::Handle<JSObject*>", "aGivenProto"))
+ args.append(Argument("JS::MutableHandle<JSObject*>", "aReflector"))
+ CGAbstractMethod.__init__(
+ self,
+ descriptor,
+ "Wrap",
+ "bool",
+ args,
+ static=static,
+ signatureOnly=signatureOnly,
+ )
+
+ def definition_body(self):
+ failureCode = "return false;\n"
+
+ declareProto = DeclareProto(self.descriptor, noGivenProto=self.noGivenProto)
+ if self.noGivenProto:
+ assertGivenProto = ""
+ else:
+ assertGivenProto = dedent(
+ """
+ MOZ_ASSERT_IF(aGivenProto, js::IsObjectInContextCompartment(aGivenProto, aCx));
+ """
+ )
+ return fill(
+ """
+ $*{assertions}
+ $*{assertGivenProto}
+
+ JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
+ $*{declareProto}
+
+ $*{createObject}
+
+ $*{unforgeable}
+
+ $*{slots}
+
+ creator.InitializationSucceeded();
+ return true;
+ """,
+ assertions=AssertInheritanceChain(self.descriptor),
+ assertGivenProto=assertGivenProto,
+ declareProto=declareProto,
+ createObject=CreateBindingJSObject(self.descriptor),
+ unforgeable=CopyUnforgeablePropertiesToInstance(
+ self.descriptor, failureCode
+ ),
+ slots=InitMemberSlots(self.descriptor, failureCode),
+ )
+
+
+class CGWrapGlobalMethod(CGAbstractMethod):
+ """
+ Create a wrapper JSObject for a global. The global must implement
+ nsWrapperCache.
+
+ properties should be a PropertyArrays instance.
+ """
+
+ def __init__(self, descriptor, properties):
+ assert descriptor.interface.hasInterfacePrototypeObject()
+ args = [
+ Argument("JSContext*", "aCx"),
+ Argument(descriptor.nativeType + "*", "aObject"),
+ Argument("nsWrapperCache*", "aCache"),
+ Argument("JS::RealmOptions&", "aOptions"),
+ Argument("JSPrincipals*", "aPrincipal"),
+ Argument("bool", "aInitStandardClasses"),
+ Argument("JS::MutableHandle<JSObject*>", "aReflector"),
+ ]
+ CGAbstractMethod.__init__(self, descriptor, "Wrap", "bool", args)
+ self.descriptor = descriptor
+ self.properties = properties
+
+ def definition_body(self):
+ if self.properties.hasNonChromeOnly():
+ properties = "sNativeProperties.Upcast()"
+ else:
+ properties = "nullptr"
+ if self.properties.hasChromeOnly():
+ chromeProperties = "nsContentUtils::ThreadsafeIsSystemCaller(aCx) ? sChromeOnlyNativeProperties.Upcast() : nullptr"
+ else:
+ chromeProperties = "nullptr"
+
+ failureCode = dedent(
+ """
+ aCache->ReleaseWrapper(aObject);
+ aCache->ClearWrapper();
+ return false;
+ """
+ )
+
+ if self.descriptor.hasLegacyUnforgeableMembers:
+ unforgeable = InitUnforgeablePropertiesOnHolder(
+ self.descriptor, self.properties, failureCode, "aReflector"
+ ).define()
+ else:
+ unforgeable = ""
+
+ if self.descriptor.hasOrdinaryObjectPrototype:
+ getProto = "JS::GetRealmObjectPrototypeHandle"
+ else:
+ getProto = "GetProtoObjectHandle"
+ return fill(
+ """
+ $*{assertions}
+ MOZ_ASSERT(ToSupportsIsOnPrimaryInheritanceChain(aObject, aCache),
+ "nsISupports must be on our primary inheritance chain");
+
+ if (!CreateGlobal<${nativeType}, ${getProto}>(aCx,
+ aObject,
+ aCache,
+ sClass.ToJSClass(),
+ aOptions,
+ aPrincipal,
+ aInitStandardClasses,
+ aReflector)) {
+ $*{failureCode}
+ }
+
+ // aReflector is a new global, so has a new realm. Enter it
+ // before doing anything with it.
+ JSAutoRealm ar(aCx, aReflector);
+
+ if (!DefineProperties(aCx, aReflector, ${properties}, ${chromeProperties})) {
+ $*{failureCode}
+ }
+ $*{unforgeable}
+
+ $*{slots}
+
+ return true;
+ """,
+ assertions=AssertInheritanceChain(self.descriptor),
+ nativeType=self.descriptor.nativeType,
+ getProto=getProto,
+ properties=properties,
+ chromeProperties=chromeProperties,
+ failureCode=failureCode,
+ unforgeable=unforgeable,
+ slots=InitMemberSlots(self.descriptor, failureCode),
+ )
+
+
+class CGUpdateMemberSlotsMethod(CGAbstractStaticMethod):
+ def __init__(self, descriptor):
+ args = [
+ Argument("JSContext*", "aCx"),
+ Argument("JS::Handle<JSObject*>", "aWrapper"),
+ Argument(descriptor.nativeType + "*", "aObject"),
+ ]
+ CGAbstractStaticMethod.__init__(
+ self, descriptor, "UpdateMemberSlots", "bool", args
+ )
+
+ def definition_body(self):
+ body = "JS::Rooted<JS::Value> temp(aCx);\n" "JSJitGetterCallArgs args(&temp);\n"
+ for m in self.descriptor.interface.members:
+ if m.isAttr() and m.getExtendedAttribute("StoreInSlot"):
+ # Skip doing this for the "window" and "self" attributes on the
+ # Window interface, because those can't be gotten safely until
+ # we have hooked it up correctly to the outer window. The
+ # window code handles doing the get itself.
+ if self.descriptor.interface.identifier.name == "Window" and (
+ m.identifier.name == "window" or m.identifier.name == "self"
+ ):
+ continue
+ body += fill(
+ """
+
+ static_assert(${slot} < JS::shadow::Object::MAX_FIXED_SLOTS,
+ "Not enough fixed slots to fit '${interface}.${member}. Ion's visitGetDOMMemberV/visitGetDOMMemberT assume StoreInSlot things are all in fixed slots.");
+ if (!get_${member}(aCx, aWrapper, aObject, args)) {
+ return false;
+ }
+ // Getter handled setting our reserved slots
+ """,
+ slot=memberReservedSlot(m, self.descriptor),
+ interface=self.descriptor.interface.identifier.name,
+ member=m.identifier.name,
+ )
+
+ body += "\nreturn true;\n"
+ return body
+
+
+class CGClearCachedValueMethod(CGAbstractMethod):
+ def __init__(self, descriptor, member):
+ self.member = member
+ # If we're StoreInSlot, we'll need to call the getter
+ if member.getExtendedAttribute("StoreInSlot"):
+ args = [Argument("JSContext*", "aCx")]
+ returnType = "bool"
+ else:
+ args = []
+ returnType = "void"
+ args.append(Argument(descriptor.nativeType + "*", "aObject"))
+ name = MakeClearCachedValueNativeName(member)
+ CGAbstractMethod.__init__(self, descriptor, name, returnType, args)
+
+ def definition_body(self):
+ slotIndex = memberReservedSlot(self.member, self.descriptor)
+ if self.member.getExtendedAttribute("StoreInSlot"):
+ # We have to root things and save the old value in case
+ # regetting fails, so we can restore it.
+ declObj = "JS::Rooted<JSObject*> obj(aCx);\n"
+ noopRetval = " true"
+ saveMember = (
+ "JS::Rooted<JS::Value> oldValue(aCx, JS::GetReservedSlot(obj, %s));\n"
+ % slotIndex
+ )
+ regetMember = fill(
+ """
+ JS::Rooted<JS::Value> temp(aCx);
+ JSJitGetterCallArgs args(&temp);
+ JSAutoRealm ar(aCx, obj);
+ if (!get_${name}(aCx, obj, aObject, args)) {
+ JS::SetReservedSlot(obj, ${slotIndex}, oldValue);
+ return false;
+ }
+ return true;
+ """,
+ name=self.member.identifier.name,
+ slotIndex=slotIndex,
+ )
+ else:
+ declObj = "JSObject* obj;\n"
+ noopRetval = ""
+ saveMember = ""
+ regetMember = ""
+
+ if self.descriptor.wantsXrays:
+ clearXrayExpandoSlots = fill(
+ """
+ xpc::ClearXrayExpandoSlots(obj, ${xraySlotIndex});
+ """,
+ xraySlotIndex=memberXrayExpandoReservedSlot(
+ self.member, self.descriptor
+ ),
+ )
+ else:
+ clearXrayExpandoSlots = ""
+
+ return fill(
+ """
+ $*{declObj}
+ obj = aObject->GetWrapper();
+ if (!obj) {
+ return${noopRetval};
+ }
+ $*{saveMember}
+ JS::SetReservedSlot(obj, ${slotIndex}, JS::UndefinedValue());
+ $*{clearXrayExpandoSlots}
+ $*{regetMember}
+ """,
+ declObj=declObj,
+ noopRetval=noopRetval,
+ saveMember=saveMember,
+ slotIndex=slotIndex,
+ clearXrayExpandoSlots=clearXrayExpandoSlots,
+ regetMember=regetMember,
+ )
+
+
+class CGCrossOriginProperties(CGThing):
+ def __init__(self, descriptor):
+ attrs = []
+ chromeOnlyAttrs = []
+ methods = []
+ chromeOnlyMethods = []
+ for m in descriptor.interface.members:
+ if m.isAttr() and (
+ m.getExtendedAttribute("CrossOriginReadable")
+ or m.getExtendedAttribute("CrossOriginWritable")
+ ):
+ if m.isStatic():
+ raise TypeError(
+ "Don't know how to deal with static method %s"
+ % m.identifier.name
+ )
+ if PropertyDefiner.getControllingCondition(
+ m, descriptor
+ ).hasDisablers():
+ raise TypeError(
+ "Don't know how to deal with disabler for %s"
+ % m.identifier.name
+ )
+ if len(m.bindingAliases) > 0:
+ raise TypeError(
+ "Don't know how to deal with aliases for %s" % m.identifier.name
+ )
+ if m.getExtendedAttribute("ChromeOnly") is not None:
+ chromeOnlyAttrs.extend(AttrDefiner.attrData(m, overrideFlags="0"))
+ else:
+ attrs.extend(AttrDefiner.attrData(m, overrideFlags="0"))
+ elif m.isMethod() and m.getExtendedAttribute("CrossOriginCallable"):
+ if m.isStatic():
+ raise TypeError(
+ "Don't know how to deal with static method %s"
+ % m.identifier.name
+ )
+ if PropertyDefiner.getControllingCondition(
+ m, descriptor
+ ).hasDisablers():
+ raise TypeError(
+ "Don't know how to deal with disabler for %s"
+ % m.identifier.name
+ )
+ if len(m.aliases) > 0:
+ raise TypeError(
+ "Don't know how to deal with aliases for %s" % m.identifier.name
+ )
+ if m.getExtendedAttribute("ChromeOnly") is not None:
+ chromeOnlyMethods.append(
+ MethodDefiner.methodData(
+ m, descriptor, overrideFlags="JSPROP_READONLY"
+ )
+ )
+ else:
+ methods.append(
+ MethodDefiner.methodData(
+ m, descriptor, overrideFlags="JSPROP_READONLY"
+ )
+ )
+
+ if len(attrs) > 0:
+ self.attributeSpecs, _ = PropertyDefiner.generatePrefableArrayValues(
+ attrs,
+ descriptor,
+ AttrDefiner.formatSpec,
+ " JS_PS_END\n",
+ AttrDefiner.condition,
+ functools.partial(AttrDefiner.specData, crossOriginOnly=True),
+ )
+ else:
+ self.attributeSpecs = [" JS_PS_END\n"]
+ if len(methods) > 0:
+ self.methodSpecs, _ = PropertyDefiner.generatePrefableArrayValues(
+ methods,
+ descriptor,
+ MethodDefiner.formatSpec,
+ " JS_FS_END\n",
+ MethodDefiner.condition,
+ MethodDefiner.specData,
+ )
+ else:
+ self.methodSpecs = [" JS_FS_END\n"]
+
+ if len(chromeOnlyAttrs) > 0:
+ (
+ self.chromeOnlyAttributeSpecs,
+ _,
+ ) = PropertyDefiner.generatePrefableArrayValues(
+ chromeOnlyAttrs,
+ descriptor,
+ AttrDefiner.formatSpec,
+ " JS_PS_END\n",
+ AttrDefiner.condition,
+ functools.partial(AttrDefiner.specData, crossOriginOnly=True),
+ )
+ else:
+ self.chromeOnlyAttributeSpecs = []
+ if len(chromeOnlyMethods) > 0:
+ self.chromeOnlyMethodSpecs, _ = PropertyDefiner.generatePrefableArrayValues(
+ chromeOnlyMethods,
+ descriptor,
+ MethodDefiner.formatSpec,
+ " JS_FS_END\n",
+ MethodDefiner.condition,
+ MethodDefiner.specData,
+ )
+ else:
+ self.chromeOnlyMethodSpecs = []
+
+ def declare(self):
+ return dedent(
+ """
+ extern const CrossOriginProperties sCrossOriginProperties;
+ """
+ )
+
+ def define(self):
+ def defineChromeOnly(name, specs, specType):
+ if len(specs) == 0:
+ return ("", "nullptr")
+ name = "sChromeOnlyCrossOrigin" + name
+ define = fill(
+ """
+ static const ${specType} ${name}[] = {
+ $*{specs}
+ };
+ """,
+ specType=specType,
+ name=name,
+ specs=",\n".join(specs),
+ )
+ return (define, name)
+
+ chromeOnlyAttributes = defineChromeOnly(
+ "Attributes", self.chromeOnlyAttributeSpecs, "JSPropertySpec"
+ )
+ chromeOnlyMethods = defineChromeOnly(
+ "Methods", self.chromeOnlyMethodSpecs, "JSFunctionSpec"
+ )
+ return fill(
+ """
+ static const JSPropertySpec sCrossOriginAttributes[] = {
+ $*{attributeSpecs}
+ };
+ static const JSFunctionSpec sCrossOriginMethods[] = {
+ $*{methodSpecs}
+ };
+ $*{chromeOnlyAttributeSpecs}
+ $*{chromeOnlyMethodSpecs}
+ const CrossOriginProperties sCrossOriginProperties = {
+ sCrossOriginAttributes,
+ sCrossOriginMethods,
+ ${chromeOnlyAttributes},
+ ${chromeOnlyMethods}
+ };
+ """,
+ attributeSpecs=",\n".join(self.attributeSpecs),
+ methodSpecs=",\n".join(self.methodSpecs),
+ chromeOnlyAttributeSpecs=chromeOnlyAttributes[0],
+ chromeOnlyMethodSpecs=chromeOnlyMethods[0],
+ chromeOnlyAttributes=chromeOnlyAttributes[1],
+ chromeOnlyMethods=chromeOnlyMethods[1],
+ )
+
+
+class CGCycleCollectionTraverseForOwningUnionMethod(CGAbstractMethod):
+ """
+ ImplCycleCollectionUnlink for owning union type.
+ """
+
+ def __init__(self, type):
+ self.type = type
+ args = [
+ Argument("nsCycleCollectionTraversalCallback&", "aCallback"),
+ Argument("%s&" % CGUnionStruct.unionTypeName(type, True), "aUnion"),
+ Argument("const char*", "aName"),
+ Argument("uint32_t", "aFlags", "0"),
+ ]
+ CGAbstractMethod.__init__(
+ self, None, "ImplCycleCollectionTraverse", "void", args
+ )
+
+ def deps(self):
+ return self.type.getDeps()
+
+ def definition_body(self):
+ memberNames = [
+ getUnionMemberName(t)
+ for t in self.type.flatMemberTypes
+ if idlTypeNeedsCycleCollection(t)
+ ]
+ assert memberNames
+
+ conditionTemplate = "aUnion.Is%s()"
+ functionCallTemplate = (
+ 'ImplCycleCollectionTraverse(aCallback, aUnion.GetAs%s(), "m%s", aFlags);\n'
+ )
+
+ ifStaments = (
+ CGIfWrapper(CGGeneric(functionCallTemplate % (m, m)), conditionTemplate % m)
+ for m in memberNames
+ )
+
+ return CGElseChain(ifStaments).define()
+
+
+class CGCycleCollectionUnlinkForOwningUnionMethod(CGAbstractMethod):
+ """
+ ImplCycleCollectionUnlink for owning union type.
+ """
+
+ def __init__(self, type):
+ self.type = type
+ args = [Argument("%s&" % CGUnionStruct.unionTypeName(type, True), "aUnion")]
+ CGAbstractMethod.__init__(self, None, "ImplCycleCollectionUnlink", "void", args)
+
+ def deps(self):
+ return self.type.getDeps()
+
+ def definition_body(self):
+ return "aUnion.Uninit();\n"
+
+
+builtinNames = {
+ IDLType.Tags.bool: "bool",
+ IDLType.Tags.int8: "int8_t",
+ IDLType.Tags.int16: "int16_t",
+ IDLType.Tags.int32: "int32_t",
+ IDLType.Tags.int64: "int64_t",
+ IDLType.Tags.uint8: "uint8_t",
+ IDLType.Tags.uint16: "uint16_t",
+ IDLType.Tags.uint32: "uint32_t",
+ IDLType.Tags.uint64: "uint64_t",
+ IDLType.Tags.unrestricted_float: "float",
+ IDLType.Tags.float: "float",
+ IDLType.Tags.unrestricted_double: "double",
+ IDLType.Tags.double: "double",
+}
+
+numericSuffixes = {
+ IDLType.Tags.int8: "",
+ IDLType.Tags.uint8: "",
+ IDLType.Tags.int16: "",
+ IDLType.Tags.uint16: "",
+ IDLType.Tags.int32: "",
+ IDLType.Tags.uint32: "U",
+ IDLType.Tags.int64: "LL",
+ IDLType.Tags.uint64: "ULL",
+ IDLType.Tags.unrestricted_float: "F",
+ IDLType.Tags.float: "F",
+ IDLType.Tags.unrestricted_double: "",
+ IDLType.Tags.double: "",
+}
+
+
+def numericValue(t, v):
+ if t == IDLType.Tags.unrestricted_double or t == IDLType.Tags.unrestricted_float:
+ typeName = builtinNames[t]
+ if v == float("inf"):
+ return "mozilla::PositiveInfinity<%s>()" % typeName
+ if v == float("-inf"):
+ return "mozilla::NegativeInfinity<%s>()" % typeName
+ if math.isnan(v):
+ return "mozilla::UnspecifiedNaN<%s>()" % typeName
+ return "%s%s" % (v, numericSuffixes[t])
+
+
+class CastableObjectUnwrapper:
+ """
+ A class for unwrapping an object stored in a JS Value (or
+ MutableHandle<Value> or Handle<Value>) named by the "source" and
+ "mutableSource" arguments based on the passed-in descriptor and storing it
+ in a variable called by the name in the "target" argument. The "source"
+ argument should be able to produce a Value or Handle<Value>; the
+ "mutableSource" argument should be able to produce a MutableHandle<Value>
+
+ codeOnFailure is the code to run if unwrapping fails.
+
+ If isCallbackReturnValue is "JSImpl" and our descriptor is also
+ JS-implemented, fall back to just creating the right object if what we
+ have isn't one already.
+ """
+
+ def __init__(
+ self,
+ descriptor,
+ source,
+ mutableSource,
+ target,
+ codeOnFailure,
+ exceptionCode=None,
+ isCallbackReturnValue=False,
+ ):
+ self.substitution = {
+ "type": descriptor.nativeType,
+ "protoID": "prototypes::id::" + descriptor.name,
+ "target": target,
+ "codeOnFailure": codeOnFailure,
+ "source": source,
+ "mutableSource": mutableSource,
+ }
+
+ if isCallbackReturnValue == "JSImpl" and descriptor.interface.isJSImplemented():
+ exceptionCode = exceptionCode or codeOnFailure
+ self.substitution["codeOnFailure"] = fill(
+ """
+ // Be careful to not wrap random DOM objects here, even if
+ // they're wrapped in opaque security wrappers for some reason.
+ // XXXbz Wish we could check for a JS-implemented object
+ // that already has a content reflection...
+ if (!IsDOMObject(js::UncheckedUnwrap(&${source}.toObject()))) {
+ nsCOMPtr<nsIGlobalObject> contentGlobal;
+ JS::Rooted<JSObject*> callback(cx, CallbackOrNull());
+ if (!callback ||
+ !GetContentGlobalForJSImplementedObject(cx, callback, getter_AddRefs(contentGlobal))) {
+ $*{exceptionCode}
+ }
+ JS::Rooted<JSObject*> jsImplSourceObj(cx, &${source}.toObject());
+ MOZ_RELEASE_ASSERT(!js::IsWrapper(jsImplSourceObj),
+ "Don't return JS implementations from other compartments");
+ JS::Rooted<JSObject*> jsImplSourceGlobal(cx, JS::GetNonCCWObjectGlobal(jsImplSourceObj));
+ ${target} = new ${type}(jsImplSourceObj, jsImplSourceGlobal, contentGlobal);
+ } else {
+ $*{codeOnFailure}
+ }
+ """,
+ exceptionCode=exceptionCode,
+ **self.substitution,
+ )
+ else:
+ self.substitution["codeOnFailure"] = codeOnFailure
+
+ def __str__(self):
+ substitution = self.substitution.copy()
+ substitution["codeOnFailure"] %= {
+ "securityError": "rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO"
+ }
+ return fill(
+ """
+ {
+ // Our JSContext should be in the right global to do unwrapping in.
+ nsresult rv = UnwrapObject<${protoID}, ${type}>(${mutableSource}, ${target}, cx);
+ if (NS_FAILED(rv)) {
+ $*{codeOnFailure}
+ }
+ }
+ """,
+ **substitution,
+ )
+
+
+class FailureFatalCastableObjectUnwrapper(CastableObjectUnwrapper):
+ """
+ As CastableObjectUnwrapper, but defaulting to throwing if unwrapping fails
+ """
+
+ def __init__(
+ self,
+ descriptor,
+ source,
+ mutableSource,
+ target,
+ exceptionCode,
+ isCallbackReturnValue,
+ sourceDescription,
+ ):
+ CastableObjectUnwrapper.__init__(
+ self,
+ descriptor,
+ source,
+ mutableSource,
+ target,
+ 'cx.ThrowErrorMessage<MSG_DOES_NOT_IMPLEMENT_INTERFACE>("%s", "%s");\n'
+ "%s"
+ % (sourceDescription, descriptor.interface.identifier.name, exceptionCode),
+ exceptionCode,
+ isCallbackReturnValue,
+ )
+
+
+def getCallbackConversionInfo(
+ type, idlObject, isMember, isCallbackReturnValue, isOptional
+):
+ """
+ Returns a tuple containing the declType, declArgs, and basic
+ conversion for the given callback type, with the given callback
+ idl object in the given context (isMember/isCallbackReturnValue/isOptional).
+ """
+ name = idlObject.identifier.name
+
+ # We can't use fast callbacks if isOptional because then we get an
+ # Optional<RootedCallback> thing, which is not transparent to consumers.
+ useFastCallback = (
+ (not isMember or isMember == "Union")
+ and not isCallbackReturnValue
+ and not isOptional
+ )
+ if useFastCallback:
+ name = "binding_detail::Fast%s" % name
+ rootArgs = ""
+ args = "&${val}.toObject(), JS::CurrentGlobalOrNull(cx)"
+ else:
+ rootArgs = dedent(
+ """
+ JS::Rooted<JSObject*> tempRoot(cx, &${val}.toObject());
+ JS::Rooted<JSObject*> tempGlobalRoot(cx, JS::CurrentGlobalOrNull(cx));
+ """
+ )
+ args = "cx, tempRoot, tempGlobalRoot, GetIncumbentGlobal()"
+
+ if type.nullable() or isCallbackReturnValue:
+ declType = CGGeneric("RefPtr<%s>" % name)
+ else:
+ declType = CGGeneric("OwningNonNull<%s>" % name)
+
+ if useFastCallback:
+ declType = CGTemplatedType("RootedCallback", declType)
+ declArgs = "cx"
+ else:
+ declArgs = None
+
+ conversion = fill(
+ """
+ { // scope for tempRoot and tempGlobalRoot if needed
+ $*{rootArgs}
+ $${declName} = new ${name}(${args});
+ }
+ """,
+ rootArgs=rootArgs,
+ name=name,
+ args=args,
+ )
+ return (declType, declArgs, conversion)
+
+
+class JSToNativeConversionInfo:
+ """
+ An object representing information about a JS-to-native conversion.
+ """
+
+ def __init__(
+ self,
+ template,
+ declType=None,
+ holderType=None,
+ dealWithOptional=False,
+ declArgs=None,
+ holderArgs=None,
+ ):
+ """
+ template: A string representing the conversion code. This will have
+ template substitution performed on it as follows:
+
+ ${val} is a handle to the JS::Value in question
+ ${maybeMutableVal} May be a mutable handle to the JS::Value in
+ question. This is only OK to use if ${val} is
+ known to not be undefined.
+ ${holderName} replaced by the holder's name, if any
+ ${declName} replaced by the declaration's name
+ ${haveValue} replaced by an expression that evaluates to a boolean
+ for whether we have a JS::Value. Only used when
+ defaultValue is not None or when True is passed for
+ checkForValue to instantiateJSToNativeConversion.
+ This expression may not be already-parenthesized, so if
+ you use it with && or || make sure to put parens
+ around it.
+ ${passedToJSImpl} replaced by an expression that evaluates to a boolean
+ for whether this value is being passed to a JS-
+ implemented interface.
+
+ declType: A CGThing representing the native C++ type we're converting
+ to. This is allowed to be None if the conversion code is
+ supposed to be used as-is.
+
+ holderType: A CGThing representing the type of a "holder" which will
+ hold a possible reference to the C++ thing whose type we
+ returned in declType, or None if no such holder is needed.
+
+ dealWithOptional: A boolean indicating whether the caller has to do
+ optional-argument handling. This should only be set
+ to true if the JS-to-native conversion is being done
+ for an optional argument or dictionary member with no
+ default value and if the returned template expects
+ both declType and holderType to be wrapped in
+ Optional<>, with ${declName} and ${holderName}
+ adjusted to point to the Value() of the Optional, and
+ Construct() calls to be made on the Optional<>s as
+ needed.
+
+ declArgs: If not None, the arguments to pass to the ${declName}
+ constructor. These will have template substitution performed
+ on them so you can use things like ${val}. This is a
+ single string, not a list of strings.
+
+ holderArgs: If not None, the arguments to pass to the ${holderName}
+ constructor. These will have template substitution
+ performed on them so you can use things like ${val}.
+ This is a single string, not a list of strings.
+
+ ${declName} must be in scope before the code from 'template' is entered.
+
+ If holderType is not None then ${holderName} must be in scope before
+ the code from 'template' is entered.
+ """
+ assert isinstance(template, str)
+ assert declType is None or isinstance(declType, CGThing)
+ assert holderType is None or isinstance(holderType, CGThing)
+ self.template = template
+ self.declType = declType
+ self.holderType = holderType
+ self.dealWithOptional = dealWithOptional
+ self.declArgs = declArgs
+ self.holderArgs = holderArgs
+
+
+def getHandleDefault(defaultValue):
+ tag = defaultValue.type.tag()
+ if tag in numericSuffixes:
+ # Some numeric literals require a suffix to compile without warnings
+ return numericValue(tag, defaultValue.value)
+ assert tag == IDLType.Tags.bool
+ return toStringBool(defaultValue.value)
+
+
+def handleDefaultStringValue(defaultValue, method):
+ """
+ Returns a string which ends up calling 'method' with a (char_t*, length)
+ pair that sets this string default value. This string is suitable for
+ passing as the second argument of handleDefault.
+ """
+ assert (
+ defaultValue.type.isDOMString()
+ or defaultValue.type.isUSVString()
+ or defaultValue.type.isUTF8String()
+ or defaultValue.type.isByteString()
+ )
+ # There shouldn't be any non-ASCII or embedded nulls in here; if
+ # it ever sneaks in we will need to think about how to properly
+ # represent that in the C++.
+ assert all(ord(c) < 128 and ord(c) > 0 for c in defaultValue.value)
+ if defaultValue.type.isByteString() or defaultValue.type.isUTF8String():
+ prefix = ""
+ else:
+ prefix = "u"
+ return fill(
+ """
+ ${method}(${prefix}"${value}");
+ """,
+ method=method,
+ prefix=prefix,
+ value=defaultValue.value,
+ )
+
+
+def recordKeyType(recordType):
+ assert recordType.keyType.isString()
+ if recordType.keyType.isByteString() or recordType.keyType.isUTF8String():
+ return "nsCString"
+ return "nsString"
+
+
+def recordKeyDeclType(recordType):
+ return CGGeneric(recordKeyType(recordType))
+
+
+def initializerForType(type):
+ """
+ Get the right initializer for the given type for a data location where we
+ plan to then initialize it from a JS::Value. Some types need to always be
+ initialized even before we start the JS::Value-to-IDL-value conversion.
+
+ Returns a string or None if no initialization is needed.
+ """
+ if type.isObject():
+ return "nullptr"
+ # We could probably return CGDictionary.getNonInitializingCtorArg() for the
+ # dictionary case, but code outside DictionaryBase subclasses can't use
+ # that, so we can't do it across the board.
+ return None
+
+
+# If this function is modified, modify CGNativeMember.getArg and
+# CGNativeMember.getRetvalInfo accordingly. The latter cares about the decltype
+# and holdertype we end up using, because it needs to be able to return the code
+# that will convert those to the actual return value of the callback function.
+def getJSToNativeConversionInfo(
+ type,
+ descriptorProvider,
+ failureCode=None,
+ isDefinitelyObject=False,
+ isMember=False,
+ isOptional=False,
+ invalidEnumValueFatal=True,
+ defaultValue=None,
+ isNullOrUndefined=False,
+ isKnownMissing=False,
+ exceptionCode=None,
+ lenientFloatCode=None,
+ allowTreatNonCallableAsNull=False,
+ isCallbackReturnValue=False,
+ sourceDescription="value",
+ nestingLevel="",
+):
+ """
+ Get a template for converting a JS value to a native object based on the
+ given type and descriptor. If failureCode is given, then we're actually
+ testing whether we can convert the argument to the desired type. That
+ means that failures to convert due to the JS value being the wrong type of
+ value need to use failureCode instead of throwing exceptions. Failures to
+ convert that are due to JS exceptions (from toString or valueOf methods) or
+ out of memory conditions need to throw exceptions no matter what
+ failureCode is. However what actually happens when throwing an exception
+ can be controlled by exceptionCode. The only requirement on that is that
+ exceptionCode must end up doing a return, and every return from this
+ function must happen via exceptionCode if exceptionCode is not None.
+
+ If isDefinitelyObject is True, that means we have a value and the value
+ tests true for isObject(), so we have no need to recheck that.
+
+ If isNullOrUndefined is True, that means we have a value and the value
+ tests true for isNullOrUndefined(), so we have no need to recheck that.
+
+ If isKnownMissing is True, that means that we are known-missing, and for
+ cases when we have a default value we only need to output the default value.
+
+ if isMember is not False, we're being converted from a property of some JS
+ object, not from an actual method argument, so we can't rely on our jsval
+ being rooted or outliving us in any way. Callers can pass "Dictionary",
+ "Variadic", "Sequence", "Union", or "OwningUnion" to indicate that the conversion
+ is for something that is a dictionary member, a variadic argument, a sequence,
+ an union, or an owning union respectively.
+ XXX Once we swtich *Rooter to Rooted* for Record and Sequence type entirely,
+ we could remove "Union" from isMember.
+
+ If isOptional is true, then we are doing conversion of an optional
+ argument with no default value.
+
+ invalidEnumValueFatal controls whether an invalid enum value conversion
+ attempt will throw (if true) or simply return without doing anything (if
+ false).
+
+ If defaultValue is not None, it's the IDL default value for this conversion
+
+ If isEnforceRange is true, we're converting an integer and throwing if the
+ value is out of range.
+
+ If isClamp is true, we're converting an integer and clamping if the
+ value is out of range.
+
+ If isAllowShared is false, we're converting a buffer source and throwing if
+ it is a SharedArrayBuffer or backed by a SharedArrayBuffer.
+
+ If lenientFloatCode is not None, it should be used in cases when
+ we're a non-finite float that's not unrestricted.
+
+ If allowTreatNonCallableAsNull is true, then [TreatNonCallableAsNull] and
+ [LegacyTreatNonObjectAsNull] extended attributes on nullable callback functions
+ will be honored.
+
+ If isCallbackReturnValue is "JSImpl" or "Callback", then the declType may be
+ adjusted to make it easier to return from a callback. Since that type is
+ never directly observable by any consumers of the callback code, this is OK.
+ Furthermore, if isCallbackReturnValue is "JSImpl", that affects the behavior
+ of the FailureFatalCastableObjectUnwrapper conversion; this is used for
+ implementing auto-wrapping of JS-implemented return values from a
+ JS-implemented interface.
+
+ sourceDescription is a description of what this JS value represents, to be
+ used in error reporting. Callers should assume that it might get placed in
+ the middle of a sentence. If it ends up at the beginning of a sentence, its
+ first character will be automatically uppercased.
+
+ The return value from this function is a JSToNativeConversionInfo.
+ """
+ # If we have a defaultValue then we're not actually optional for
+ # purposes of what we need to be declared as.
+ assert defaultValue is None or not isOptional
+
+ # Also, we should not have a defaultValue if we know we're an object
+ assert not isDefinitelyObject or defaultValue is None
+
+ # And we can't both be an object and be null or undefined
+ assert not isDefinitelyObject or not isNullOrUndefined
+
+ isClamp = type.hasClamp()
+ isEnforceRange = type.hasEnforceRange()
+ isAllowShared = type.hasAllowShared()
+
+ # If exceptionCode is not set, we'll just rethrow the exception we got.
+ # Note that we can't just set failureCode to exceptionCode, because setting
+ # failureCode will prevent pending exceptions from being set in cases when
+ # they really should be!
+ if exceptionCode is None:
+ exceptionCode = "return false;\n"
+
+ # Unfortunately, .capitalize() on a string will lowercase things inside the
+ # string, which we do not want.
+ def firstCap(string):
+ return string[0].upper() + string[1:]
+
+ # Helper functions for dealing with failures due to the JS value being the
+ # wrong type of value
+ def onFailureNotAnObject(failureCode):
+ return CGGeneric(
+ failureCode
+ or (
+ 'cx.ThrowErrorMessage<MSG_NOT_OBJECT>("%s");\n'
+ "%s" % (firstCap(sourceDescription), exceptionCode)
+ )
+ )
+
+ def onFailureBadType(failureCode, typeName):
+ return CGGeneric(
+ failureCode
+ or (
+ 'cx.ThrowErrorMessage<MSG_DOES_NOT_IMPLEMENT_INTERFACE>("%s", "%s");\n'
+ "%s" % (firstCap(sourceDescription), typeName, exceptionCode)
+ )
+ )
+
+ # It's a failure in the committed-to conversion, not a failure to match up
+ # to a type, so we don't want to use failureCode in here. We want to just
+ # throw an exception unconditionally.
+ def onFailureIsShared():
+ return CGGeneric(
+ 'cx.ThrowErrorMessage<MSG_TYPEDARRAY_IS_SHARED>("%s");\n'
+ "%s" % (firstCap(sourceDescription), exceptionCode)
+ )
+
+ def onFailureIsLarge():
+ return CGGeneric(
+ 'cx.ThrowErrorMessage<MSG_TYPEDARRAY_IS_LARGE>("%s");\n'
+ "%s" % (firstCap(sourceDescription), exceptionCode)
+ )
+
+ def onFailureNotCallable(failureCode):
+ return CGGeneric(
+ failureCode
+ or (
+ 'cx.ThrowErrorMessage<MSG_NOT_CALLABLE>("%s");\n'
+ "%s" % (firstCap(sourceDescription), exceptionCode)
+ )
+ )
+
+ # A helper function for handling default values. Takes a template
+ # body and the C++ code to set the default value and wraps the
+ # given template body in handling for the default value.
+ def handleDefault(template, setDefault):
+ if defaultValue is None:
+ return template
+ if isKnownMissing:
+ return fill(
+ """
+ {
+ // scope for any temporaries our default value setting needs.
+ $*{setDefault}
+ }
+ """,
+ setDefault=setDefault,
+ )
+ return fill(
+ """
+ if ($${haveValue}) {
+ $*{templateBody}
+ } else {
+ $*{setDefault}
+ }
+ """,
+ templateBody=template,
+ setDefault=setDefault,
+ )
+
+ # A helper function for wrapping up the template body for
+ # possibly-nullable objecty stuff
+ def wrapObjectTemplate(templateBody, type, codeToSetNull, failureCode=None):
+ if isNullOrUndefined and type.nullable():
+ # Just ignore templateBody and set ourselves to null.
+ # Note that we don't have to worry about default values
+ # here either, since we already examined this value.
+ return codeToSetNull
+
+ if not isDefinitelyObject:
+ # Handle the non-object cases by wrapping up the whole
+ # thing in an if cascade.
+ if type.nullable():
+ elifLine = "} else if (${val}.isNullOrUndefined()) {\n"
+ elifBody = codeToSetNull
+ else:
+ elifLine = ""
+ elifBody = ""
+
+ # Note that $${val} below expands to ${val}. This string is
+ # used as a template later, and val will be filled in then.
+ templateBody = fill(
+ """
+ if ($${val}.isObject()) {
+ $*{templateBody}
+ $*{elifLine}
+ $*{elifBody}
+ } else {
+ $*{failureBody}
+ }
+ """,
+ templateBody=templateBody,
+ elifLine=elifLine,
+ elifBody=elifBody,
+ failureBody=onFailureNotAnObject(failureCode).define(),
+ )
+
+ if isinstance(defaultValue, IDLNullValue):
+ assert type.nullable() # Parser should enforce this
+ templateBody = handleDefault(templateBody, codeToSetNull)
+ elif isinstance(defaultValue, IDLEmptySequenceValue):
+ # Our caller will handle it
+ pass
+ else:
+ assert defaultValue is None
+
+ return templateBody
+
+ # A helper function for converting things that look like a JSObject*.
+ def handleJSObjectType(
+ type, isMember, failureCode, exceptionCode, sourceDescription
+ ):
+ if not isMember or isMember == "Union":
+ if isOptional:
+ # We have a specialization of Optional that will use a
+ # Rooted for the storage here.
+ declType = CGGeneric("JS::Handle<JSObject*>")
+ else:
+ declType = CGGeneric("JS::Rooted<JSObject*>")
+ declArgs = "cx"
+ else:
+ assert isMember in (
+ "Sequence",
+ "Variadic",
+ "Dictionary",
+ "OwningUnion",
+ "Record",
+ )
+ # We'll get traced by the sequence or dictionary or union tracer
+ declType = CGGeneric("JSObject*")
+ declArgs = None
+ templateBody = "${declName} = &${val}.toObject();\n"
+
+ # For JS-implemented APIs, we refuse to allow passing objects that the
+ # API consumer does not subsume. The extra parens around
+ # ($${passedToJSImpl}) suppress unreachable code warnings when
+ # $${passedToJSImpl} is the literal `false`. But Apple is shipping a
+ # buggy clang (clang 3.9) in Xcode 8.3, so there even the parens are not
+ # enough. So we manually disable some warnings in clang.
+ if (
+ not isinstance(descriptorProvider, Descriptor)
+ or descriptorProvider.interface.isJSImplemented()
+ ):
+ templateBody = (
+ fill(
+ """
+ #ifdef __clang__
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wunreachable-code"
+ #pragma clang diagnostic ignored "-Wunreachable-code-return"
+ #endif // __clang__
+ if (($${passedToJSImpl}) && !CallerSubsumes($${val})) {
+ cx.ThrowErrorMessage<MSG_PERMISSION_DENIED_TO_PASS_ARG>("${sourceDescription}");
+ $*{exceptionCode}
+ }
+ #ifdef __clang__
+ #pragma clang diagnostic pop
+ #endif // __clang__
+ """,
+ sourceDescription=sourceDescription,
+ exceptionCode=exceptionCode,
+ )
+ + templateBody
+ )
+
+ setToNullCode = "${declName} = nullptr;\n"
+ template = wrapObjectTemplate(templateBody, type, setToNullCode, failureCode)
+ return JSToNativeConversionInfo(
+ template, declType=declType, dealWithOptional=isOptional, declArgs=declArgs
+ )
+
+ def incrementNestingLevel():
+ if nestingLevel == "":
+ return 1
+ return nestingLevel + 1
+
+ assert not (isEnforceRange and isClamp) # These are mutually exclusive
+
+ if type.isSequence() or type.isObservableArray():
+ assert not isEnforceRange and not isClamp and not isAllowShared
+
+ if failureCode is None:
+ notSequence = (
+ 'cx.ThrowErrorMessage<MSG_CONVERSION_ERROR>("%s", "%s");\n'
+ "%s"
+ % (
+ firstCap(sourceDescription),
+ "sequence" if type.isSequence() else "observable array",
+ exceptionCode,
+ )
+ )
+ else:
+ notSequence = failureCode
+
+ nullable = type.nullable()
+ # Be very careful not to change "type": we need it later
+ if nullable:
+ elementType = type.inner.inner
+ else:
+ elementType = type.inner
+
+ # We want to use auto arrays if we can, but we have to be careful with
+ # reallocation behavior for arrays. In particular, if we use auto
+ # arrays for sequences and have a sequence of elements which are
+ # themselves sequences or have sequences as members, we have a problem.
+ # In that case, resizing the outermost AutoTArray to the right size
+ # will memmove its elements, but AutoTArrays are not memmovable and
+ # hence will end up with pointers to bogus memory, which is bad. To
+ # deal with this, we typically map WebIDL sequences to our Sequence
+ # type, which is in fact memmovable. The one exception is when we're
+ # passing in a sequence directly as an argument without any sort of
+ # optional or nullable complexity going on. In that situation, we can
+ # use an AutoSequence instead. We have to keep using Sequence in the
+ # nullable and optional cases because we don't want to leak the
+ # AutoSequence type to consumers, which would be unavoidable with
+ # Nullable<AutoSequence> or Optional<AutoSequence>.
+ if (
+ (isMember and isMember != "Union")
+ or isOptional
+ or nullable
+ or isCallbackReturnValue
+ ):
+ sequenceClass = "Sequence"
+ else:
+ sequenceClass = "binding_detail::AutoSequence"
+
+ # XXXbz we can't include the index in the sourceDescription, because
+ # we don't really have a way to pass one in dynamically at runtime...
+ elementInfo = getJSToNativeConversionInfo(
+ elementType,
+ descriptorProvider,
+ isMember="Sequence",
+ exceptionCode=exceptionCode,
+ lenientFloatCode=lenientFloatCode,
+ isCallbackReturnValue=isCallbackReturnValue,
+ sourceDescription="element of %s" % sourceDescription,
+ nestingLevel=incrementNestingLevel(),
+ )
+ if elementInfo.dealWithOptional:
+ raise TypeError("Shouldn't have optional things in sequences")
+ if elementInfo.holderType is not None:
+ raise TypeError("Shouldn't need holders for sequences")
+
+ typeName = CGTemplatedType(sequenceClass, elementInfo.declType)
+ sequenceType = typeName.define()
+
+ if isMember == "Union" and typeNeedsRooting(type):
+ assert not nullable
+ typeName = CGTemplatedType(
+ "binding_detail::RootedAutoSequence", elementInfo.declType
+ )
+ elif nullable:
+ typeName = CGTemplatedType("Nullable", typeName)
+
+ if nullable:
+ arrayRef = "${declName}.SetValue()"
+ else:
+ arrayRef = "${declName}"
+
+ elementConversion = string.Template(elementInfo.template).substitute(
+ {
+ "val": "temp" + str(nestingLevel),
+ "maybeMutableVal": "&temp" + str(nestingLevel),
+ "declName": "slot" + str(nestingLevel),
+ # We only need holderName here to handle isExternal()
+ # interfaces, which use an internal holder for the
+ # conversion even when forceOwningType ends up true.
+ "holderName": "tempHolder" + str(nestingLevel),
+ "passedToJSImpl": "${passedToJSImpl}",
+ }
+ )
+
+ elementInitializer = initializerForType(elementType)
+ if elementInitializer is None:
+ elementInitializer = ""
+ else:
+ elementInitializer = elementInitializer + ", "
+
+ # NOTE: Keep this in sync with variadic conversions as needed
+ templateBody = fill(
+ """
+ JS::ForOfIterator iter${nestingLevel}(cx);
+ if (!iter${nestingLevel}.init($${val}, JS::ForOfIterator::AllowNonIterable)) {
+ $*{exceptionCode}
+ }
+ if (!iter${nestingLevel}.valueIsIterable()) {
+ $*{notSequence}
+ }
+ ${sequenceType} &arr${nestingLevel} = ${arrayRef};
+ JS::Rooted<JS::Value> temp${nestingLevel}(cx);
+ while (true) {
+ bool done${nestingLevel};
+ if (!iter${nestingLevel}.next(&temp${nestingLevel}, &done${nestingLevel})) {
+ $*{exceptionCode}
+ }
+ if (done${nestingLevel}) {
+ break;
+ }
+ ${elementType}* slotPtr${nestingLevel} = arr${nestingLevel}.AppendElement(${elementInitializer}mozilla::fallible);
+ if (!slotPtr${nestingLevel}) {
+ JS_ReportOutOfMemory(cx);
+ $*{exceptionCode}
+ }
+ ${elementType}& slot${nestingLevel} = *slotPtr${nestingLevel};
+ $*{elementConversion}
+ }
+ """,
+ exceptionCode=exceptionCode,
+ notSequence=notSequence,
+ sequenceType=sequenceType,
+ arrayRef=arrayRef,
+ elementType=elementInfo.declType.define(),
+ elementConversion=elementConversion,
+ elementInitializer=elementInitializer,
+ nestingLevel=str(nestingLevel),
+ )
+
+ templateBody = wrapObjectTemplate(
+ templateBody, type, "${declName}.SetNull();\n", notSequence
+ )
+ if isinstance(defaultValue, IDLEmptySequenceValue):
+ if type.nullable():
+ codeToSetEmpty = "${declName}.SetValue();\n"
+ else:
+ codeToSetEmpty = (
+ "/* ${declName} array is already empty; nothing to do */\n"
+ )
+ templateBody = handleDefault(templateBody, codeToSetEmpty)
+
+ declArgs = None
+ holderType = None
+ holderArgs = None
+ # Sequence arguments that might contain traceable things need
+ # to get traced
+ if typeNeedsRooting(elementType):
+ if not isMember:
+ holderType = CGTemplatedType("SequenceRooter", elementInfo.declType)
+ # If our sequence is nullable, this will set the Nullable to be
+ # not-null, but that's ok because we make an explicit SetNull() call
+ # on it as needed if our JS value is actually null.
+ holderArgs = "cx, &%s" % arrayRef
+ elif isMember == "Union":
+ declArgs = "cx"
+
+ return JSToNativeConversionInfo(
+ templateBody,
+ declType=typeName,
+ declArgs=declArgs,
+ holderType=holderType,
+ dealWithOptional=isOptional,
+ holderArgs=holderArgs,
+ )
+
+ if type.isRecord():
+ assert not isEnforceRange and not isClamp and not isAllowShared
+ if failureCode is None:
+ notRecord = 'cx.ThrowErrorMessage<MSG_NOT_OBJECT>("%s");\n' "%s" % (
+ firstCap(sourceDescription),
+ exceptionCode,
+ )
+ else:
+ notRecord = failureCode
+
+ nullable = type.nullable()
+ # Be very careful not to change "type": we need it later
+ if nullable:
+ recordType = type.inner
+ else:
+ recordType = type
+ valueType = recordType.inner
+
+ valueInfo = getJSToNativeConversionInfo(
+ valueType,
+ descriptorProvider,
+ isMember="Record",
+ exceptionCode=exceptionCode,
+ lenientFloatCode=lenientFloatCode,
+ isCallbackReturnValue=isCallbackReturnValue,
+ sourceDescription="value in %s" % sourceDescription,
+ nestingLevel=incrementNestingLevel(),
+ )
+ if valueInfo.dealWithOptional:
+ raise TypeError("Shouldn't have optional things in record")
+ if valueInfo.holderType is not None:
+ raise TypeError("Shouldn't need holders for record")
+
+ declType = CGTemplatedType(
+ "Record", [recordKeyDeclType(recordType), valueInfo.declType]
+ )
+ typeName = declType.define()
+
+ if isMember == "Union" and typeNeedsRooting(type):
+ assert not nullable
+ declType = CGTemplatedType(
+ "RootedRecord", [recordKeyDeclType(recordType), valueInfo.declType]
+ )
+ elif nullable:
+ declType = CGTemplatedType("Nullable", declType)
+
+ if nullable:
+ recordRef = "${declName}.SetValue()"
+ else:
+ recordRef = "${declName}"
+
+ valueConversion = string.Template(valueInfo.template).substitute(
+ {
+ "val": "temp",
+ "maybeMutableVal": "&temp",
+ "declName": "slot",
+ # We only need holderName here to handle isExternal()
+ # interfaces, which use an internal holder for the
+ # conversion even when forceOwningType ends up true.
+ "holderName": "tempHolder",
+ "passedToJSImpl": "${passedToJSImpl}",
+ }
+ )
+
+ keyType = recordKeyType(recordType)
+ if recordType.keyType.isJSString():
+ raise TypeError(
+ "Have do deal with JSString record type, but don't know how"
+ )
+ if recordType.keyType.isByteString() or recordType.keyType.isUTF8String():
+ hashKeyType = "nsCStringHashKey"
+ if recordType.keyType.isByteString():
+ keyConversionFunction = "ConvertJSValueToByteString"
+ else:
+ keyConversionFunction = "ConvertJSValueToString"
+
+ else:
+ hashKeyType = "nsStringHashKey"
+ if recordType.keyType.isDOMString():
+ keyConversionFunction = "ConvertJSValueToString"
+ else:
+ assert recordType.keyType.isUSVString()
+ keyConversionFunction = "ConvertJSValueToUSVString"
+
+ templateBody = fill(
+ """
+ auto& recordEntries = ${recordRef}.Entries();
+
+ JS::Rooted<JSObject*> recordObj(cx, &$${val}.toObject());
+ JS::RootedVector<jsid> ids(cx);
+ if (!js::GetPropertyKeys(cx, recordObj,
+ JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &ids)) {
+ $*{exceptionCode}
+ }
+ if (!recordEntries.SetCapacity(ids.length(), mozilla::fallible)) {
+ JS_ReportOutOfMemory(cx);
+ $*{exceptionCode}
+ }
+ JS::Rooted<JS::Value> propNameValue(cx);
+ JS::Rooted<JS::Value> temp(cx);
+ JS::Rooted<jsid> curId(cx);
+ JS::Rooted<JS::Value> idVal(cx);
+ // Use a hashset to keep track of ids seen, to avoid
+ // introducing nasty O(N^2) behavior scanning for them all the
+ // time. Ideally we'd use a data structure with O(1) lookup
+ // _and_ ordering for the MozMap, but we don't have one lying
+ // around.
+ nsTHashtable<${hashKeyType}> idsSeen;
+ for (size_t i = 0; i < ids.length(); ++i) {
+ curId = ids[i];
+
+ JS::Rooted<mozilla::Maybe<JS::PropertyDescriptor>> desc(cx);
+ if (!JS_GetOwnPropertyDescriptorById(cx, recordObj, curId,
+ &desc)) {
+ $*{exceptionCode}
+ }
+
+ if (desc.isNothing() || !desc->enumerable()) {
+ continue;
+ }
+
+ idVal = js::IdToValue(curId);
+ ${keyType} propName;
+ // This will just throw if idVal is a Symbol, like the spec says
+ // to do.
+ if (!${keyConversionFunction}(cx, idVal, "key of ${sourceDescription}", propName)) {
+ $*{exceptionCode}
+ }
+
+ if (!JS_GetPropertyById(cx, recordObj, curId, &temp)) {
+ $*{exceptionCode}
+ }
+
+ ${typeName}::EntryType* entry;
+ if (!idsSeen.EnsureInserted(propName)) {
+ // Find the existing entry.
+ auto idx = recordEntries.IndexOf(propName);
+ MOZ_ASSERT(idx != recordEntries.NoIndex,
+ "Why is it not found?");
+ // Now blow it away to make it look like it was just added
+ // to the array, because it's not obvious that it's
+ // safe to write to its already-initialized mValue via our
+ // normal codegen conversions. For example, the value
+ // could be a union and this would change its type, but
+ // codegen assumes we won't do that.
+ entry = recordEntries.ReconstructElementAt(idx);
+ } else {
+ // Safe to do an infallible append here, because we did a
+ // SetCapacity above to the right capacity.
+ entry = recordEntries.AppendElement();
+ }
+ entry->mKey = propName;
+ ${valueType}& slot = entry->mValue;
+ $*{valueConversion}
+ }
+ """,
+ exceptionCode=exceptionCode,
+ recordRef=recordRef,
+ hashKeyType=hashKeyType,
+ keyType=keyType,
+ keyConversionFunction=keyConversionFunction,
+ sourceDescription=sourceDescription,
+ typeName=typeName,
+ valueType=valueInfo.declType.define(),
+ valueConversion=valueConversion,
+ )
+
+ templateBody = wrapObjectTemplate(
+ templateBody, type, "${declName}.SetNull();\n", notRecord
+ )
+
+ declArgs = None
+ holderType = None
+ holderArgs = None
+ # record arguments that might contain traceable things need
+ # to get traced
+ if not isMember and isCallbackReturnValue:
+ # Go ahead and just convert directly into our actual return value
+ declType = CGWrapper(declType, post="&")
+ declArgs = "aRetVal"
+ elif typeNeedsRooting(valueType):
+ if not isMember:
+ holderType = CGTemplatedType(
+ "RecordRooter", [recordKeyDeclType(recordType), valueInfo.declType]
+ )
+ # If our record is nullable, this will set the Nullable to be
+ # not-null, but that's ok because we make an explicit SetNull() call
+ # on it as needed if our JS value is actually null.
+ holderArgs = "cx, &%s" % recordRef
+ elif isMember == "Union":
+ declArgs = "cx"
+
+ return JSToNativeConversionInfo(
+ templateBody,
+ declType=declType,
+ declArgs=declArgs,
+ holderType=holderType,
+ dealWithOptional=isOptional,
+ holderArgs=holderArgs,
+ )
+
+ if type.isUnion():
+ nullable = type.nullable()
+ if nullable:
+ type = type.inner
+
+ isOwningUnion = (isMember and isMember != "Union") or isCallbackReturnValue
+ unionArgumentObj = "${declName}"
+ if nullable:
+ if isOptional and not isOwningUnion:
+ unionArgumentObj += ".Value()"
+ # If we're owning, we're a Nullable, which hasn't been told it has
+ # a value. Otherwise we're an already-constructed Maybe.
+ unionArgumentObj += ".SetValue()"
+
+ templateBody = CGIfWrapper(
+ CGGeneric(exceptionCode),
+ '!%s.Init(cx, ${val}, "%s", ${passedToJSImpl})'
+ % (unionArgumentObj, firstCap(sourceDescription)),
+ )
+
+ if type.hasNullableType:
+ assert not nullable
+ # Make sure to handle a null default value here
+ if defaultValue and isinstance(defaultValue, IDLNullValue):
+ assert defaultValue.type == type
+ templateBody = CGIfElseWrapper(
+ "!(${haveValue})",
+ CGGeneric("%s.SetNull();\n" % unionArgumentObj),
+ templateBody,
+ )
+
+ typeName = CGUnionStruct.unionTypeDecl(type, isOwningUnion)
+ argumentTypeName = typeName + "Argument"
+ if nullable:
+ typeName = "Nullable<" + typeName + " >"
+
+ declType = CGGeneric(typeName)
+ if isOwningUnion:
+ holderType = None
+ else:
+ holderType = CGGeneric(argumentTypeName)
+ if nullable:
+ holderType = CGTemplatedType("Maybe", holderType)
+
+ # If we're isOptional and not nullable the normal optional handling will
+ # handle lazy construction of our holder. If we're nullable and not
+ # owning we do it all by hand because we do not want our holder
+ # constructed if we're null. But if we're owning we don't have a
+ # holder anyway, so we can do the normal Optional codepath.
+ declLoc = "${declName}"
+ constructDecl = None
+ if nullable:
+ if isOptional and not isOwningUnion:
+ declType = CGTemplatedType("Optional", declType)
+ constructDecl = CGGeneric("${declName}.Construct();\n")
+ declLoc = "${declName}.Value()"
+
+ if not isMember and isCallbackReturnValue:
+ declType = CGWrapper(declType, post="&")
+ declArgs = "aRetVal"
+ else:
+ declArgs = None
+
+ if (
+ defaultValue
+ and not isinstance(defaultValue, IDLNullValue)
+ and not isinstance(defaultValue, IDLDefaultDictionaryValue)
+ ):
+ tag = defaultValue.type.tag()
+
+ if tag in numericSuffixes or tag is IDLType.Tags.bool:
+ defaultStr = getHandleDefault(defaultValue)
+ # Make sure we actually construct the thing inside the nullable.
+ value = declLoc + (".SetValue()" if nullable else "")
+ name = getUnionMemberName(defaultValue.type)
+ default = CGGeneric(
+ "%s.RawSetAs%s() = %s;\n" % (value, name, defaultStr)
+ )
+ elif isinstance(defaultValue, IDLEmptySequenceValue):
+ name = getUnionMemberName(defaultValue.type)
+ # Make sure we actually construct the thing inside the nullable.
+ value = declLoc + (".SetValue()" if nullable else "")
+ if not isOwningUnion and typeNeedsRooting(defaultValue.type):
+ ctorArgs = "cx"
+ else:
+ ctorArgs = ""
+ # It's enough to set us to the right type; that will
+ # create an empty array, which is all we need here.
+ default = CGGeneric("%s.RawSetAs%s(%s);\n" % (value, name, ctorArgs))
+ elif defaultValue.type.isEnum():
+ name = getUnionMemberName(defaultValue.type)
+ # Make sure we actually construct the thing inside the nullable.
+ value = declLoc + (".SetValue()" if nullable else "")
+ default = CGGeneric(
+ "%s.RawSetAs%s() = %s::%s;\n"
+ % (
+ value,
+ name,
+ defaultValue.type.inner.identifier.name,
+ getEnumValueName(defaultValue.value),
+ )
+ )
+ else:
+ default = CGGeneric(
+ handleDefaultStringValue(
+ defaultValue, "%s.SetStringLiteral" % unionArgumentObj
+ )
+ )
+
+ templateBody = CGIfElseWrapper("!(${haveValue})", default, templateBody)
+
+ if nullable:
+ assert not type.hasNullableType
+ if defaultValue:
+ if isinstance(defaultValue, IDLNullValue):
+ extraConditionForNull = "!(${haveValue}) || "
+ else:
+ extraConditionForNull = "(${haveValue}) && "
+ else:
+ extraConditionForNull = ""
+
+ hasUndefinedType = any(t.isUndefined() for t in type.flatMemberTypes)
+ assert not hasUndefinedType or defaultValue is None
+
+ nullTest = (
+ "${val}.isNull()" if hasUndefinedType else "${val}.isNullOrUndefined()"
+ )
+ templateBody = CGIfElseWrapper(
+ extraConditionForNull + nullTest,
+ CGGeneric("%s.SetNull();\n" % declLoc),
+ templateBody,
+ )
+ elif (
+ not type.hasNullableType
+ and defaultValue
+ and isinstance(defaultValue, IDLDefaultDictionaryValue)
+ ):
+ assert type.hasDictionaryType()
+ assert defaultValue.type.isDictionary()
+ if not isOwningUnion and typeNeedsRooting(defaultValue.type):
+ ctorArgs = "cx"
+ else:
+ ctorArgs = ""
+ initDictionaryWithNull = CGIfWrapper(
+ CGGeneric("return false;\n"),
+ (
+ '!%s.RawSetAs%s(%s).Init(cx, JS::NullHandleValue, "Member of %s")'
+ % (
+ declLoc,
+ getUnionMemberName(defaultValue.type),
+ ctorArgs,
+ type.prettyName(),
+ )
+ ),
+ )
+ templateBody = CGIfElseWrapper(
+ "!(${haveValue})", initDictionaryWithNull, templateBody
+ )
+
+ templateBody = CGList([constructDecl, templateBody])
+
+ return JSToNativeConversionInfo(
+ templateBody.define(),
+ declType=declType,
+ declArgs=declArgs,
+ dealWithOptional=isOptional and (not nullable or isOwningUnion),
+ )
+
+ if type.isPromise():
+ assert not type.nullable()
+ assert defaultValue is None
+
+ # We always have to hold a strong ref to Promise here, because
+ # Promise::resolve returns an addrefed thing.
+ argIsPointer = isCallbackReturnValue
+ if argIsPointer:
+ declType = CGGeneric("RefPtr<Promise>")
+ else:
+ declType = CGGeneric("OwningNonNull<Promise>")
+
+ # Per spec, what we're supposed to do is take the original
+ # Promise.resolve and call it with the original Promise as this
+ # value to make a Promise out of whatever value we actually have
+ # here. The question is which global we should use. There are
+ # several cases to consider:
+ #
+ # 1) Normal call to API with a Promise argument. This is a case the
+ # spec covers, and we should be using the current Realm's
+ # Promise. That means the current compartment.
+ # 2) Call to API with a Promise argument over Xrays. In practice,
+ # this sort of thing seems to be used for giving an API
+ # implementation a way to wait for conclusion of an asyc
+ # operation, _not_ to expose the Promise to content code. So we
+ # probably want to allow callers to use such an API in a
+ # "natural" way, by passing chrome-side promises; indeed, that
+ # may be all that the caller has to represent their async
+ # operation. That means we really need to do the
+ # Promise.resolve() in the caller (chrome) compartment: if we do
+ # it in the content compartment, we will try to call .then() on
+ # the chrome promise while in the content compartment, which will
+ # throw and we'll just get a rejected Promise. Note that this is
+ # also the reason why a caller who has a chrome Promise
+ # representing an async operation can't itself convert it to a
+ # content-side Promise (at least not without some serious
+ # gyrations).
+ # 3) Promise return value from a callback or callback interface.
+ # Per spec, this should use the Realm of the callback object. In
+ # our case, that's the compartment of the underlying callback,
+ # not the current compartment (which may be the compartment of
+ # some cross-compartment wrapper around said callback).
+ # 4) Return value from a JS-implemented interface. In this case we
+ # have a problem. Our current compartment is the compartment of
+ # the JS implementation. But if the JS implementation returned
+ # a page-side Promise (which is a totally sane thing to do, and
+ # in fact the right thing to do given that this return value is
+ # going right to content script) then we don't want to
+ # Promise.resolve with our current compartment Promise, because
+ # that will wrap it up in a chrome-side Promise, which is
+ # decidedly _not_ what's desired here. So in that case we
+ # should really unwrap the return value and use the global of
+ # the result. CheckedUnwrapStatic should be good enough for that;
+ # if it fails, then we're failing unwrap while in a
+ # system-privileged compartment, so presumably we have a dead
+ # object wrapper. Just error out. Do NOT fall back to using
+ # the current compartment instead: that will return a
+ # system-privileged rejected (because getting .then inside
+ # resolve() failed) Promise to the caller, which they won't be
+ # able to touch. That's not helpful. If we error out, on the
+ # other hand, they will get a content-side rejected promise.
+ # Same thing if the value returned is not even an object.
+ if isCallbackReturnValue == "JSImpl":
+ # Case 4 above. Note that globalObj defaults to the current
+ # compartment global. Note that we don't use $*{exceptionCode}
+ # here because that will try to aRv.Throw(NS_ERROR_UNEXPECTED)
+ # which we don't really want here.
+ assert exceptionCode == "aRv.Throw(NS_ERROR_UNEXPECTED);\nreturn nullptr;\n"
+ getPromiseGlobal = fill(
+ """
+ if (!$${val}.isObject()) {
+ aRv.ThrowTypeError<MSG_NOT_OBJECT>("${sourceDescription}");
+ return nullptr;
+ }
+ JSObject* unwrappedVal = js::CheckedUnwrapStatic(&$${val}.toObject());
+ if (!unwrappedVal) {
+ // A slight lie, but not much of one, for a dead object wrapper.
+ aRv.ThrowTypeError<MSG_NOT_OBJECT>("${sourceDescription}");
+ return nullptr;
+ }
+ globalObj = JS::GetNonCCWObjectGlobal(unwrappedVal);
+ """,
+ sourceDescription=sourceDescription,
+ )
+ elif isCallbackReturnValue == "Callback":
+ getPromiseGlobal = dedent(
+ """
+ // We basically want our entry global here. Play it safe
+ // and use GetEntryGlobal() to get it, with whatever
+ // principal-clamping it ends up doing.
+ globalObj = GetEntryGlobal()->GetGlobalJSObject();
+ """
+ )
+ else:
+ getPromiseGlobal = dedent(
+ """
+ globalObj = JS::CurrentGlobalOrNull(cx);
+ """
+ )
+
+ templateBody = fill(
+ """
+ { // Scope for our GlobalObject, FastErrorResult, JSAutoRealm,
+ // etc.
+
+ JS::Rooted<JSObject*> globalObj(cx);
+ $*{getPromiseGlobal}
+ JSAutoRealm ar(cx, globalObj);
+ GlobalObject promiseGlobal(cx, globalObj);
+ if (promiseGlobal.Failed()) {
+ $*{exceptionCode}
+ }
+
+ JS::Rooted<JS::Value> valueToResolve(cx, $${val});
+ if (!JS_WrapValue(cx, &valueToResolve)) {
+ $*{exceptionCode}
+ }
+ binding_detail::FastErrorResult promiseRv;
+ nsCOMPtr<nsIGlobalObject> global =
+ do_QueryInterface(promiseGlobal.GetAsSupports());
+ if (!global) {
+ promiseRv.Throw(NS_ERROR_UNEXPECTED);
+ MOZ_ALWAYS_TRUE(promiseRv.MaybeSetPendingException(cx));
+ $*{exceptionCode}
+ }
+ $${declName} = Promise::Resolve(global, cx, valueToResolve,
+ promiseRv);
+ if (promiseRv.MaybeSetPendingException(cx)) {
+ $*{exceptionCode}
+ }
+ }
+ """,
+ getPromiseGlobal=getPromiseGlobal,
+ exceptionCode=exceptionCode,
+ )
+
+ return JSToNativeConversionInfo(
+ templateBody, declType=declType, dealWithOptional=isOptional
+ )
+
+ if type.isGeckoInterface():
+ assert not isEnforceRange and not isClamp and not isAllowShared
+
+ descriptor = descriptorProvider.getDescriptor(
+ type.unroll().inner.identifier.name
+ )
+
+ assert descriptor.nativeType != "JSObject"
+
+ if descriptor.interface.isCallback():
+ (declType, declArgs, conversion) = getCallbackConversionInfo(
+ type, descriptor.interface, isMember, isCallbackReturnValue, isOptional
+ )
+ template = wrapObjectTemplate(
+ conversion, type, "${declName} = nullptr;\n", failureCode
+ )
+ return JSToNativeConversionInfo(
+ template,
+ declType=declType,
+ declArgs=declArgs,
+ dealWithOptional=isOptional,
+ )
+
+ if descriptor.interface.identifier.name == "WindowProxy":
+ declType = CGGeneric("mozilla::dom::WindowProxyHolder")
+ if type.nullable():
+ declType = CGTemplatedType("Nullable", declType)
+ windowProxyHolderRef = "${declName}.SetValue()"
+ else:
+ windowProxyHolderRef = "${declName}"
+
+ failureCode = onFailureBadType(
+ failureCode, descriptor.interface.identifier.name
+ ).define()
+ templateBody = fill(
+ """
+ JS::Rooted<JSObject*> source(cx, &$${val}.toObject());
+ if (NS_FAILED(UnwrapWindowProxyArg(cx, source, ${windowProxyHolderRef}))) {
+ $*{onFailure}
+ }
+ """,
+ windowProxyHolderRef=windowProxyHolderRef,
+ onFailure=failureCode,
+ )
+ templateBody = wrapObjectTemplate(
+ templateBody, type, "${declName}.SetNull();\n", failureCode
+ )
+ return JSToNativeConversionInfo(
+ templateBody, declType=declType, dealWithOptional=isOptional
+ )
+
+ # This is an interface that we implement as a concrete class
+ # or an XPCOM interface.
+
+ # Allow null pointers for nullable types and old-binding classes, and
+ # use an RefPtr or raw pointer for callback return values to make
+ # them easier to return.
+ argIsPointer = (
+ type.nullable() or type.unroll().inner.isExternal() or isCallbackReturnValue
+ )
+
+ # Sequence and dictionary members, as well as owning unions (which can
+ # appear here as return values in JS-implemented interfaces) have to
+ # hold a strong ref to the thing being passed down. Those all set
+ # isMember.
+ #
+ # Also, callback return values always end up addrefing anyway, so there
+ # is no point trying to avoid it here and it makes other things simpler
+ # since we can assume the return value is a strong ref.
+ assert not descriptor.interface.isCallback()
+ forceOwningType = (isMember and isMember != "Union") or isCallbackReturnValue
+
+ typeName = descriptor.nativeType
+ typePtr = typeName + "*"
+
+ # Compute a few things:
+ # - declType is the type we want to return as the first element of our
+ # tuple.
+ # - holderType is the type we want to return as the third element
+ # of our tuple.
+
+ # Set up some sensible defaults for these things insofar as we can.
+ holderType = None
+ if argIsPointer:
+ if forceOwningType:
+ declType = "RefPtr<" + typeName + ">"
+ else:
+ declType = typePtr
+ else:
+ if forceOwningType:
+ declType = "OwningNonNull<" + typeName + ">"
+ else:
+ declType = "NonNull<" + typeName + ">"
+
+ templateBody = ""
+ if forceOwningType:
+ templateBody += fill(
+ """
+ static_assert(IsRefcounted<${typeName}>::value, "We can only store refcounted classes.");
+ """,
+ typeName=typeName,
+ )
+
+ if not descriptor.interface.isExternal():
+ if failureCode is not None:
+ templateBody += str(
+ CastableObjectUnwrapper(
+ descriptor,
+ "${val}",
+ "${maybeMutableVal}",
+ "${declName}",
+ failureCode,
+ )
+ )
+ else:
+ templateBody += str(
+ FailureFatalCastableObjectUnwrapper(
+ descriptor,
+ "${val}",
+ "${maybeMutableVal}",
+ "${declName}",
+ exceptionCode,
+ isCallbackReturnValue,
+ firstCap(sourceDescription),
+ )
+ )
+ else:
+ # External interface. We always have a holder for these, because we
+ # don't actually know whether we have to addref when unwrapping or not.
+ # So we just pass an getter_AddRefs(RefPtr) to XPConnect and if we'll
+ # need a release it'll put a non-null pointer in there.
+ if forceOwningType:
+ # Don't return a holderType in this case; our declName
+ # will just own stuff.
+ templateBody += "RefPtr<" + typeName + "> ${holderName};\n"
+ else:
+ holderType = "RefPtr<" + typeName + ">"
+ templateBody += (
+ "JS::Rooted<JSObject*> source(cx, &${val}.toObject());\n"
+ + "if (NS_FAILED(UnwrapArg<"
+ + typeName
+ + ">(cx, source, getter_AddRefs(${holderName})))) {\n"
+ )
+ templateBody += CGIndenter(
+ onFailureBadType(failureCode, descriptor.interface.identifier.name)
+ ).define()
+ templateBody += "}\n" "MOZ_ASSERT(${holderName});\n"
+
+ # And store our value in ${declName}
+ templateBody += "${declName} = ${holderName};\n"
+
+ # Just pass failureCode, not onFailureBadType, here, so we'll report
+ # the thing as not an object as opposed to not implementing whatever
+ # our interface is.
+ templateBody = wrapObjectTemplate(
+ templateBody, type, "${declName} = nullptr;\n", failureCode
+ )
+
+ declType = CGGeneric(declType)
+ if holderType is not None:
+ holderType = CGGeneric(holderType)
+ return JSToNativeConversionInfo(
+ templateBody,
+ declType=declType,
+ holderType=holderType,
+ dealWithOptional=isOptional,
+ )
+
+ if type.isSpiderMonkeyInterface():
+ assert not isEnforceRange and not isClamp
+ name = type.unroll().name # unroll() because it may be nullable
+ interfaceType = CGGeneric(name)
+ declType = interfaceType
+ if type.nullable():
+ declType = CGTemplatedType("Nullable", declType)
+ objRef = "${declName}.SetValue()"
+ else:
+ objRef = "${declName}"
+
+ # Again, this is a bit strange since we are actually building a
+ # template string here. ${objRef} and $*{badType} below are filled in
+ # right now; $${val} expands to ${val}, to be filled in later.
+ template = fill(
+ """
+ if (!${objRef}.Init(&$${val}.toObject())) {
+ $*{badType}
+ }
+ """,
+ objRef=objRef,
+ badType=onFailureBadType(failureCode, type.name).define(),
+ )
+ if type.isBufferSource():
+ if type.isArrayBuffer():
+ isSharedMethod = "JS::IsSharedArrayBufferObject"
+ isLargeMethod = "JS::IsLargeArrayBufferMaybeShared"
+ else:
+ assert type.isArrayBufferView() or type.isTypedArray()
+ isSharedMethod = "JS::IsArrayBufferViewShared"
+ isLargeMethod = "JS::IsLargeArrayBufferView"
+ if not isAllowShared:
+ template += fill(
+ """
+ if (${isSharedMethod}(${objRef}.Obj())) {
+ $*{badType}
+ }
+ """,
+ isSharedMethod=isSharedMethod,
+ objRef=objRef,
+ badType=onFailureIsShared().define(),
+ )
+ # For now reject large (> 2 GB) ArrayBuffers and ArrayBufferViews.
+ # Supporting this will require changing dom::TypedArray and
+ # consumers.
+ template += fill(
+ """
+ if (${isLargeMethod}(${objRef}.Obj())) {
+ $*{badType}
+ }
+ """,
+ isLargeMethod=isLargeMethod,
+ objRef=objRef,
+ badType=onFailureIsLarge().define(),
+ )
+ template = wrapObjectTemplate(
+ template, type, "${declName}.SetNull();\n", failureCode
+ )
+ if not isMember or isMember == "Union":
+ # This is a bit annoying. In a union we don't want to have a
+ # holder, since unions don't support that. But if we're optional we
+ # want to have a holder, so that the callee doesn't see
+ # Optional<RootedSpiderMonkeyInterface<InterfaceType>>. So do a
+ # holder if we're optional and use a RootedSpiderMonkeyInterface
+ # otherwise.
+ if isOptional:
+ holderType = CGTemplatedType(
+ "SpiderMonkeyInterfaceRooter", interfaceType
+ )
+ # If our SpiderMonkey interface is nullable, this will set the
+ # Nullable to be not-null, but that's ok because we make an
+ # explicit SetNull() call on it as needed if our JS value is
+ # actually null. XXXbz Because "Maybe" takes const refs for
+ # constructor arguments, we can't pass a reference here; have
+ # to pass a pointer.
+ holderArgs = "cx, &%s" % objRef
+ declArgs = None
+ else:
+ holderType = None
+ holderArgs = None
+ declType = CGTemplatedType("RootedSpiderMonkeyInterface", declType)
+ declArgs = "cx"
+ else:
+ holderType = None
+ holderArgs = None
+ declArgs = None
+ return JSToNativeConversionInfo(
+ template,
+ declType=declType,
+ holderType=holderType,
+ dealWithOptional=isOptional,
+ declArgs=declArgs,
+ holderArgs=holderArgs,
+ )
+
+ if type.isJSString():
+ assert not isEnforceRange and not isClamp and not isAllowShared
+ if type.nullable():
+ raise TypeError("Nullable JSString not supported")
+
+ declArgs = "cx"
+ if isMember:
+ raise TypeError("JSString not supported as member")
+ else:
+ declType = "JS::Rooted<JSString*>"
+
+ if isOptional:
+ raise TypeError("JSString not supported as optional")
+ templateBody = fill(
+ """
+ if (!($${declName} = ConvertJSValueToJSString(cx, $${val}))) {
+ $*{exceptionCode}
+ }
+ """,
+ exceptionCode=exceptionCode,
+ )
+
+ if defaultValue is not None:
+ assert not isinstance(defaultValue, IDLNullValue)
+ defaultCode = fill(
+ """
+ static const char data[] = { ${data} };
+ $${declName} = JS_NewStringCopyN(cx, data, ArrayLength(data) - 1);
+ if (!$${declName}) {
+ $*{exceptionCode}
+ }
+ """,
+ data=", ".join(
+ ["'" + char + "'" for char in defaultValue.value] + ["0"]
+ ),
+ exceptionCode=exceptionCode,
+ )
+
+ templateBody = handleDefault(templateBody, defaultCode)
+ return JSToNativeConversionInfo(
+ templateBody, declType=CGGeneric(declType), declArgs=declArgs
+ )
+
+ if type.isDOMString() or type.isUSVString() or type.isUTF8String():
+ assert not isEnforceRange and not isClamp and not isAllowShared
+
+ treatAs = {
+ "Default": "eStringify",
+ "EmptyString": "eEmpty",
+ "Null": "eNull",
+ }
+ if type.nullable():
+ # For nullable strings null becomes a null string.
+ treatNullAs = "Null"
+ # For nullable strings undefined also becomes a null string.
+ undefinedBehavior = "eNull"
+ else:
+ undefinedBehavior = "eStringify"
+ if type.legacyNullToEmptyString:
+ treatNullAs = "EmptyString"
+ else:
+ treatNullAs = "Default"
+ nullBehavior = treatAs[treatNullAs]
+
+ def getConversionCode(varName):
+ normalizeCode = ""
+ if type.isUSVString():
+ normalizeCode = fill(
+ """
+ if (!NormalizeUSVString(${var})) {
+ JS_ReportOutOfMemory(cx);
+ $*{exceptionCode}
+ }
+ """,
+ var=varName,
+ exceptionCode=exceptionCode,
+ )
+
+ conversionCode = fill(
+ """
+ if (!ConvertJSValueToString(cx, $${val}, ${nullBehavior}, ${undefinedBehavior}, ${varName})) {
+ $*{exceptionCode}
+ }
+ $*{normalizeCode}
+ """,
+ nullBehavior=nullBehavior,
+ undefinedBehavior=undefinedBehavior,
+ varName=varName,
+ exceptionCode=exceptionCode,
+ normalizeCode=normalizeCode,
+ )
+
+ if defaultValue is None:
+ return conversionCode
+
+ if isinstance(defaultValue, IDLNullValue):
+ assert type.nullable()
+ defaultCode = "%s.SetIsVoid(true);\n" % varName
+ else:
+ defaultCode = handleDefaultStringValue(
+ defaultValue, "%s.AssignLiteral" % varName
+ )
+ return handleDefault(conversionCode, defaultCode)
+
+ if isMember and isMember != "Union":
+ # Convert directly into the ns[C]String member we have.
+ if type.isUTF8String():
+ declType = "nsCString"
+ else:
+ declType = "nsString"
+ return JSToNativeConversionInfo(
+ getConversionCode("${declName}"),
+ declType=CGGeneric(declType),
+ dealWithOptional=isOptional,
+ )
+
+ if isOptional:
+ if type.isUTF8String():
+ declType = "Optional<nsACString>"
+ holderType = CGGeneric("binding_detail::FakeString<char>")
+ else:
+ declType = "Optional<nsAString>"
+ holderType = CGGeneric("binding_detail::FakeString<char16_t>")
+ conversionCode = "%s" "${declName} = &${holderName};\n" % getConversionCode(
+ "${holderName}"
+ )
+ else:
+ if type.isUTF8String():
+ declType = "binding_detail::FakeString<char>"
+ else:
+ declType = "binding_detail::FakeString<char16_t>"
+ holderType = None
+ conversionCode = getConversionCode("${declName}")
+
+ # No need to deal with optional here; we handled it already
+ return JSToNativeConversionInfo(
+ conversionCode, declType=CGGeneric(declType), holderType=holderType
+ )
+
+ if type.isByteString():
+ assert not isEnforceRange and not isClamp and not isAllowShared
+
+ nullable = toStringBool(type.nullable())
+
+ conversionCode = fill(
+ """
+ if (!ConvertJSValueToByteString(cx, $${val}, ${nullable}, "${sourceDescription}", $${declName})) {
+ $*{exceptionCode}
+ }
+ """,
+ nullable=nullable,
+ sourceDescription=sourceDescription,
+ exceptionCode=exceptionCode,
+ )
+
+ if defaultValue is not None:
+ if isinstance(defaultValue, IDLNullValue):
+ assert type.nullable()
+ defaultCode = "${declName}.SetIsVoid(true);\n"
+ else:
+ defaultCode = handleDefaultStringValue(
+ defaultValue, "${declName}.AssignLiteral"
+ )
+ conversionCode = handleDefault(conversionCode, defaultCode)
+
+ return JSToNativeConversionInfo(
+ conversionCode, declType=CGGeneric("nsCString"), dealWithOptional=isOptional
+ )
+
+ if type.isEnum():
+ assert not isEnforceRange and not isClamp and not isAllowShared
+
+ enumName = type.unroll().inner.identifier.name
+ declType = CGGeneric(enumName)
+ if type.nullable():
+ declType = CGTemplatedType("Nullable", declType)
+ declType = declType.define()
+ enumLoc = "${declName}.SetValue()"
+ else:
+ enumLoc = "${declName}"
+ declType = declType.define()
+
+ if invalidEnumValueFatal:
+ handleInvalidEnumValueCode = "MOZ_ASSERT(index >= 0);\n"
+ else:
+ # invalidEnumValueFatal is false only for attributes. So we won't
+ # have a non-default exceptionCode here unless attribute "arg
+ # conversion" code starts passing in an exceptionCode. At which
+ # point we'll need to figure out what that even means.
+ assert exceptionCode == "return false;\n"
+ handleInvalidEnumValueCode = dedent(
+ """
+ if (index < 0) {
+ return true;
+ }
+ """
+ )
+
+ template = fill(
+ """
+ {
+ int index;
+ if (!FindEnumStringIndex<${invalidEnumValueFatal}>(cx, $${val}, ${values}, "${enumtype}", "${sourceDescription}", &index)) {
+ $*{exceptionCode}
+ }
+ $*{handleInvalidEnumValueCode}
+ ${enumLoc} = static_cast<${enumtype}>(index);
+ }
+ """,
+ enumtype=enumName,
+ values=enumName + "Values::" + ENUM_ENTRY_VARIABLE_NAME,
+ invalidEnumValueFatal=toStringBool(invalidEnumValueFatal),
+ handleInvalidEnumValueCode=handleInvalidEnumValueCode,
+ exceptionCode=exceptionCode,
+ enumLoc=enumLoc,
+ sourceDescription=sourceDescription,
+ )
+
+ setNull = "${declName}.SetNull();\n"
+
+ if type.nullable():
+ template = CGIfElseWrapper(
+ "${val}.isNullOrUndefined()", CGGeneric(setNull), CGGeneric(template)
+ ).define()
+
+ if defaultValue is not None:
+ if isinstance(defaultValue, IDLNullValue):
+ assert type.nullable()
+ template = handleDefault(template, setNull)
+ else:
+ assert defaultValue.type.tag() == IDLType.Tags.domstring
+ template = handleDefault(
+ template,
+ (
+ "%s = %s::%s;\n"
+ % (enumLoc, enumName, getEnumValueName(defaultValue.value))
+ ),
+ )
+ return JSToNativeConversionInfo(
+ template, declType=CGGeneric(declType), dealWithOptional=isOptional
+ )
+
+ if type.isCallback():
+ assert not isEnforceRange and not isClamp and not isAllowShared
+ assert not type.treatNonCallableAsNull() or type.nullable()
+ assert not type.treatNonObjectAsNull() or type.nullable()
+ assert not type.treatNonObjectAsNull() or not type.treatNonCallableAsNull()
+
+ callback = type.unroll().callback
+ name = callback.identifier.name
+ (declType, declArgs, conversion) = getCallbackConversionInfo(
+ type, callback, isMember, isCallbackReturnValue, isOptional
+ )
+
+ if allowTreatNonCallableAsNull and type.treatNonCallableAsNull():
+ haveCallable = "JS::IsCallable(&${val}.toObject())"
+ if not isDefinitelyObject:
+ haveCallable = "${val}.isObject() && " + haveCallable
+ if defaultValue is not None:
+ assert isinstance(defaultValue, IDLNullValue)
+ haveCallable = "(${haveValue}) && " + haveCallable
+ template = (
+ ("if (%s) {\n" % haveCallable) + conversion + "} else {\n"
+ " ${declName} = nullptr;\n"
+ "}\n"
+ )
+ elif allowTreatNonCallableAsNull and type.treatNonObjectAsNull():
+ if not isDefinitelyObject:
+ haveObject = "${val}.isObject()"
+ if defaultValue is not None:
+ assert isinstance(defaultValue, IDLNullValue)
+ haveObject = "(${haveValue}) && " + haveObject
+ template = CGIfElseWrapper(
+ haveObject,
+ CGGeneric(conversion),
+ CGGeneric("${declName} = nullptr;\n"),
+ ).define()
+ else:
+ template = conversion
+ else:
+ template = wrapObjectTemplate(
+ "if (JS::IsCallable(&${val}.toObject())) {\n"
+ + conversion
+ + "} else {\n"
+ + indent(onFailureNotCallable(failureCode).define())
+ + "}\n",
+ type,
+ "${declName} = nullptr;\n",
+ failureCode,
+ )
+ return JSToNativeConversionInfo(
+ template, declType=declType, declArgs=declArgs, dealWithOptional=isOptional
+ )
+
+ if type.isAny():
+ assert not isEnforceRange and not isClamp and not isAllowShared
+
+ declArgs = None
+ if isMember in ("Variadic", "Sequence", "Dictionary", "Record"):
+ # Rooting is handled by the sequence and dictionary tracers.
+ declType = "JS::Value"
+ else:
+ assert not isMember
+ declType = "JS::Rooted<JS::Value>"
+ declArgs = "cx"
+
+ assert not isOptional
+ templateBody = "${declName} = ${val};\n"
+
+ # For JS-implemented APIs, we refuse to allow passing objects that the
+ # API consumer does not subsume. The extra parens around
+ # ($${passedToJSImpl}) suppress unreachable code warnings when
+ # $${passedToJSImpl} is the literal `false`. But Apple is shipping a
+ # buggy clang (clang 3.9) in Xcode 8.3, so there even the parens are not
+ # enough. So we manually disable some warnings in clang.
+ if (
+ not isinstance(descriptorProvider, Descriptor)
+ or descriptorProvider.interface.isJSImplemented()
+ ):
+ templateBody = (
+ fill(
+ """
+ #ifdef __clang__
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wunreachable-code"
+ #pragma clang diagnostic ignored "-Wunreachable-code-return"
+ #endif // __clang__
+ if (($${passedToJSImpl}) && !CallerSubsumes($${val})) {
+ cx.ThrowErrorMessage<MSG_PERMISSION_DENIED_TO_PASS_ARG>("${sourceDescription}");
+ $*{exceptionCode}
+ }
+ #ifdef __clang__
+ #pragma clang diagnostic pop
+ #endif // __clang__
+ """,
+ sourceDescription=sourceDescription,
+ exceptionCode=exceptionCode,
+ )
+ + templateBody
+ )
+
+ # We may not have a default value if we're being converted for
+ # a setter, say.
+ if defaultValue:
+ if isinstance(defaultValue, IDLNullValue):
+ defaultHandling = "${declName} = JS::NullValue();\n"
+ else:
+ assert isinstance(defaultValue, IDLUndefinedValue)
+ defaultHandling = "${declName} = JS::UndefinedValue();\n"
+ templateBody = handleDefault(templateBody, defaultHandling)
+ return JSToNativeConversionInfo(
+ templateBody, declType=CGGeneric(declType), declArgs=declArgs
+ )
+
+ if type.isObject():
+ assert not isEnforceRange and not isClamp and not isAllowShared
+ return handleJSObjectType(
+ type, isMember, failureCode, exceptionCode, sourceDescription
+ )
+
+ if type.isDictionary():
+ # There are no nullable dictionary-typed arguments or dictionary-typed
+ # dictionary members.
+ assert (
+ not type.nullable()
+ or isCallbackReturnValue
+ or (isMember and isMember != "Dictionary")
+ )
+ # All optional dictionary-typed arguments always have default values,
+ # but dictionary-typed dictionary members can be optional.
+ assert not isOptional or isMember == "Dictionary"
+ # In the callback return value case we never have to worry
+ # about a default value; we always have a value.
+ assert not isCallbackReturnValue or defaultValue is None
+
+ typeName = CGDictionary.makeDictionaryName(type.unroll().inner)
+ if (not isMember or isMember == "Union") and not isCallbackReturnValue:
+ # Since we're not a member and not nullable or optional, no one will
+ # see our real type, so we can do the fast version of the dictionary
+ # that doesn't pre-initialize members.
+ typeName = "binding_detail::Fast" + typeName
+
+ declType = CGGeneric(typeName)
+
+ # We do manual default value handling here, because we actually do want
+ # a jsval, and we only handle the default-dictionary case (which we map
+ # into initialization with the JS value `null`) anyway
+ # NOTE: if isNullOrUndefined or isDefinitelyObject are true,
+ # we know we have a value, so we don't have to worry about the
+ # default value.
+ if (
+ not isNullOrUndefined
+ and not isDefinitelyObject
+ and defaultValue is not None
+ ):
+ assert isinstance(defaultValue, IDLDefaultDictionaryValue)
+ # Initializing from JS null does the right thing to give
+ # us a default-initialized dictionary.
+ val = "(${haveValue}) ? ${val} : JS::NullHandleValue"
+ else:
+ val = "${val}"
+
+ dictLoc = "${declName}"
+ if type.nullable():
+ dictLoc += ".SetValue()"
+
+ if type.unroll().inner.needsConversionFromJS:
+ args = "cx, %s, " % val
+ else:
+ # We can end up in this case if a dictionary that does not need
+ # conversion from JS has a dictionary-typed member with a default
+ # value of {}.
+ args = ""
+ conversionCode = fill(
+ """
+ if (!${dictLoc}.Init(${args}"${desc}", $${passedToJSImpl})) {
+ $*{exceptionCode}
+ }
+ """,
+ dictLoc=dictLoc,
+ args=args,
+ desc=firstCap(sourceDescription),
+ exceptionCode=exceptionCode,
+ )
+
+ if failureCode is not None:
+ # This means we're part of an overload or union conversion, and
+ # should simply skip stuff if our value is not convertible to
+ # dictionary, instead of trying and throwing. If we're either
+ # isDefinitelyObject or isNullOrUndefined then we're convertible to
+ # dictionary and don't need to check here.
+ if isDefinitelyObject or isNullOrUndefined:
+ template = conversionCode
+ else:
+ template = fill(
+ """
+ if (!IsConvertibleToDictionary(${val})) {
+ $*{failureCode}
+ }
+ $*{conversionCode}
+ """,
+ val=val,
+ failureCode=failureCode,
+ conversionCode=conversionCode,
+ )
+ else:
+ template = conversionCode
+
+ if type.nullable():
+ declType = CGTemplatedType("Nullable", declType)
+ template = CGIfElseWrapper(
+ "${val}.isNullOrUndefined()",
+ CGGeneric("${declName}.SetNull();\n"),
+ CGGeneric(template),
+ ).define()
+
+ # Dictionary arguments that might contain traceable things need to get
+ # traced
+ if (not isMember or isMember == "Union") and isCallbackReturnValue:
+ # Go ahead and just convert directly into our actual return value
+ declType = CGWrapper(declType, post="&")
+ declArgs = "aRetVal"
+ elif (not isMember or isMember == "Union") and typeNeedsRooting(type):
+ declType = CGTemplatedType("RootedDictionary", declType)
+ declArgs = "cx"
+ else:
+ declArgs = None
+
+ return JSToNativeConversionInfo(
+ template, declType=declType, declArgs=declArgs, dealWithOptional=isOptional
+ )
+
+ if type.isUndefined():
+ assert not isOptional
+ # This one only happens for return values, and its easy: Just
+ # ignore the jsval.
+ return JSToNativeConversionInfo("")
+
+ if not type.isPrimitive():
+ raise TypeError("Need conversion for argument type '%s'" % str(type))
+
+ typeName = builtinNames[type.tag()]
+
+ conversionBehavior = "eDefault"
+ if isEnforceRange:
+ assert type.isInteger()
+ conversionBehavior = "eEnforceRange"
+ elif isClamp:
+ assert type.isInteger()
+ conversionBehavior = "eClamp"
+
+ alwaysNull = False
+ if type.nullable():
+ declType = CGGeneric("Nullable<" + typeName + ">")
+ writeLoc = "${declName}.SetValue()"
+ readLoc = "${declName}.Value()"
+ nullCondition = "${val}.isNullOrUndefined()"
+ if defaultValue is not None and isinstance(defaultValue, IDLNullValue):
+ nullCondition = "!(${haveValue}) || " + nullCondition
+ if isKnownMissing:
+ alwaysNull = True
+ template = dedent(
+ """
+ ${declName}.SetNull();
+ """
+ )
+ if not alwaysNull:
+ template = fill(
+ """
+ if (${nullCondition}) {
+ $${declName}.SetNull();
+ } else if (!ValueToPrimitive<${typeName}, ${conversionBehavior}>(cx, $${val}, "${sourceDescription}", &${writeLoc})) {
+ $*{exceptionCode}
+ }
+ """,
+ nullCondition=nullCondition,
+ typeName=typeName,
+ conversionBehavior=conversionBehavior,
+ sourceDescription=firstCap(sourceDescription),
+ writeLoc=writeLoc,
+ exceptionCode=exceptionCode,
+ )
+ else:
+ assert defaultValue is None or not isinstance(defaultValue, IDLNullValue)
+ writeLoc = "${declName}"
+ readLoc = writeLoc
+ template = fill(
+ """
+ if (!ValueToPrimitive<${typeName}, ${conversionBehavior}>(cx, $${val}, "${sourceDescription}", &${writeLoc})) {
+ $*{exceptionCode}
+ }
+ """,
+ typeName=typeName,
+ conversionBehavior=conversionBehavior,
+ sourceDescription=firstCap(sourceDescription),
+ writeLoc=writeLoc,
+ exceptionCode=exceptionCode,
+ )
+ declType = CGGeneric(typeName)
+
+ if type.isFloat() and not type.isUnrestricted() and not alwaysNull:
+ if lenientFloatCode is not None:
+ nonFiniteCode = lenientFloatCode
+ else:
+ nonFiniteCode = 'cx.ThrowErrorMessage<MSG_NOT_FINITE>("%s");\n' "%s" % (
+ firstCap(sourceDescription),
+ exceptionCode,
+ )
+
+ # We're appending to an if-block brace, so strip trailing whitespace
+ # and add an extra space before the else.
+ template = template.rstrip()
+ template += fill(
+ """
+ else if (!std::isfinite(${readLoc})) {
+ $*{nonFiniteCode}
+ }
+ """,
+ readLoc=readLoc,
+ nonFiniteCode=nonFiniteCode,
+ )
+
+ if (
+ defaultValue is not None
+ and
+ # We already handled IDLNullValue, so just deal with the other ones
+ not isinstance(defaultValue, IDLNullValue)
+ ):
+ tag = defaultValue.type.tag()
+ defaultStr = getHandleDefault(defaultValue)
+ template = handleDefault(template, "%s = %s;\n" % (writeLoc, defaultStr))
+
+ return JSToNativeConversionInfo(
+ template, declType=declType, dealWithOptional=isOptional
+ )
+
+
+def instantiateJSToNativeConversion(info, replacements, checkForValue=False):
+ """
+ Take a JSToNativeConversionInfo as returned by getJSToNativeConversionInfo
+ and a set of replacements as required by the strings in such an object, and
+ generate code to convert into stack C++ types.
+
+ If checkForValue is True, then the conversion will get wrapped in
+ a check for ${haveValue}.
+ """
+ templateBody, declType, holderType, dealWithOptional = (
+ info.template,
+ info.declType,
+ info.holderType,
+ info.dealWithOptional,
+ )
+
+ if dealWithOptional and not checkForValue:
+ raise TypeError("Have to deal with optional things, but don't know how")
+ if checkForValue and declType is None:
+ raise TypeError(
+ "Need to predeclare optional things, so they will be "
+ "outside the check for big enough arg count!"
+ )
+
+ # We can't precompute our holder constructor arguments, since
+ # those might depend on ${declName}, which we change below. Just
+ # compute arguments at the point when we need them as we go.
+ def getArgsCGThing(args):
+ return CGGeneric(string.Template(args).substitute(replacements))
+
+ result = CGList([])
+ # Make a copy of "replacements" since we may be about to start modifying it
+ replacements = dict(replacements)
+ originalDeclName = replacements["declName"]
+ if declType is not None:
+ if dealWithOptional:
+ replacements["declName"] = "%s.Value()" % originalDeclName
+ declType = CGTemplatedType("Optional", declType)
+ declCtorArgs = None
+ elif info.declArgs is not None:
+ declCtorArgs = CGWrapper(getArgsCGThing(info.declArgs), pre="(", post=")")
+ else:
+ declCtorArgs = None
+ result.append(
+ CGList(
+ [
+ declType,
+ CGGeneric(" "),
+ CGGeneric(originalDeclName),
+ declCtorArgs,
+ CGGeneric(";\n"),
+ ]
+ )
+ )
+
+ originalHolderName = replacements["holderName"]
+ if holderType is not None:
+ if dealWithOptional:
+ replacements["holderName"] = "%s.ref()" % originalHolderName
+ holderType = CGTemplatedType("Maybe", holderType)
+ holderCtorArgs = None
+ elif info.holderArgs is not None:
+ holderCtorArgs = CGWrapper(
+ getArgsCGThing(info.holderArgs), pre="(", post=")"
+ )
+ else:
+ holderCtorArgs = None
+ result.append(
+ CGList(
+ [
+ holderType,
+ CGGeneric(" "),
+ CGGeneric(originalHolderName),
+ holderCtorArgs,
+ CGGeneric(";\n"),
+ ]
+ )
+ )
+
+ if "maybeMutableVal" not in replacements:
+ replacements["maybeMutableVal"] = replacements["val"]
+
+ conversion = CGGeneric(string.Template(templateBody).substitute(replacements))
+
+ if checkForValue:
+ if dealWithOptional:
+ declConstruct = CGIndenter(
+ CGGeneric(
+ "%s.Construct(%s);\n"
+ % (
+ originalDeclName,
+ getArgsCGThing(info.declArgs).define() if info.declArgs else "",
+ )
+ )
+ )
+ if holderType is not None:
+ holderConstruct = CGIndenter(
+ CGGeneric(
+ "%s.emplace(%s);\n"
+ % (
+ originalHolderName,
+ getArgsCGThing(info.holderArgs).define()
+ if info.holderArgs
+ else "",
+ )
+ )
+ )
+ else:
+ holderConstruct = None
+ else:
+ declConstruct = None
+ holderConstruct = None
+
+ conversion = CGList(
+ [
+ CGGeneric(
+ string.Template("if (${haveValue}) {\n").substitute(replacements)
+ ),
+ declConstruct,
+ holderConstruct,
+ CGIndenter(conversion),
+ CGGeneric("}\n"),
+ ]
+ )
+
+ result.append(conversion)
+ return result
+
+
+def convertConstIDLValueToJSVal(value):
+ if isinstance(value, IDLNullValue):
+ return "JS::NullValue()"
+ if isinstance(value, IDLUndefinedValue):
+ return "JS::UndefinedValue()"
+ tag = value.type.tag()
+ if tag in [
+ IDLType.Tags.int8,
+ IDLType.Tags.uint8,
+ IDLType.Tags.int16,
+ IDLType.Tags.uint16,
+ IDLType.Tags.int32,
+ ]:
+ return "JS::Int32Value(%s)" % (value.value)
+ if tag == IDLType.Tags.uint32:
+ return "JS::NumberValue(%sU)" % (value.value)
+ if tag in [IDLType.Tags.int64, IDLType.Tags.uint64]:
+ return "JS::CanonicalizedDoubleValue(%s)" % numericValue(tag, value.value)
+ if tag == IDLType.Tags.bool:
+ return "JS::BooleanValue(%s)" % (toStringBool(value.value))
+ if tag in [IDLType.Tags.float, IDLType.Tags.double]:
+ return "JS::CanonicalizedDoubleValue(%s)" % (value.value)
+ raise TypeError("Const value of unhandled type: %s" % value.type)
+
+
+class CGArgumentConverter(CGThing):
+ """
+ A class that takes an IDL argument object and its index in the
+ argument list and generates code to unwrap the argument to the
+ right native type.
+
+ argDescription is a description of the argument for error-reporting
+ purposes. Callers should assume that it might get placed in the middle of a
+ sentence. If it ends up at the beginning of a sentence, its first character
+ will be automatically uppercased.
+ """
+
+ def __init__(
+ self,
+ argument,
+ index,
+ descriptorProvider,
+ argDescription,
+ member,
+ invalidEnumValueFatal=True,
+ lenientFloatCode=None,
+ ):
+ CGThing.__init__(self)
+ self.argument = argument
+ self.argDescription = argDescription
+ assert not argument.defaultValue or argument.optional
+
+ replacer = {"index": index, "argc": "args.length()"}
+ self.replacementVariables = {
+ "declName": "arg%d" % index,
+ "holderName": ("arg%d" % index) + "_holder",
+ "obj": "obj",
+ "passedToJSImpl": toStringBool(
+ isJSImplementedDescriptor(descriptorProvider)
+ ),
+ }
+ # 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. This won't
+ # matter for iterable since generated functions for those interface
+ # don't take arguments.
+ if member.isMethod() and member.isMaplikeOrSetlikeOrIterableMethod():
+ self.replacementVariables["val"] = string.Template(
+ "args.get(${index})"
+ ).substitute(replacer)
+ self.replacementVariables["maybeMutableVal"] = string.Template(
+ "args[${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
+ self.descriptorProvider = descriptorProvider
+ if self.argument.canHaveMissingValue():
+ self.argcAndIndex = replacer
+ else:
+ self.argcAndIndex = None
+ self.invalidEnumValueFatal = invalidEnumValueFatal
+ self.lenientFloatCode = lenientFloatCode
+
+ def define(self):
+ typeConversion = getJSToNativeConversionInfo(
+ self.argument.type,
+ self.descriptorProvider,
+ isOptional=(self.argcAndIndex is not None and not self.argument.variadic),
+ invalidEnumValueFatal=self.invalidEnumValueFatal,
+ defaultValue=self.argument.defaultValue,
+ lenientFloatCode=self.lenientFloatCode,
+ isMember="Variadic" if self.argument.variadic else False,
+ allowTreatNonCallableAsNull=self.argument.allowTreatNonCallableAsNull(),
+ sourceDescription=self.argDescription,
+ )
+
+ if not self.argument.variadic:
+ return instantiateJSToNativeConversion(
+ typeConversion, self.replacementVariables, self.argcAndIndex is not None
+ ).define()
+
+ # Variadic arguments get turned into a sequence.
+ if typeConversion.dealWithOptional:
+ raise TypeError("Shouldn't have optional things in variadics")
+ if typeConversion.holderType is not None:
+ raise TypeError("Shouldn't need holders for variadics")
+
+ replacer = dict(self.argcAndIndex, **self.replacementVariables)
+ replacer["seqType"] = CGTemplatedType(
+ "AutoSequence", typeConversion.declType
+ ).define()
+ if typeNeedsRooting(self.argument.type):
+ rooterDecl = (
+ "SequenceRooter<%s> ${holderName}(cx, &${declName});\n"
+ % typeConversion.declType.define()
+ )
+ else:
+ rooterDecl = ""
+ replacer["elemType"] = typeConversion.declType.define()
+
+ replacer["elementInitializer"] = initializerForType(self.argument.type) or ""
+
+ # NOTE: Keep this in sync with sequence conversions as needed
+ variadicConversion = string.Template(
+ "${seqType} ${declName};\n"
+ + rooterDecl
+ + dedent(
+ """
+ if (${argc} > ${index}) {
+ if (!${declName}.SetCapacity(${argc} - ${index}, mozilla::fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+ for (uint32_t variadicArg = ${index}; variadicArg < ${argc}; ++variadicArg) {
+ // OK to do infallible append here, since we ensured capacity already.
+ ${elemType}& slot = *${declName}.AppendElement(${elementInitializer});
+ """
+ )
+ ).substitute(replacer)
+
+ val = string.Template("args[variadicArg]").substitute(replacer)
+ variadicConversion += indent(
+ string.Template(typeConversion.template).substitute(
+ {
+ "val": val,
+ "maybeMutableVal": val,
+ "declName": "slot",
+ # We only need holderName here to handle isExternal()
+ # interfaces, which use an internal holder for the
+ # conversion even when forceOwningType ends up true.
+ "holderName": "tempHolder",
+ # Use the same ${obj} as for the variadic arg itself
+ "obj": replacer["obj"],
+ "passedToJSImpl": toStringBool(
+ isJSImplementedDescriptor(self.descriptorProvider)
+ ),
+ }
+ ),
+ 4,
+ )
+
+ variadicConversion += " }\n" "}\n"
+ return variadicConversion
+
+
+def getMaybeWrapValueFuncForType(type):
+ if type.isJSString():
+ return "MaybeWrapStringValue"
+ # Callbacks might actually be DOM objects; nothing prevents a page from
+ # doing that.
+ if type.isCallback() or type.isCallbackInterface() or type.isObject():
+ if type.nullable():
+ return "MaybeWrapObjectOrNullValue"
+ return "MaybeWrapObjectValue"
+ # SpiderMonkey interfaces are never DOM objects. Neither are sequences or
+ # dictionaries, since those are always plain JS objects.
+ if type.isSpiderMonkeyInterface() or type.isDictionary() or type.isSequence():
+ if type.nullable():
+ return "MaybeWrapNonDOMObjectOrNullValue"
+ return "MaybeWrapNonDOMObjectValue"
+ if type.isAny():
+ return "MaybeWrapValue"
+
+ # For other types, just go ahead an fall back on MaybeWrapValue for now:
+ # it's always safe to do, and shouldn't be particularly slow for any of
+ # them
+ return "MaybeWrapValue"
+
+
+sequenceWrapLevel = 0
+recordWrapLevel = 0
+
+
+def getWrapTemplateForType(
+ type,
+ descriptorProvider,
+ result,
+ successCode,
+ returnsNewObject,
+ exceptionCode,
+ spiderMonkeyInterfacesAreStructs,
+ isConstructorRetval=False,
+):
+ """
+ Reflect a C++ value stored in "result", of IDL type "type" into JS. The
+ "successCode" is the code to run once we have successfully done the
+ conversion and must guarantee that execution of the conversion template
+ stops once the successCode has executed (e.g. by doing a 'return', or by
+ doing a 'break' if the entire conversion template is inside a block that
+ the 'break' will exit).
+
+ If spiderMonkeyInterfacesAreStructs is true, then if the type is a
+ SpiderMonkey interface, "result" is one of the
+ dom::SpiderMonkeyInterfaceObjectStorage subclasses, not a JSObject*.
+
+ The resulting string should be used with string.Template. It
+ needs the following keys when substituting:
+
+ jsvalHandle: something that can be passed to methods taking a
+ JS::MutableHandle<JS::Value>. This can be a
+ JS::MutableHandle<JS::Value> or a JS::Rooted<JS::Value>*.
+ jsvalRef: something that can have .address() called on it to get a
+ JS::Value* and .set() called on it to set it to a JS::Value.
+ This can be a JS::MutableHandle<JS::Value> or a
+ JS::Rooted<JS::Value>.
+ obj: a JS::Handle<JSObject*>.
+
+ Returns (templateString, infallibility of conversion template)
+ """
+ if successCode is None:
+ successCode = "return true;\n"
+
+ def setUndefined():
+ return _setValue("", setter="setUndefined")
+
+ def setNull():
+ return _setValue("", setter="setNull")
+
+ def setInt32(value):
+ return _setValue(value, setter="setInt32")
+
+ def setString(value):
+ return _setValue(value, wrapAsType=type, setter="setString")
+
+ def setObject(value, wrapAsType=None):
+ return _setValue(value, wrapAsType=wrapAsType, setter="setObject")
+
+ def setObjectOrNull(value, wrapAsType=None):
+ return _setValue(value, wrapAsType=wrapAsType, setter="setObjectOrNull")
+
+ def setUint32(value):
+ return _setValue(value, setter="setNumber")
+
+ def setDouble(value):
+ return _setValue("JS_NumberValue(%s)" % value)
+
+ def setBoolean(value):
+ return _setValue(value, setter="setBoolean")
+
+ def _setValue(value, wrapAsType=None, setter="set"):
+ """
+ Returns the code to set the jsval to value.
+
+ If wrapAsType is not None, then will wrap the resulting value using the
+ function that getMaybeWrapValueFuncForType(wrapAsType) returns.
+ Otherwise, no wrapping will be done.
+ """
+ if wrapAsType is None:
+ tail = successCode
+ else:
+ tail = fill(
+ """
+ if (!${maybeWrap}(cx, $${jsvalHandle})) {
+ $*{exceptionCode}
+ }
+ $*{successCode}
+ """,
+ maybeWrap=getMaybeWrapValueFuncForType(wrapAsType),
+ exceptionCode=exceptionCode,
+ successCode=successCode,
+ )
+ return ("${jsvalRef}.%s(%s);\n" % (setter, value)) + tail
+
+ def wrapAndSetPtr(wrapCall, failureCode=None):
+ """
+ Returns the code to set the jsval by calling "wrapCall". "failureCode"
+ is the code to run if calling "wrapCall" fails
+ """
+ if failureCode is None:
+ failureCode = exceptionCode
+ return fill(
+ """
+ if (!${wrapCall}) {
+ $*{failureCode}
+ }
+ $*{successCode}
+ """,
+ wrapCall=wrapCall,
+ failureCode=failureCode,
+ successCode=successCode,
+ )
+
+ if type is None or type.isUndefined():
+ return (setUndefined(), True)
+
+ if (type.isSequence() or type.isRecord()) and type.nullable():
+ # These are both wrapped in Nullable<>
+ recTemplate, recInfall = getWrapTemplateForType(
+ type.inner,
+ descriptorProvider,
+ "%s.Value()" % result,
+ successCode,
+ returnsNewObject,
+ exceptionCode,
+ spiderMonkeyInterfacesAreStructs,
+ )
+ code = fill(
+ """
+
+ if (${result}.IsNull()) {
+ $*{setNull}
+ }
+ $*{recTemplate}
+ """,
+ result=result,
+ setNull=setNull(),
+ recTemplate=recTemplate,
+ )
+ return code, recInfall
+
+ if type.isSequence():
+ # Now do non-nullable sequences. Our success code is just to break to
+ # where we set the element in the array. Note that we bump the
+ # sequenceWrapLevel around this call so that nested sequence conversions
+ # will use different iteration variables.
+ global sequenceWrapLevel
+ index = "sequenceIdx%d" % sequenceWrapLevel
+ sequenceWrapLevel += 1
+ innerTemplate = wrapForType(
+ type.inner,
+ descriptorProvider,
+ {
+ "result": "%s[%s]" % (result, index),
+ "successCode": "break;\n",
+ "jsvalRef": "tmp",
+ "jsvalHandle": "&tmp",
+ "returnsNewObject": returnsNewObject,
+ "exceptionCode": exceptionCode,
+ "obj": "returnArray",
+ "spiderMonkeyInterfacesAreStructs": spiderMonkeyInterfacesAreStructs,
+ },
+ )
+ sequenceWrapLevel -= 1
+ code = fill(
+ """
+
+ uint32_t length = ${result}.Length();
+ JS::Rooted<JSObject*> returnArray(cx, JS::NewArrayObject(cx, length));
+ if (!returnArray) {
+ $*{exceptionCode}
+ }
+ // Scope for 'tmp'
+ {
+ JS::Rooted<JS::Value> tmp(cx);
+ for (uint32_t ${index} = 0; ${index} < length; ++${index}) {
+ // Control block to let us common up the JS_DefineElement calls when there
+ // are different ways to succeed at wrapping the object.
+ do {
+ $*{innerTemplate}
+ } while (false);
+ if (!JS_DefineElement(cx, returnArray, ${index}, tmp,
+ JSPROP_ENUMERATE)) {
+ $*{exceptionCode}
+ }
+ }
+ }
+ $*{set}
+ """,
+ result=result,
+ exceptionCode=exceptionCode,
+ index=index,
+ innerTemplate=innerTemplate,
+ set=setObject("*returnArray"),
+ )
+
+ return (code, False)
+
+ if type.isRecord():
+ # Now do non-nullable record. Our success code is just to break to
+ # where we define the property on the object. Note that we bump the
+ # recordWrapLevel around this call so that nested record conversions
+ # will use different temp value names.
+ global recordWrapLevel
+ valueName = "recordValue%d" % recordWrapLevel
+ recordWrapLevel += 1
+ innerTemplate = wrapForType(
+ type.inner,
+ descriptorProvider,
+ {
+ "result": valueName,
+ "successCode": "break;\n",
+ "jsvalRef": "tmp",
+ "jsvalHandle": "&tmp",
+ "returnsNewObject": returnsNewObject,
+ "exceptionCode": exceptionCode,
+ "obj": "returnObj",
+ "spiderMonkeyInterfacesAreStructs": spiderMonkeyInterfacesAreStructs,
+ },
+ )
+ recordWrapLevel -= 1
+ if type.keyType.isByteString():
+ # There is no length-taking JS_DefineProperty. So to keep
+ # things sane with embedded nulls, we want to byte-inflate
+ # to an nsAString. The only byte-inflation function we
+ # have around is AppendASCIItoUTF16, which luckily doesn't
+ # assert anything about the input being ASCII.
+ expandedKeyDecl = "NS_ConvertASCIItoUTF16 expandedKey(entry.mKey);\n"
+ keyName = "expandedKey"
+ elif type.keyType.isUTF8String():
+ # We do the same as above for utf8 strings. We could do better if
+ # we had a DefineProperty API that takes utf-8 property names.
+ expandedKeyDecl = "NS_ConvertUTF8toUTF16 expandedKey(entry.mKey);\n"
+ keyName = "expandedKey"
+ else:
+ expandedKeyDecl = ""
+ keyName = "entry.mKey"
+
+ code = fill(
+ """
+
+ JS::Rooted<JSObject*> returnObj(cx, JS_NewPlainObject(cx));
+ if (!returnObj) {
+ $*{exceptionCode}
+ }
+ // Scope for 'tmp'
+ {
+ JS::Rooted<JS::Value> tmp(cx);
+ for (auto& entry : ${result}.Entries()) {
+ auto& ${valueName} = entry.mValue;
+ // Control block to let us common up the JS_DefineUCProperty calls when there
+ // are different ways to succeed at wrapping the value.
+ do {
+ $*{innerTemplate}
+ } while (false);
+ $*{expandedKeyDecl}
+ if (!JS_DefineUCProperty(cx, returnObj,
+ ${keyName}.BeginReading(),
+ ${keyName}.Length(), tmp,
+ JSPROP_ENUMERATE)) {
+ $*{exceptionCode}
+ }
+ }
+ }
+ $*{set}
+ """,
+ result=result,
+ exceptionCode=exceptionCode,
+ valueName=valueName,
+ innerTemplate=innerTemplate,
+ expandedKeyDecl=expandedKeyDecl,
+ keyName=keyName,
+ set=setObject("*returnObj"),
+ )
+
+ return (code, False)
+
+ if type.isPromise():
+ assert not type.nullable()
+ # The use of ToJSValue here is a bit annoying because the Promise
+ # version is not inlined. But we can't put an inline version in either
+ # ToJSValue.h or BindingUtils.h, because Promise.h includes ToJSValue.h
+ # and that includes BindingUtils.h, so we'd get an include loop if
+ # either of those headers included Promise.h. And trying to write the
+ # conversion by hand here is pretty annoying because we have to handle
+ # the various RefPtr, rawptr, NonNull, etc cases, which ToJSValue will
+ # handle for us. So just eat the cost of the function call.
+ return (wrapAndSetPtr("ToJSValue(cx, %s, ${jsvalHandle})" % result), False)
+
+ if type.isGeckoInterface() and not type.isCallbackInterface():
+ descriptor = descriptorProvider.getDescriptor(
+ type.unroll().inner.identifier.name
+ )
+ if type.nullable():
+ if descriptor.interface.identifier.name == "WindowProxy":
+ template, infal = getWrapTemplateForType(
+ type.inner,
+ descriptorProvider,
+ "%s.Value()" % result,
+ successCode,
+ returnsNewObject,
+ exceptionCode,
+ spiderMonkeyInterfacesAreStructs,
+ )
+ return (
+ "if (%s.IsNull()) {\n" % result
+ + indent(setNull())
+ + "}\n"
+ + template,
+ infal,
+ )
+
+ wrappingCode = "if (!%s) {\n" % (result) + indent(setNull()) + "}\n"
+ else:
+ wrappingCode = ""
+
+ if not descriptor.interface.isExternal():
+ if descriptor.wrapperCache:
+ wrapMethod = "GetOrCreateDOMReflector"
+ wrapArgs = "cx, %s, ${jsvalHandle}" % result
+ else:
+ wrapMethod = "WrapNewBindingNonWrapperCachedObject"
+ wrapArgs = "cx, ${obj}, %s, ${jsvalHandle}" % result
+ if isConstructorRetval:
+ wrapArgs += ", desiredProto"
+ wrap = "%s(%s)" % (wrapMethod, wrapArgs)
+ # Can only fail to wrap as a new-binding object if they already
+ # threw an exception.
+ failed = "MOZ_ASSERT(JS_IsExceptionPending(cx));\n" + exceptionCode
+ else:
+ if descriptor.notflattened:
+ getIID = "&NS_GET_IID(%s), " % descriptor.nativeType
+ else:
+ getIID = ""
+ wrap = "WrapObject(cx, %s, %s${jsvalHandle})" % (result, getIID)
+ failed = None
+
+ wrappingCode += wrapAndSetPtr(wrap, failed)
+ return (wrappingCode, False)
+
+ if type.isJSString():
+ return (setString(result), False)
+
+ if type.isDOMString() or type.isUSVString():
+ if type.nullable():
+ return (
+ wrapAndSetPtr("xpc::StringToJsval(cx, %s, ${jsvalHandle})" % result),
+ False,
+ )
+ else:
+ return (
+ wrapAndSetPtr(
+ "xpc::NonVoidStringToJsval(cx, %s, ${jsvalHandle})" % result
+ ),
+ False,
+ )
+
+ if type.isByteString():
+ if type.nullable():
+ return (
+ wrapAndSetPtr("ByteStringToJsval(cx, %s, ${jsvalHandle})" % result),
+ False,
+ )
+ else:
+ return (
+ wrapAndSetPtr(
+ "NonVoidByteStringToJsval(cx, %s, ${jsvalHandle})" % result
+ ),
+ False,
+ )
+
+ if type.isUTF8String():
+ if type.nullable():
+ return (
+ wrapAndSetPtr("UTF8StringToJsval(cx, %s, ${jsvalHandle})" % result),
+ False,
+ )
+ else:
+ return (
+ wrapAndSetPtr(
+ "NonVoidUTF8StringToJsval(cx, %s, ${jsvalHandle})" % result
+ ),
+ False,
+ )
+
+ if type.isEnum():
+ if type.nullable():
+ resultLoc = "%s.Value()" % result
+ else:
+ resultLoc = result
+ conversion = fill(
+ """
+ if (!ToJSValue(cx, ${result}, $${jsvalHandle})) {
+ $*{exceptionCode}
+ }
+ $*{successCode}
+ """,
+ result=resultLoc,
+ exceptionCode=exceptionCode,
+ successCode=successCode,
+ )
+
+ if type.nullable():
+ conversion = CGIfElseWrapper(
+ "%s.IsNull()" % result, CGGeneric(setNull()), CGGeneric(conversion)
+ ).define()
+ return conversion, False
+
+ if type.isCallback() or type.isCallbackInterface():
+ # Callbacks can store null if we nuked the compartments their
+ # objects lived in.
+ wrapCode = setObjectOrNull(
+ "GetCallbackFromCallbackObject(cx, %(result)s)", wrapAsType=type
+ )
+ if type.nullable():
+ wrapCode = (
+ "if (%(result)s) {\n"
+ + indent(wrapCode)
+ + "} else {\n"
+ + indent(setNull())
+ + "}\n"
+ )
+ wrapCode = wrapCode % {"result": result}
+ return wrapCode, False
+
+ if type.isAny():
+ # See comments in GetOrCreateDOMReflector explaining why we need
+ # to wrap here.
+ # NB: _setValue(..., type-that-is-any) calls JS_WrapValue(), so is fallible
+ head = "JS::ExposeValueToActiveJS(%s);\n" % result
+ return (head + _setValue(result, wrapAsType=type), False)
+
+ if type.isObject() or (
+ type.isSpiderMonkeyInterface() and not spiderMonkeyInterfacesAreStructs
+ ):
+ # See comments in GetOrCreateDOMReflector explaining why we need
+ # to wrap here.
+ if type.nullable():
+ toValue = "%s"
+ setter = setObjectOrNull
+ head = """if (%s) {
+ JS::ExposeObjectToActiveJS(%s);
+ }
+ """ % (
+ result,
+ result,
+ )
+ else:
+ toValue = "*%s"
+ setter = setObject
+ head = "JS::ExposeObjectToActiveJS(%s);\n" % result
+ # NB: setObject{,OrNull}(..., some-object-type) calls JS_WrapValue(), so is fallible
+ return (head + setter(toValue % result, wrapAsType=type), False)
+
+ if type.isObservableArray():
+ # This first argument isn't used at all for now, the attribute getter
+ # for ObservableArray type are generated in getObservableArrayGetterBody
+ # instead.
+ return "", False
+
+ if not (
+ type.isUnion()
+ or type.isPrimitive()
+ or type.isDictionary()
+ or (type.isSpiderMonkeyInterface() and spiderMonkeyInterfacesAreStructs)
+ ):
+ raise TypeError("Need to learn to wrap %s" % type)
+
+ if type.nullable():
+ recTemplate, recInfal = getWrapTemplateForType(
+ type.inner,
+ descriptorProvider,
+ "%s.Value()" % result,
+ successCode,
+ returnsNewObject,
+ exceptionCode,
+ spiderMonkeyInterfacesAreStructs,
+ )
+ return (
+ "if (%s.IsNull()) {\n" % result + indent(setNull()) + "}\n" + recTemplate,
+ recInfal,
+ )
+
+ if type.isSpiderMonkeyInterface():
+ assert spiderMonkeyInterfacesAreStructs
+ # See comments in GetOrCreateDOMReflector explaining why we need
+ # to wrap here.
+ # NB: setObject(..., some-object-type) calls JS_WrapValue(), so is fallible
+ return (setObject("*%s.Obj()" % result, wrapAsType=type), False)
+
+ if type.isUnion():
+ return (wrapAndSetPtr("%s.ToJSVal(cx, ${obj}, ${jsvalHandle})" % result), False)
+
+ if type.isDictionary():
+ return (
+ wrapAndSetPtr("%s.ToObjectInternal(cx, ${jsvalHandle})" % result),
+ False,
+ )
+
+ tag = type.tag()
+
+ if tag in [
+ IDLType.Tags.int8,
+ IDLType.Tags.uint8,
+ IDLType.Tags.int16,
+ IDLType.Tags.uint16,
+ IDLType.Tags.int32,
+ ]:
+ return (setInt32("int32_t(%s)" % result), True)
+
+ elif tag in [
+ IDLType.Tags.int64,
+ IDLType.Tags.uint64,
+ IDLType.Tags.unrestricted_float,
+ IDLType.Tags.float,
+ IDLType.Tags.unrestricted_double,
+ IDLType.Tags.double,
+ ]:
+ # XXXbz will cast to double do the "even significand" thing that webidl
+ # calls for for 64-bit ints? Do we care?
+ return (setDouble("double(%s)" % result), True)
+
+ elif tag == IDLType.Tags.uint32:
+ return (setUint32(result), True)
+
+ elif tag == IDLType.Tags.bool:
+ return (setBoolean(result), True)
+
+ else:
+ raise TypeError("Need to learn to wrap primitive: %s" % type)
+
+
+def wrapForType(type, descriptorProvider, templateValues):
+ """
+ Reflect a C++ value of IDL type "type" into JS. TemplateValues is a dict
+ that should contain:
+
+ * 'jsvalRef': something that can have .address() called on it to get a
+ JS::Value* and .set() called on it to set it to a JS::Value.
+ This can be a JS::MutableHandle<JS::Value> or a
+ JS::Rooted<JS::Value>.
+ * 'jsvalHandle': something that can be passed to methods taking a
+ JS::MutableHandle<JS::Value>. This can be a
+ JS::MutableHandle<JS::Value> or a JS::Rooted<JS::Value>*.
+ * 'obj' (optional): the name of the variable that contains the JSObject to
+ use as a scope when wrapping, if not supplied 'obj'
+ will be used as the name
+ * 'result' (optional): the name of the variable in which the C++ value is
+ stored, if not supplied 'result' will be used as
+ the name
+ * 'successCode' (optional): the code to run once we have successfully
+ done the conversion, if not supplied 'return
+ true;' will be used as the code. The
+ successCode must ensure that once it runs no
+ more of the conversion template will be
+ executed (e.g. by doing a 'return' or 'break'
+ as appropriate).
+ * 'returnsNewObject' (optional): If true, we're wrapping for the return
+ value of a [NewObject] method. Assumed
+ false if not set.
+ * 'exceptionCode' (optional): Code to run when a JS exception is thrown.
+ The default is "return false;". The code
+ passed here must return.
+ * 'isConstructorRetval' (optional): If true, we're wrapping a constructor
+ return value.
+ """
+ wrap = getWrapTemplateForType(
+ type,
+ descriptorProvider,
+ templateValues.get("result", "result"),
+ templateValues.get("successCode", None),
+ templateValues.get("returnsNewObject", False),
+ templateValues.get("exceptionCode", "return false;\n"),
+ templateValues.get("spiderMonkeyInterfacesAreStructs", False),
+ isConstructorRetval=templateValues.get("isConstructorRetval", False),
+ )[0]
+
+ defaultValues = {"obj": "obj"}
+ return string.Template(wrap).substitute(defaultValues, **templateValues)
+
+
+def infallibleForMember(member, type, descriptorProvider):
+ """
+ Determine the fallibility of changing a C++ value of IDL type "type" into
+ JS for the given attribute. Apart from returnsNewObject, all the defaults
+ are used, since the fallbility does not change based on the boolean values,
+ and the template will be discarded.
+
+ CURRENT ASSUMPTIONS:
+ We assume that successCode for wrapping up return values cannot contain
+ failure conditions.
+ """
+ return getWrapTemplateForType(
+ type,
+ descriptorProvider,
+ "result",
+ None,
+ memberReturnsNewObject(member),
+ "return false;\n",
+ False,
+ )[1]
+
+
+def leafTypeNeedsCx(type, retVal):
+ return (
+ type.isAny()
+ or type.isObject()
+ or type.isJSString()
+ or (retVal and type.isSpiderMonkeyInterface())
+ )
+
+
+def leafTypeNeedsScopeObject(type, retVal):
+ return retVal and type.isSpiderMonkeyInterface()
+
+
+def leafTypeNeedsRooting(type):
+ return leafTypeNeedsCx(type, False) or type.isSpiderMonkeyInterface()
+
+
+def typeNeedsRooting(type):
+ return typeMatchesLambda(type, lambda t: leafTypeNeedsRooting(t))
+
+
+def typeNeedsCx(type, retVal=False):
+ return typeMatchesLambda(type, lambda t: leafTypeNeedsCx(t, retVal))
+
+
+def typeNeedsScopeObject(type, retVal=False):
+ return typeMatchesLambda(type, lambda t: leafTypeNeedsScopeObject(t, retVal))
+
+
+def typeMatchesLambda(type, func):
+ if type is None:
+ return False
+ if type.nullable():
+ return typeMatchesLambda(type.inner, func)
+ if type.isSequence() or type.isRecord():
+ return typeMatchesLambda(type.inner, func)
+ if type.isUnion():
+ return any(typeMatchesLambda(t, func) for t in type.unroll().flatMemberTypes)
+ if type.isDictionary():
+ return dictionaryMatchesLambda(type.inner, func)
+ return func(type)
+
+
+def dictionaryMatchesLambda(dictionary, func):
+ return any(typeMatchesLambda(m.type, func) for m in dictionary.members) or (
+ dictionary.parent and dictionaryMatchesLambda(dictionary.parent, func)
+ )
+
+
+# Whenever this is modified, please update CGNativeMember.getRetvalInfo as
+# needed to keep the types compatible.
+def getRetvalDeclarationForType(returnType, descriptorProvider, isMember=False):
+ """
+ Returns a tuple containing five things:
+
+ 1) A CGThing for the type of the return value, or None if there is no need
+ for a return value.
+
+ 2) A value indicating the kind of ourparam to pass the value as. Valid
+ options are None to not pass as an out param at all, "ref" (to pass a
+ reference as an out param), and "ptr" (to pass a pointer as an out
+ param).
+
+ 3) A CGThing for a tracer for the return value, or None if no tracing is
+ needed.
+
+ 4) An argument string to pass to the retval declaration
+ constructor or None if there are no arguments.
+
+ 5) The name of a function that needs to be called with the return value
+ before using it, or None if no function needs to be called.
+ """
+ if returnType is None or returnType.isUndefined():
+ # Nothing to declare
+ return None, None, None, None, None
+ if returnType.isPrimitive() and returnType.tag() in builtinNames:
+ result = CGGeneric(builtinNames[returnType.tag()])
+ if returnType.nullable():
+ result = CGTemplatedType("Nullable", result)
+ return result, None, None, None, None
+ if returnType.isJSString():
+ if isMember:
+ raise TypeError("JSString not supported as return type member")
+ return CGGeneric("JS::Rooted<JSString*>"), "ptr", None, "cx", None
+ if returnType.isDOMString() or returnType.isUSVString():
+ if isMember:
+ return CGGeneric("nsString"), "ref", None, None, None
+ return CGGeneric("DOMString"), "ref", None, None, None
+ if returnType.isByteString() or returnType.isUTF8String():
+ if isMember:
+ return CGGeneric("nsCString"), "ref", None, None, None
+ return CGGeneric("nsAutoCString"), "ref", None, None, None
+ if returnType.isEnum():
+ result = CGGeneric(returnType.unroll().inner.identifier.name)
+ if returnType.nullable():
+ result = CGTemplatedType("Nullable", result)
+ return result, None, None, None, None
+ if returnType.isGeckoInterface() or returnType.isPromise():
+ if returnType.isGeckoInterface():
+ typeName = returnType.unroll().inner.identifier.name
+ if typeName == "WindowProxy":
+ result = CGGeneric("WindowProxyHolder")
+ if returnType.nullable():
+ result = CGTemplatedType("Nullable", result)
+ return result, None, None, None, None
+
+ typeName = descriptorProvider.getDescriptor(typeName).nativeType
+ else:
+ typeName = "Promise"
+ if isMember:
+ conversion = None
+ result = CGGeneric("StrongPtrForMember<%s>" % typeName)
+ else:
+ conversion = CGGeneric("StrongOrRawPtr<%s>" % typeName)
+ result = CGGeneric("auto")
+ return result, None, None, None, conversion
+ if returnType.isCallback():
+ name = returnType.unroll().callback.identifier.name
+ return CGGeneric("RefPtr<%s>" % name), None, None, None, None
+ if returnType.isAny():
+ if isMember:
+ return CGGeneric("JS::Value"), None, None, None, None
+ return CGGeneric("JS::Rooted<JS::Value>"), "ptr", None, "cx", None
+ if returnType.isObject() or returnType.isSpiderMonkeyInterface():
+ if isMember:
+ return CGGeneric("JSObject*"), None, None, None, None
+ return CGGeneric("JS::Rooted<JSObject*>"), "ptr", None, "cx", None
+ if returnType.isSequence():
+ nullable = returnType.nullable()
+ if nullable:
+ returnType = returnType.inner
+ result, _, _, _, _ = getRetvalDeclarationForType(
+ returnType.inner, descriptorProvider, isMember="Sequence"
+ )
+ # While we have our inner type, set up our rooter, if needed
+ if not isMember and typeNeedsRooting(returnType):
+ rooter = CGGeneric(
+ "SequenceRooter<%s > resultRooter(cx, &result);\n" % result.define()
+ )
+ else:
+ rooter = None
+ result = CGTemplatedType("nsTArray", result)
+ if nullable:
+ result = CGTemplatedType("Nullable", result)
+ return result, "ref", rooter, None, None
+ if returnType.isRecord():
+ nullable = returnType.nullable()
+ if nullable:
+ returnType = returnType.inner
+ result, _, _, _, _ = getRetvalDeclarationForType(
+ returnType.inner, descriptorProvider, isMember="Record"
+ )
+ # While we have our inner type, set up our rooter, if needed
+ if not isMember and typeNeedsRooting(returnType):
+ rooter = CGGeneric(
+ "RecordRooter<%s> resultRooter(cx, &result);\n"
+ % ("nsString, " + result.define())
+ )
+ else:
+ rooter = None
+ result = CGTemplatedType("Record", [recordKeyDeclType(returnType), result])
+ if nullable:
+ result = CGTemplatedType("Nullable", result)
+ return result, "ref", rooter, None, None
+ if returnType.isDictionary():
+ nullable = returnType.nullable()
+ dictName = CGDictionary.makeDictionaryName(returnType.unroll().inner)
+ result = CGGeneric(dictName)
+ if not isMember and typeNeedsRooting(returnType):
+ if nullable:
+ result = CGTemplatedType("NullableRootedDictionary", result)
+ else:
+ result = CGTemplatedType("RootedDictionary", result)
+ resultArgs = "cx"
+ else:
+ if nullable:
+ result = CGTemplatedType("Nullable", result)
+ resultArgs = None
+ return result, "ref", None, resultArgs, None
+ if returnType.isUnion():
+ result = CGGeneric(CGUnionStruct.unionTypeName(returnType.unroll(), True))
+ if not isMember and typeNeedsRooting(returnType):
+ if returnType.nullable():
+ result = CGTemplatedType("NullableRootedUnion", result)
+ else:
+ result = CGTemplatedType("RootedUnion", result)
+ resultArgs = "cx"
+ else:
+ if returnType.nullable():
+ result = CGTemplatedType("Nullable", result)
+ resultArgs = None
+ return result, "ref", None, resultArgs, None
+ raise TypeError("Don't know how to declare return value for %s" % returnType)
+
+
+def needCx(returnType, arguments, extendedAttributes, considerTypes, static=False):
+ return (
+ not static
+ and considerTypes
+ and (
+ typeNeedsCx(returnType, True) or any(typeNeedsCx(a.type) for a in arguments)
+ )
+ or "implicitJSContext" in extendedAttributes
+ )
+
+
+def needScopeObject(
+ returnType, arguments, extendedAttributes, isWrapperCached, considerTypes, isMember
+):
+ """
+ isMember should be true if we're dealing with an attribute
+ annotated as [StoreInSlot].
+ """
+ return (
+ considerTypes
+ and not isWrapperCached
+ and (
+ (not isMember and typeNeedsScopeObject(returnType, True))
+ or any(typeNeedsScopeObject(a.type) for a in arguments)
+ )
+ )
+
+
+def callerTypeGetterForDescriptor(descriptor):
+ if descriptor.interface.isExposedInAnyWorker():
+ systemCallerGetter = "nsContentUtils::ThreadsafeIsSystemCaller"
+ else:
+ systemCallerGetter = "nsContentUtils::IsSystemCaller"
+ return "%s(cx) ? CallerType::System : CallerType::NonSystem" % systemCallerGetter
+
+
+class CGCallGenerator(CGThing):
+ """
+ A class to generate an actual call to a C++ object. Assumes that the C++
+ object is stored in a variable whose name is given by the |object| argument.
+
+ needsCallerType is a boolean indicating whether the call should receive
+ a PrincipalType for the caller.
+
+ needsErrorResult is a boolean indicating whether the call should be
+ fallible and thus needs ErrorResult parameter.
+
+ resultVar: If the returnType is not void, then the result of the call is
+ stored in a C++ variable named by resultVar. The caller is responsible for
+ declaring the result variable. If the caller doesn't care about the result
+ value, resultVar can be omitted.
+
+ context: The context string to pass to MaybeSetPendingException.
+ """
+
+ def __init__(
+ self,
+ needsErrorResult,
+ needsCallerType,
+ isChromeOnly,
+ arguments,
+ argsPre,
+ returnType,
+ extendedAttributes,
+ descriptor,
+ nativeMethodName,
+ static,
+ object="self",
+ argsPost=[],
+ resultVar=None,
+ context="nullptr",
+ ):
+ CGThing.__init__(self)
+
+ (
+ result,
+ resultOutParam,
+ resultRooter,
+ resultArgs,
+ resultConversion,
+ ) = getRetvalDeclarationForType(returnType, descriptor)
+
+ args = CGList([CGGeneric(arg) for arg in argsPre], ", ")
+ for a, name in arguments:
+ arg = CGGeneric(name)
+
+ # Now constify the things that need it
+ def needsConst(a):
+ if a.type.isDictionary():
+ return True
+ if a.type.isSequence():
+ return True
+ if a.type.isRecord():
+ return True
+ # isObject() types are always a JS::Rooted, whether
+ # nullable or not, and it turns out a const JS::Rooted
+ # is not very helpful at all (in particular, it won't
+ # even convert to a JS::Handle).
+ # XXX bz Well, why not???
+ if a.type.nullable() and not a.type.isObject():
+ return True
+ if a.type.isString():
+ return True
+ if a.canHaveMissingValue():
+ # This will need an Optional or it's a variadic;
+ # in both cases it should be const.
+ return True
+ if a.type.isUnion():
+ return True
+ if a.type.isSpiderMonkeyInterface():
+ return True
+ return False
+
+ if needsConst(a):
+ arg = CGWrapper(arg, pre="Constify(", post=")")
+ # And convert NonNull<T> to T&
+ if (
+ (a.type.isGeckoInterface() or a.type.isCallback() or a.type.isPromise())
+ and not a.type.nullable()
+ ) or a.type.isDOMString():
+ arg = CGWrapper(arg, pre="NonNullHelper(", post=")")
+
+ # If it's a refcounted object, let the static analysis know it's
+ # alive for the duration of the call.
+ if a.type.isGeckoInterface() or a.type.isCallback():
+ arg = CGWrapper(arg, pre="MOZ_KnownLive(", post=")")
+
+ args.append(arg)
+
+ needResultDecl = False
+
+ # Build up our actual call
+ self.cgRoot = CGList([])
+
+ # Return values that go in outparams go here
+ if resultOutParam is not None:
+ if resultVar is None:
+ needResultDecl = True
+ resultVar = "result"
+ if resultOutParam == "ref":
+ args.append(CGGeneric(resultVar))
+ else:
+ assert resultOutParam == "ptr"
+ args.append(CGGeneric("&" + resultVar))
+
+ needsSubjectPrincipal = "needsSubjectPrincipal" in extendedAttributes
+ if needsSubjectPrincipal:
+ needsNonSystemPrincipal = (
+ "needsNonSystemSubjectPrincipal" in extendedAttributes
+ )
+ if needsNonSystemPrincipal:
+ checkPrincipal = dedent(
+ """
+ if (principal->IsSystemPrincipal()) {
+ principal = nullptr;
+ }
+ """
+ )
+ else:
+ checkPrincipal = ""
+
+ getPrincipal = fill(
+ """
+ JS::Realm* realm = js::GetContextRealm(cx);
+ MOZ_ASSERT(realm);
+ JSPrincipals* principals = JS::GetRealmPrincipals(realm);
+ nsIPrincipal* principal = nsJSPrincipals::get(principals);
+ ${checkPrincipal}
+ """,
+ checkPrincipal=checkPrincipal,
+ )
+
+ if descriptor.interface.isExposedInAnyWorker():
+ self.cgRoot.append(
+ CGGeneric(
+ fill(
+ """
+ Maybe<nsIPrincipal*> subjectPrincipal;
+ if (NS_IsMainThread()) {
+ $*{getPrincipal}
+ subjectPrincipal.emplace(principal);
+ }
+ """,
+ getPrincipal=getPrincipal,
+ )
+ )
+ )
+ subjectPrincipalArg = "subjectPrincipal"
+ else:
+ if needsNonSystemPrincipal:
+ principalType = "nsIPrincipal*"
+ subjectPrincipalArg = "subjectPrincipal"
+ else:
+ principalType = "NonNull<nsIPrincipal>"
+ subjectPrincipalArg = "NonNullHelper(subjectPrincipal)"
+
+ self.cgRoot.append(
+ CGGeneric(
+ fill(
+ """
+ ${principalType} subjectPrincipal;
+ {
+ $*{getPrincipal}
+ subjectPrincipal = principal;
+ }
+ """,
+ principalType=principalType,
+ getPrincipal=getPrincipal,
+ )
+ )
+ )
+
+ args.append(CGGeneric("MOZ_KnownLive(%s)" % subjectPrincipalArg))
+
+ if needsCallerType:
+ if isChromeOnly:
+ args.append(CGGeneric("SystemCallerGuarantee()"))
+ else:
+ args.append(CGGeneric(callerTypeGetterForDescriptor(descriptor)))
+
+ canOOM = "canOOM" in extendedAttributes
+ if needsErrorResult:
+ args.append(CGGeneric("rv"))
+ elif canOOM:
+ args.append(CGGeneric("OOMReporter::From(rv)"))
+ args.extend(CGGeneric(arg) for arg in argsPost)
+
+ call = CGGeneric(nativeMethodName)
+ if not static:
+ call = CGWrapper(call, pre="%s->" % object)
+ call = CGList([call, CGWrapper(args, pre="(", post=")")])
+ if returnType is None or returnType.isUndefined() or resultOutParam is not None:
+ assert resultConversion is None
+ call = CGList(
+ [
+ CGWrapper(
+ call,
+ pre=(
+ "// NOTE: This assert does NOT call the function.\n"
+ "static_assert(std::is_void_v<decltype("
+ ),
+ post=')>, "Should be returning void here");',
+ ),
+ call,
+ ],
+ "\n",
+ )
+ elif resultConversion is not None:
+ call = CGList([resultConversion, CGWrapper(call, pre="(", post=")")])
+ if resultVar is None and result is not None:
+ needResultDecl = True
+ resultVar = "result"
+
+ if needResultDecl:
+ if resultArgs is not None:
+ resultArgsStr = "(%s)" % resultArgs
+ else:
+ resultArgsStr = ""
+ result = CGWrapper(result, post=(" %s%s" % (resultVar, resultArgsStr)))
+ if resultOutParam is None and resultArgs is None:
+ call = CGList([result, CGWrapper(call, pre="(", post=")")])
+ else:
+ self.cgRoot.append(CGWrapper(result, post=";\n"))
+ if resultOutParam is None:
+ call = CGWrapper(call, pre=resultVar + " = ")
+ if resultRooter is not None:
+ self.cgRoot.append(resultRooter)
+ elif result is not None:
+ assert resultOutParam is None
+ call = CGWrapper(call, pre=resultVar + " = ")
+
+ call = CGWrapper(call, post=";\n")
+ self.cgRoot.append(call)
+
+ if needsErrorResult or canOOM:
+ self.cgRoot.prepend(CGGeneric("FastErrorResult rv;\n"))
+ self.cgRoot.append(
+ CGGeneric(
+ fill(
+ """
+ if (MOZ_UNLIKELY(rv.MaybeSetPendingException(cx, ${context}))) {
+ return false;
+ }
+ """,
+ context=context,
+ )
+ )
+ )
+
+ self.cgRoot.append(CGGeneric("MOZ_ASSERT(!JS_IsExceptionPending(cx));\n"))
+
+ def define(self):
+ return self.cgRoot.define()
+
+
+def getUnionMemberName(type):
+ # Promises can't be in unions, because they're not distinguishable
+ # from anything else.
+ assert not type.isPromise()
+ if type.isGeckoInterface():
+ return type.inner.identifier.name
+ if type.isEnum():
+ return type.inner.identifier.name
+ return type.name
+
+
+# A counter for making sure that when we're wrapping up things in
+# nested sequences we don't use the same variable name to iterate over
+# different sequences.
+sequenceWrapLevel = 0
+recordWrapLevel = 0
+
+
+def wrapTypeIntoCurrentCompartment(type, value, isMember=True):
+ """
+ Take the thing named by "value" and if it contains "any",
+ "object", or spidermonkey-interface types inside return a CGThing
+ that will wrap them into the current compartment.
+ """
+ if type.isAny():
+ assert not type.nullable()
+ if isMember:
+ value = "JS::MutableHandle<JS::Value>::fromMarkedLocation(&%s)" % value
+ else:
+ value = "&" + value
+ return CGGeneric(
+ "if (!JS_WrapValue(cx, %s)) {\n" " return false;\n" "}\n" % value
+ )
+
+ if type.isObject():
+ if isMember:
+ value = "JS::MutableHandle<JSObject*>::fromMarkedLocation(&%s)" % value
+ else:
+ value = "&" + value
+ return CGGeneric(
+ "if (!JS_WrapObject(cx, %s)) {\n" " return false;\n" "}\n" % value
+ )
+
+ if type.isSpiderMonkeyInterface():
+ origValue = value
+ if type.nullable():
+ value = "%s.Value()" % value
+ wrapCode = CGGeneric(
+ "if (!%s.WrapIntoNewCompartment(cx)) {\n" " return false;\n" "}\n" % value
+ )
+ if type.nullable():
+ wrapCode = CGIfWrapper(wrapCode, "!%s.IsNull()" % origValue)
+ return wrapCode
+
+ if type.isSequence():
+ origValue = value
+ origType = type
+ if type.nullable():
+ type = type.inner
+ value = "%s.Value()" % value
+ global sequenceWrapLevel
+ index = "indexName%d" % sequenceWrapLevel
+ sequenceWrapLevel += 1
+ wrapElement = wrapTypeIntoCurrentCompartment(
+ type.inner, "%s[%s]" % (value, index)
+ )
+ sequenceWrapLevel -= 1
+ if not wrapElement:
+ return None
+ wrapCode = CGWrapper(
+ CGIndenter(wrapElement),
+ pre=(
+ "for (uint32_t %s = 0; %s < %s.Length(); ++%s) {\n"
+ % (index, index, value, index)
+ ),
+ post="}\n",
+ )
+ if origType.nullable():
+ wrapCode = CGIfWrapper(wrapCode, "!%s.IsNull()" % origValue)
+ return wrapCode
+
+ if type.isRecord():
+ origType = type
+ if type.nullable():
+ type = type.inner
+ recordRef = "%s.Value()" % value
+ else:
+ recordRef = value
+ global recordWrapLevel
+ entryRef = "mapEntry%d" % recordWrapLevel
+ recordWrapLevel += 1
+ wrapElement = wrapTypeIntoCurrentCompartment(type.inner, "%s.mValue" % entryRef)
+ recordWrapLevel -= 1
+ if not wrapElement:
+ return None
+ wrapCode = CGWrapper(
+ CGIndenter(wrapElement),
+ pre=("for (auto& %s : %s.Entries()) {\n" % (entryRef, recordRef)),
+ post="}\n",
+ )
+ if origType.nullable():
+ wrapCode = CGIfWrapper(wrapCode, "!%s.IsNull()" % value)
+ return wrapCode
+
+ if type.isDictionary():
+ assert not type.nullable()
+ myDict = type.inner
+ memberWraps = []
+ while myDict:
+ for member in myDict.members:
+ memberWrap = wrapArgIntoCurrentCompartment(
+ member,
+ "%s.%s"
+ % (value, CGDictionary.makeMemberName(member.identifier.name)),
+ )
+ if memberWrap:
+ memberWraps.append(memberWrap)
+ myDict = myDict.parent
+ return CGList(memberWraps) if len(memberWraps) != 0 else None
+
+ if type.isUnion():
+ memberWraps = []
+ if type.nullable():
+ type = type.inner
+ value = "%s.Value()" % value
+ for member in type.flatMemberTypes:
+ memberName = getUnionMemberName(member)
+ memberWrap = wrapTypeIntoCurrentCompartment(
+ member, "%s.GetAs%s()" % (value, memberName)
+ )
+ if memberWrap:
+ memberWrap = CGIfWrapper(memberWrap, "%s.Is%s()" % (value, memberName))
+ memberWraps.append(memberWrap)
+ return CGList(memberWraps, "else ") if len(memberWraps) != 0 else None
+
+ if (
+ type.isUndefined()
+ or type.isString()
+ or type.isPrimitive()
+ or type.isEnum()
+ or type.isGeckoInterface()
+ or type.isCallback()
+ or type.isPromise()
+ ):
+ # All of these don't need wrapping.
+ return None
+
+ raise TypeError(
+ "Unknown type; we don't know how to wrap it in constructor "
+ "arguments: %s" % type
+ )
+
+
+def wrapArgIntoCurrentCompartment(arg, value, isMember=True):
+ """
+ As wrapTypeIntoCurrentCompartment but handles things being optional
+ """
+ origValue = value
+ isOptional = arg.canHaveMissingValue()
+ if isOptional:
+ value = value + ".Value()"
+ wrap = wrapTypeIntoCurrentCompartment(arg.type, value, isMember)
+ if wrap and isOptional:
+ wrap = CGIfWrapper(wrap, "%s.WasPassed()" % origValue)
+ return wrap
+
+
+def needsContainsHack(m):
+ return m.getExtendedAttribute("ReturnValueNeedsContainsHack")
+
+
+def needsCallerType(m):
+ return m.getExtendedAttribute("NeedsCallerType")
+
+
+class CGPerSignatureCall(CGThing):
+ """
+ This class handles the guts of generating code for a particular
+ call signature. A call signature consists of four things:
+
+ 1) A return type, which can be None to indicate that there is no
+ actual return value (e.g. this is an attribute setter) or an
+ IDLType if there's an IDL type involved (including |void|).
+ 2) An argument list, which is allowed to be empty.
+ 3) A name of a native method to call.
+ 4) Whether or not this method is static. Note that this only controls how
+ the method is called (|self->nativeMethodName(...)| vs
+ |nativeMethodName(...)|).
+
+ We also need to know whether this is a method or a getter/setter
+ to do error reporting correctly.
+
+ The idlNode parameter can be either a method or an attr. We can query
+ |idlNode.identifier| in both cases, so we can be agnostic between the two.
+
+ dontSetSlot should be set to True if the value should not be cached in a
+ slot (even if the attribute is marked as StoreInSlot or Cached in the
+ WebIDL).
+ """
+
+ # XXXbz For now each entry in the argument list is either an
+ # IDLArgument or a FakeArgument, but longer-term we may want to
+ # have ways of flagging things like JSContext* or optional_argc in
+ # there.
+
+ def __init__(
+ self,
+ returnType,
+ arguments,
+ nativeMethodName,
+ static,
+ descriptor,
+ idlNode,
+ argConversionStartsAt=0,
+ getter=False,
+ setter=False,
+ isConstructor=False,
+ useCounterName=None,
+ resultVar=None,
+ objectName="obj",
+ dontSetSlot=False,
+ extendedAttributes=None,
+ ):
+ assert idlNode.isMethod() == (not getter and not setter)
+ assert idlNode.isAttr() == (getter or setter)
+ # Constructors are always static
+ assert not isConstructor or static
+
+ CGThing.__init__(self)
+ self.returnType = returnType
+ self.descriptor = descriptor
+ self.idlNode = idlNode
+ if extendedAttributes is None:
+ extendedAttributes = descriptor.getExtendedAttributes(
+ idlNode, getter=getter, setter=setter
+ )
+ self.extendedAttributes = extendedAttributes
+ self.arguments = arguments
+ self.argCount = len(arguments)
+ self.isConstructor = isConstructor
+ self.setSlot = (
+ not dontSetSlot and idlNode.isAttr() and idlNode.slotIndices is not None
+ )
+ cgThings = []
+
+ deprecated = idlNode.getExtendedAttribute("Deprecated") or (
+ idlNode.isStatic()
+ and descriptor.interface.getExtendedAttribute("Deprecated")
+ )
+ if deprecated:
+ cgThings.append(
+ CGGeneric(
+ dedent(
+ """
+ DeprecationWarning(cx, obj, DeprecatedOperations::e%s);
+ """
+ % deprecated[0]
+ )
+ )
+ )
+
+ lenientFloatCode = None
+ if idlNode.getExtendedAttribute("LenientFloat") is not None and (
+ setter or idlNode.isMethod()
+ ):
+ cgThings.append(
+ CGGeneric(
+ dedent(
+ """
+ bool foundNonFiniteFloat = false;
+ """
+ )
+ )
+ )
+ lenientFloatCode = "foundNonFiniteFloat = true;\n"
+
+ argsPre = []
+ if idlNode.isStatic():
+ # If we're a constructor, "obj" may not be a function, so calling
+ # XrayAwareCalleeGlobal() on it is not safe. Of course in the
+ # constructor case either "obj" is an Xray or we're already in the
+ # content compartment, not the Xray compartment, so just
+ # constructing the GlobalObject from "obj" is fine.
+ if isConstructor:
+ objForGlobalObject = "obj"
+ else:
+ objForGlobalObject = "xpc::XrayAwareCalleeGlobal(obj)"
+ cgThings.append(
+ CGGeneric(
+ fill(
+ """
+ GlobalObject global(cx, ${obj});
+ if (global.Failed()) {
+ return false;
+ }
+
+ """,
+ obj=objForGlobalObject,
+ )
+ )
+ )
+ argsPre.append("global")
+
+ # For JS-implemented interfaces we do not want to base the
+ # needsCx decision on the types involved, just on our extended
+ # attributes. Also, JSContext is not needed for the static case
+ # since GlobalObject already contains the context.
+ needsCx = needCx(
+ returnType,
+ arguments,
+ self.extendedAttributes,
+ not descriptor.interface.isJSImplemented(),
+ static,
+ )
+ if needsCx:
+ argsPre.append("cx")
+
+ needsUnwrap = False
+ argsPost = []
+ runConstructorInCallerCompartment = descriptor.interface.getExtendedAttribute(
+ "RunConstructorInCallerCompartment"
+ )
+ if isConstructor and not runConstructorInCallerCompartment:
+ needsUnwrap = True
+ needsUnwrappedVar = False
+ unwrappedVar = "obj"
+ if descriptor.interface.isJSImplemented():
+ # We need the desired proto in our constructor, because the
+ # constructor will actually construct our reflector.
+ argsPost.append("desiredProto")
+ elif descriptor.interface.isJSImplemented():
+ if not idlNode.isStatic():
+ needsUnwrap = True
+ needsUnwrappedVar = True
+ argsPost.append(
+ "(unwrappedObj ? js::GetNonCCWObjectRealm(*unwrappedObj) : js::GetContextRealm(cx))"
+ )
+ elif needScopeObject(
+ returnType,
+ arguments,
+ self.extendedAttributes,
+ descriptor.wrapperCache,
+ True,
+ idlNode.getExtendedAttribute("StoreInSlot"),
+ ):
+ # If we ever end up with APIs like this on cross-origin objects,
+ # figure out how the CheckedUnwrapDynamic bits should work. Chances
+ # are, just calling it with "cx" is fine... For now, though, just
+ # assert that it does not matter.
+ assert not descriptor.isMaybeCrossOriginObject()
+ # The scope object should always be from the relevant
+ # global. Make sure to unwrap it as needed.
+ cgThings.append(
+ CGGeneric(
+ dedent(
+ """
+ JS::Rooted<JSObject*> unwrappedObj(cx, js::CheckedUnwrapStatic(obj));
+ // Caller should have ensured that "obj" can be unwrapped already.
+ MOZ_DIAGNOSTIC_ASSERT(unwrappedObj);
+ """
+ )
+ )
+ )
+ argsPre.append("unwrappedObj")
+
+ if needsUnwrap and needsUnwrappedVar:
+ # We cannot assign into obj because it's a Handle, not a
+ # MutableHandle, so we need a separate Rooted.
+ cgThings.append(CGGeneric("Maybe<JS::Rooted<JSObject*> > unwrappedObj;\n"))
+ unwrappedVar = "unwrappedObj.ref()"
+
+ if idlNode.isMethod() and idlNode.isLegacycaller():
+ # If we can have legacycaller with identifier, we can't
+ # just use the idlNode to determine whether we're
+ # generating code for the legacycaller or not.
+ assert idlNode.isIdentifierLess()
+ # Pass in our thisVal
+ argsPre.append("args.thisv()")
+
+ if idlNode.isMethod():
+ argDescription = "argument %(index)d"
+ elif setter:
+ argDescription = "value being assigned"
+ else:
+ assert self.argCount == 0
+
+ if needsUnwrap:
+ # It's very important that we construct our unwrappedObj, if we need
+ # to do it, before we might start setting up Rooted things for our
+ # arguments, so that we don't violate the stack discipline Rooted
+ # depends on.
+ cgThings.append(
+ CGGeneric("bool objIsXray = xpc::WrapperFactory::IsXrayWrapper(obj);\n")
+ )
+ if needsUnwrappedVar:
+ cgThings.append(
+ CGIfWrapper(
+ CGGeneric("unwrappedObj.emplace(cx, obj);\n"), "objIsXray"
+ )
+ )
+
+ for i in range(argConversionStartsAt, self.argCount):
+ cgThings.append(
+ CGArgumentConverter(
+ arguments[i],
+ i,
+ self.descriptor,
+ argDescription % {"index": i + 1},
+ idlNode,
+ invalidEnumValueFatal=not setter,
+ lenientFloatCode=lenientFloatCode,
+ )
+ )
+
+ # Now that argument processing is done, enforce the LenientFloat stuff
+ if lenientFloatCode:
+ if setter:
+ foundNonFiniteFloatBehavior = "return true;\n"
+ else:
+ assert idlNode.isMethod()
+ foundNonFiniteFloatBehavior = dedent(
+ """
+ args.rval().setUndefined();
+ return true;
+ """
+ )
+ cgThings.append(
+ CGGeneric(
+ fill(
+ """
+ if (foundNonFiniteFloat) {
+ $*{returnSteps}
+ }
+ """,
+ returnSteps=foundNonFiniteFloatBehavior,
+ )
+ )
+ )
+
+ if needsUnwrap:
+ # Something depends on having the unwrapped object, so unwrap it now.
+ xraySteps = []
+ # XXXkhuey we should be able to MOZ_ASSERT that ${obj} is
+ # not null.
+ xraySteps.append(
+ CGGeneric(
+ fill(
+ """
+ // Since our object is an Xray, we can just CheckedUnwrapStatic:
+ // we know Xrays have no dynamic unwrap behavior.
+ ${obj} = js::CheckedUnwrapStatic(${obj});
+ if (!${obj}) {
+ return false;
+ }
+ """,
+ obj=unwrappedVar,
+ )
+ )
+ )
+ if isConstructor:
+ # If we're called via an xray, we need to enter the underlying
+ # object's compartment and then wrap up all of our arguments into
+ # that compartment as needed. This is all happening after we've
+ # already done the conversions from JS values to WebIDL (C++)
+ # values, so we only need to worry about cases where there are 'any'
+ # or 'object' types, or other things that we represent as actual
+ # JSAPI types, present. Effectively, we're emulating a
+ # CrossCompartmentWrapper, but working with the C++ types, not the
+ # original list of JS::Values.
+ cgThings.append(CGGeneric("Maybe<JSAutoRealm> ar;\n"))
+ xraySteps.append(CGGeneric("ar.emplace(cx, obj);\n"))
+ xraySteps.append(
+ CGGeneric(
+ dedent(
+ """
+ if (!JS_WrapObject(cx, &desiredProto)) {
+ return false;
+ }
+ """
+ )
+ )
+ )
+ xraySteps.extend(
+ wrapArgIntoCurrentCompartment(arg, argname, isMember=False)
+ for arg, argname in self.getArguments()
+ )
+
+ cgThings.append(CGIfWrapper(CGList(xraySteps), "objIsXray"))
+
+ if idlNode.getExtendedAttribute("CEReactions") is not None and not getter:
+ cgThings.append(
+ CGGeneric(
+ dedent(
+ """
+ Maybe<AutoCEReaction> ceReaction;
+ DocGroup* docGroup = self->GetDocGroup();
+ if (docGroup) {
+ ceReaction.emplace(docGroup->CustomElementReactionsStack(), cx);
+ }
+ """
+ )
+ )
+ )
+
+ # 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.isMaplikeOrSetlikeOrIterableMethod():
+ if (
+ idlNode.maplikeOrSetlikeOrIterable.isMaplike()
+ or idlNode.maplikeOrSetlikeOrIterable.isSetlike()
+ ):
+ cgThings.append(
+ CGMaplikeOrSetlikeMethodGenerator(
+ descriptor,
+ idlNode.maplikeOrSetlikeOrIterable,
+ idlNode.identifier.name,
+ )
+ )
+ else:
+ cgThings.append(
+ CGIterableMethodGenerator(
+ descriptor,
+ idlNode.identifier.name,
+ self.getArgumentNames(),
+ )
+ )
+ elif idlNode.isAttr() and idlNode.type.isObservableArray():
+ assert setter
+ cgThings.append(CGObservableArraySetterGenerator(descriptor, idlNode))
+ else:
+ context = GetLabelForErrorReporting(descriptor, idlNode, isConstructor)
+ if getter:
+ context = context + " getter"
+ elif setter:
+ context = context + " setter"
+ # Callee expects a quoted string for the context if
+ # there's a context.
+ context = '"%s"' % context
+
+ if idlNode.isMethod() and idlNode.getExtendedAttribute("WebExtensionStub"):
+ [
+ nativeMethodName,
+ argsPre,
+ args,
+ ] = self.processWebExtensionStubAttribute(idlNode, cgThings)
+ else:
+ args = self.getArguments()
+
+ cgThings.append(
+ CGCallGenerator(
+ self.needsErrorResult(),
+ needsCallerType(idlNode),
+ isChromeOnly(idlNode),
+ args,
+ argsPre,
+ returnType,
+ self.extendedAttributes,
+ descriptor,
+ nativeMethodName,
+ static,
+ # We know our "self" must be being kept alive; otherwise we have
+ # a serious problem. In common cases it's just an argument and
+ # we're MOZ_CAN_RUN_SCRIPT, but in some cases it's on the stack
+ # and being kept alive via references from JS.
+ object="MOZ_KnownLive(self)",
+ argsPost=argsPost,
+ resultVar=resultVar,
+ context=context,
+ )
+ )
+
+ if useCounterName:
+ # Generate a telemetry call for when [UseCounter] is used.
+ windowCode = fill(
+ """
+ SetUseCounter(obj, eUseCounter_${useCounterName});
+ """,
+ useCounterName=useCounterName,
+ )
+ workerCode = fill(
+ """
+ SetUseCounter(UseCounterWorker::${useCounterName});
+ """,
+ useCounterName=useCounterName,
+ )
+ code = ""
+ if idlNode.isExposedInWindow() and idlNode.isExposedInAnyWorker():
+ code += fill(
+ """
+ if (NS_IsMainThread()) {
+ ${windowCode}
+ } else {
+ ${workerCode}
+ }
+ """,
+ windowCode=windowCode,
+ workerCode=workerCode,
+ )
+ elif idlNode.isExposedInWindow():
+ code += windowCode
+ elif idlNode.isExposedInAnyWorker():
+ code += workerCode
+
+ cgThings.append(CGGeneric(code))
+
+ self.cgRoot = CGList(cgThings)
+
+ def getArgumentNames(self):
+ return ["arg" + str(i) for i in range(len(self.arguments))]
+
+ def getArguments(self):
+ return list(zip(self.arguments, self.getArgumentNames()))
+
+ def processWebExtensionStubAttribute(self, idlNode, cgThings):
+ nativeMethodName = "CallWebExtMethod"
+ stubNameSuffix = idlNode.getExtendedAttribute("WebExtensionStub")
+ if isinstance(stubNameSuffix, list):
+ nativeMethodName += stubNameSuffix[0]
+
+ argsLength = len(self.getArguments())
+ singleVariadicArg = argsLength == 1 and self.getArguments()[0][0].variadic
+
+ # If the method signature does only include a single variadic arguments,
+ # then `arg0` is already a Sequence of JS values and we can pass that
+ # to the WebExtensions Stub method as is.
+ if singleVariadicArg:
+ argsPre = [
+ "cx",
+ 'u"%s"_ns' % idlNode.identifier.name,
+ "Constify(%s)" % "arg0",
+ ]
+ args = []
+ return [nativeMethodName, argsPre, args]
+
+ argsPre = [
+ "cx",
+ 'u"%s"_ns' % idlNode.identifier.name,
+ "Constify(%s)" % "args_sequence",
+ ]
+ args = []
+
+ # Determine the maximum number of elements of the js values sequence argument,
+ # skipping the last optional callback argument if any:
+ #
+ # if this WebExtensions API method does expect a last optional callback argument,
+ # then it is the callback parameter supported for chrome-compatibility
+ # reasons, and we want it as a separate argument passed to the WebExtension
+ # stub method and skip it from the js values sequence including all other
+ # arguments.
+ maxArgsSequenceLen = argsLength
+ if argsLength > 0:
+ lastArg = self.getArguments()[argsLength - 1]
+ isCallback = lastArg[0].type.tag() == IDLType.Tags.callback
+ if isCallback and lastArg[0].optional:
+ argsPre.append(
+ "MOZ_KnownLive(NonNullHelper(Constify(%s)))" % lastArg[1]
+ )
+ maxArgsSequenceLen = argsLength - 1
+
+ cgThings.append(
+ CGGeneric(
+ dedent(
+ fill(
+ """
+ // Collecting all args js values into the single sequence argument
+ // passed to the webextensions stub method.
+ //
+ // NOTE: The stub method will receive the original non-normalized js values,
+ // but those arguments will still be normalized on the main thread by the
+ // WebExtensions API request handler using the same JSONSchema defnition
+ // used by the non-webIDL webextensions API bindings.
+ AutoSequence<JS::Value> args_sequence;
+ SequenceRooter<JS::Value> args_sequence_holder(cx, &args_sequence);
+
+ // maximum number of arguments expected by the WebExtensions API method
+ // excluding the last optional chrome-compatible callback argument (which
+ // is being passed to the stub method as a separate additional argument).
+ uint32_t maxArgsSequenceLen = ${maxArgsSequenceLen};
+
+ uint32_t sequenceArgsLen = args.length() <= maxArgsSequenceLen ?
+ args.length() : maxArgsSequenceLen;
+
+ if (sequenceArgsLen > 0) {
+ if (!args_sequence.SetCapacity(sequenceArgsLen, mozilla::fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+ for (uint32_t argIdx = 0; argIdx < sequenceArgsLen; ++argIdx) {
+ // OK to do infallible append here, since we ensured capacity already.
+ JS::Value& slot = *args_sequence.AppendElement();
+ slot = args[argIdx];
+ }
+ }
+ """,
+ maxArgsSequenceLen=maxArgsSequenceLen,
+ )
+ )
+ )
+ )
+
+ return [nativeMethodName, argsPre, args]
+
+ def needsErrorResult(self):
+ return "needsErrorResult" in self.extendedAttributes
+
+ def wrap_return_value(self):
+ wrapCode = ""
+
+ returnsNewObject = memberReturnsNewObject(self.idlNode)
+ if returnsNewObject and (
+ self.returnType.isGeckoInterface() or self.returnType.isPromise()
+ ):
+ wrapCode += dedent(
+ """
+ static_assert(!std::is_pointer_v<decltype(result)>,
+ "NewObject implies that we need to keep the object alive with a strong reference.");
+ """
+ )
+
+ if self.setSlot:
+ # For attributes in slots, we want to do some
+ # post-processing once we've wrapped them.
+ successCode = "break;\n"
+ else:
+ successCode = None
+
+ resultTemplateValues = {
+ "jsvalRef": "args.rval()",
+ "jsvalHandle": "args.rval()",
+ "returnsNewObject": returnsNewObject,
+ "isConstructorRetval": self.isConstructor,
+ "successCode": successCode,
+ # 'obj' in this dictionary is the thing whose compartment we are
+ # trying to do the to-JS conversion in. We're going to put that
+ # thing in a variable named "conversionScope" if setSlot is true.
+ # Otherwise, just use "obj" for lack of anything better.
+ "obj": "conversionScope" if self.setSlot else "obj",
+ }
+
+ wrapCode += wrapForType(self.returnType, self.descriptor, resultTemplateValues)
+
+ if self.setSlot:
+ if self.idlNode.isStatic():
+ raise TypeError(
+ "Attribute %s.%s is static, so we don't have a useful slot "
+ "to cache it in, because we don't have support for that on "
+ "interface objects. See "
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=1363870"
+ % (
+ self.descriptor.interface.identifier.name,
+ self.idlNode.identifier.name,
+ )
+ )
+
+ # When using a slot on the Xray expando, we need to make sure that
+ # our initial conversion to a JS::Value is done in the caller
+ # compartment. When using a slot on our reflector, we want to do
+ # the conversion in the compartment of that reflector (that is,
+ # slotStorage). In both cases we want to make sure that we finally
+ # set up args.rval() to be in the caller compartment. We also need
+ # to make sure that the conversion steps happen inside a do/while
+ # that they can break out of on success.
+ #
+ # Of course we always have to wrap the value into the slotStorage
+ # compartment before we store it in slotStorage.
+
+ # postConversionSteps are the steps that run while we're still in
+ # the compartment we do our conversion in but after we've finished
+ # the initial conversion into args.rval().
+ postConversionSteps = ""
+ if needsContainsHack(self.idlNode):
+ # Define a .contains on the object that has the same value as
+ # .includes; needed for backwards compat in extensions as we
+ # migrate some DOMStringLists to FrozenArray.
+ postConversionSteps += dedent(
+ """
+ if (args.rval().isObject() && nsContentUtils::ThreadsafeIsSystemCaller(cx)) {
+ JS::Rooted<JSObject*> rvalObj(cx, &args.rval().toObject());
+ JS::Rooted<JS::Value> includesVal(cx);
+ if (!JS_GetProperty(cx, rvalObj, "includes", &includesVal) ||
+ !JS_DefineProperty(cx, rvalObj, "contains", includesVal, JSPROP_ENUMERATE)) {
+ return false;
+ }
+ }
+
+ """
+ )
+ if self.idlNode.getExtendedAttribute("Frozen"):
+ assert (
+ self.idlNode.type.isSequence() or self.idlNode.type.isDictionary()
+ )
+ freezeValue = CGGeneric(
+ "JS::Rooted<JSObject*> rvalObj(cx, &args.rval().toObject());\n"
+ "if (!JS_FreezeObject(cx, rvalObj)) {\n"
+ " return false;\n"
+ "}\n"
+ )
+ if self.idlNode.type.nullable():
+ freezeValue = CGIfWrapper(freezeValue, "args.rval().isObject()")
+ postConversionSteps += freezeValue.define()
+
+ # slotStorageSteps are steps that run once we have entered the
+ # slotStorage compartment.
+ slotStorageSteps = fill(
+ """
+ // Make a copy so that we don't do unnecessary wrapping on args.rval().
+ JS::Rooted<JS::Value> storedVal(cx, args.rval());
+ if (!${maybeWrap}(cx, &storedVal)) {
+ return false;
+ }
+ JS::SetReservedSlot(slotStorage, slotIndex, storedVal);
+ """,
+ maybeWrap=getMaybeWrapValueFuncForType(self.idlNode.type),
+ )
+
+ checkForXray = mayUseXrayExpandoSlots(self.descriptor, self.idlNode)
+
+ # For the case of Cached attributes, go ahead and preserve our
+ # wrapper if needed. We need to do this because otherwise the
+ # wrapper could get garbage-collected and the cached value would
+ # suddenly disappear, but the whole premise of cached values is that
+ # they never change without explicit action on someone's part. We
+ # don't do this for StoreInSlot, since those get dealt with during
+ # wrapper setup, and failure would involve us trying to clear an
+ # already-preserved wrapper.
+ if (
+ self.idlNode.getExtendedAttribute("Cached")
+ and self.descriptor.wrapperCache
+ ):
+ preserveWrapper = dedent(
+ """
+ PreserveWrapper(self);
+ """
+ )
+ if checkForXray:
+ preserveWrapper = fill(
+ """
+ if (!isXray) {
+ // In the Xray case we don't need to do this, because getting the
+ // expando object already preserved our wrapper.
+ $*{preserveWrapper}
+ }
+ """,
+ preserveWrapper=preserveWrapper,
+ )
+ slotStorageSteps += preserveWrapper
+
+ if checkForXray:
+ # In the Xray case we use the current global as conversion
+ # scope, as explained in the big compartment/conversion comment
+ # above.
+ conversionScope = "isXray ? JS::CurrentGlobalOrNull(cx) : slotStorage"
+ else:
+ conversionScope = "slotStorage"
+
+ wrapCode = fill(
+ """
+ {
+ JS::Rooted<JSObject*> conversionScope(cx, ${conversionScope});
+ JSAutoRealm ar(cx, conversionScope);
+ do { // block we break out of when done wrapping
+ $*{wrapCode}
+ } while (false);
+ $*{postConversionSteps}
+ }
+ { // And now store things in the realm of our slotStorage.
+ JSAutoRealm ar(cx, slotStorage);
+ $*{slotStorageSteps}
+ }
+ // And now make sure args.rval() is in the caller realm.
+ return ${maybeWrap}(cx, args.rval());
+ """,
+ conversionScope=conversionScope,
+ wrapCode=wrapCode,
+ postConversionSteps=postConversionSteps,
+ slotStorageSteps=slotStorageSteps,
+ maybeWrap=getMaybeWrapValueFuncForType(self.idlNode.type),
+ )
+ return wrapCode
+
+ def define(self):
+ return self.cgRoot.define() + self.wrap_return_value()
+
+
+class CGSwitch(CGList):
+ """
+ A class to generate code for a switch statement.
+
+ Takes three constructor arguments: an expression, a list of cases,
+ and an optional default.
+
+ Each case is a CGCase. The default is a CGThing for the body of
+ the default case, if any.
+ """
+
+ def __init__(self, expression, cases, default=None):
+ CGList.__init__(self, [CGIndenter(c) for c in cases])
+ self.prepend(CGGeneric("switch (" + expression + ") {\n"))
+ if default is not None:
+ self.append(
+ CGIndenter(
+ CGWrapper(CGIndenter(default), pre="default: {\n", post="}\n")
+ )
+ )
+
+ self.append(CGGeneric("}\n"))
+
+
+class CGCase(CGList):
+ """
+ A class to generate code for a case statement.
+
+ Takes three constructor arguments: an expression, a CGThing for
+ the body (allowed to be None if there is no body), and an optional
+ argument for whether add a break, add fallthrough annotation or add nothing
+ (defaulting to add a break).
+ """
+
+ ADD_BREAK = 0
+ ADD_FALLTHROUGH = 1
+ DONT_ADD_BREAK = 2
+
+ def __init__(self, expression, body, breakOrFallthrough=ADD_BREAK):
+ CGList.__init__(self, [])
+
+ assert (
+ breakOrFallthrough == CGCase.ADD_BREAK
+ or breakOrFallthrough == CGCase.ADD_FALLTHROUGH
+ or breakOrFallthrough == CGCase.DONT_ADD_BREAK
+ )
+
+ self.append(CGGeneric("case " + expression + ": {\n"))
+ bodyList = CGList([body])
+ if breakOrFallthrough == CGCase.ADD_FALLTHROUGH:
+ bodyList.append(CGGeneric("[[fallthrough]];\n"))
+ elif breakOrFallthrough == CGCase.ADD_BREAK:
+ bodyList.append(CGGeneric("break;\n"))
+ self.append(CGIndenter(bodyList))
+ self.append(CGGeneric("}\n"))
+
+
+class CGMethodCall(CGThing):
+ """
+ A class to generate selection of a method signature from a set of
+ signatures and generation of a call to that signature.
+ """
+
+ def __init__(
+ self, nativeMethodName, static, descriptor, method, isConstructor=False
+ ):
+ CGThing.__init__(self)
+
+ methodName = GetLabelForErrorReporting(descriptor, method, isConstructor)
+ argDesc = "argument %d"
+
+ if method.getExtendedAttribute("UseCounter"):
+ useCounterName = methodName.replace(".", "_").replace(" ", "_")
+ else:
+ useCounterName = None
+
+ if method.isStatic():
+ nativeType = descriptor.nativeType
+ staticTypeOverride = PropertyDefiner.getStringAttr(
+ method, "StaticClassOverride"
+ )
+ if staticTypeOverride:
+ nativeType = staticTypeOverride
+ nativeMethodName = "%s::%s" % (nativeType, nativeMethodName)
+
+ def requiredArgCount(signature):
+ arguments = signature[1]
+ if len(arguments) == 0:
+ return 0
+ requiredArgs = len(arguments)
+ while requiredArgs and arguments[requiredArgs - 1].optional:
+ requiredArgs -= 1
+ return requiredArgs
+
+ def getPerSignatureCall(signature, argConversionStartsAt=0):
+ return CGPerSignatureCall(
+ signature[0],
+ signature[1],
+ nativeMethodName,
+ static,
+ descriptor,
+ method,
+ argConversionStartsAt=argConversionStartsAt,
+ isConstructor=isConstructor,
+ useCounterName=useCounterName,
+ )
+
+ signatures = method.signatures()
+ if len(signatures) == 1:
+ # Special case: we can just do a per-signature method call
+ # here for our one signature and not worry about switching
+ # on anything.
+ signature = signatures[0]
+ self.cgRoot = CGList([getPerSignatureCall(signature)])
+ requiredArgs = requiredArgCount(signature)
+
+ # 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.isMaplikeOrSetlikeOrIterableMethod():
+ code = fill(
+ """
+ if (!args.requireAtLeast(cx, "${methodName}", ${requiredArgs})) {
+ return false;
+ }
+ """,
+ requiredArgs=requiredArgs,
+ methodName=methodName,
+ )
+ self.cgRoot.prepend(CGGeneric(code))
+ return
+
+ # Need to find the right overload
+ maxArgCount = method.maxArgCount
+ allowedArgCounts = method.allowedArgCounts
+
+ argCountCases = []
+ for argCountIdx, argCount in enumerate(allowedArgCounts):
+ possibleSignatures = method.signaturesForArgCount(argCount)
+
+ # Try to optimize away cases when the next argCount in the list
+ # will have the same code as us; if it does, we can fall through to
+ # that case.
+ if argCountIdx + 1 < len(allowedArgCounts):
+ nextPossibleSignatures = method.signaturesForArgCount(
+ allowedArgCounts[argCountIdx + 1]
+ )
+ else:
+ nextPossibleSignatures = None
+ if possibleSignatures == nextPossibleSignatures:
+ # Same set of signatures means we better have the same
+ # distinguishing index. So we can in fact just fall through to
+ # the next case here.
+ assert len(possibleSignatures) == 1 or (
+ method.distinguishingIndexForArgCount(argCount)
+ == method.distinguishingIndexForArgCount(
+ allowedArgCounts[argCountIdx + 1]
+ )
+ )
+ argCountCases.append(
+ CGCase(str(argCount), None, CGCase.ADD_FALLTHROUGH)
+ )
+ continue
+
+ if len(possibleSignatures) == 1:
+ # easy case!
+ signature = possibleSignatures[0]
+ argCountCases.append(
+ CGCase(str(argCount), getPerSignatureCall(signature))
+ )
+ continue
+
+ distinguishingIndex = method.distinguishingIndexForArgCount(argCount)
+
+ def distinguishingArgument(signature):
+ args = signature[1]
+ if distinguishingIndex < len(args):
+ return args[distinguishingIndex]
+ assert args[-1].variadic
+ return args[-1]
+
+ def distinguishingType(signature):
+ return distinguishingArgument(signature).type
+
+ for sig in possibleSignatures:
+ # We should not have "any" args at distinguishingIndex,
+ # since we have multiple possible signatures remaining,
+ # but "any" is never distinguishable from anything else.
+ assert not distinguishingType(sig).isAny()
+ # We can't handle unions at the distinguishing index.
+ if distinguishingType(sig).isUnion():
+ raise TypeError(
+ "No support for unions as distinguishing "
+ "arguments yet: %s" % distinguishingArgument(sig).location
+ )
+ # We don't support variadics as the distinguishingArgument yet.
+ # If you want to add support, consider this case:
+ #
+ # undefined(long... foo);
+ # undefined(long bar, Int32Array baz);
+ #
+ # in which we have to convert argument 0 to long before picking
+ # an overload... but all the variadic stuff needs to go into a
+ # single array in case we pick that overload, so we have to have
+ # machinery for converting argument 0 to long and then either
+ # placing it in the variadic bit or not. Or something. We may
+ # be able to loosen this restriction if the variadic arg is in
+ # fact at distinguishingIndex, perhaps. Would need to
+ # double-check.
+ if distinguishingArgument(sig).variadic:
+ raise TypeError(
+ "No support for variadics as distinguishing "
+ "arguments yet: %s" % distinguishingArgument(sig).location
+ )
+
+ # Convert all our arguments up to the distinguishing index.
+ # Doesn't matter which of the possible signatures we use, since
+ # they all have the same types up to that point; just use
+ # possibleSignatures[0]
+ caseBody = [
+ CGArgumentConverter(
+ possibleSignatures[0][1][i],
+ i,
+ descriptor,
+ argDesc % (i + 1),
+ method,
+ )
+ for i in range(0, distinguishingIndex)
+ ]
+
+ # Select the right overload from our set.
+ distinguishingArg = "args[%d]" % distinguishingIndex
+
+ def tryCall(
+ signature, indent, isDefinitelyObject=False, isNullOrUndefined=False
+ ):
+ assert not isDefinitelyObject or not isNullOrUndefined
+ assert isDefinitelyObject or isNullOrUndefined
+ if isDefinitelyObject:
+ failureCode = "break;\n"
+ else:
+ failureCode = None
+ type = distinguishingType(signature)
+ # The argument at index distinguishingIndex can't possibly be
+ # unset here, because we've already checked that argc is large
+ # enough that we can examine this argument. But note that we
+ # still want to claim that optional arguments are optional, in
+ # case undefined was passed in.
+ argIsOptional = distinguishingArgument(signature).canHaveMissingValue()
+ testCode = instantiateJSToNativeConversion(
+ getJSToNativeConversionInfo(
+ type,
+ descriptor,
+ failureCode=failureCode,
+ isDefinitelyObject=isDefinitelyObject,
+ isNullOrUndefined=isNullOrUndefined,
+ isOptional=argIsOptional,
+ sourceDescription=(argDesc % (distinguishingIndex + 1)),
+ ),
+ {
+ "declName": "arg%d" % distinguishingIndex,
+ "holderName": ("arg%d" % distinguishingIndex) + "_holder",
+ "val": distinguishingArg,
+ "obj": "obj",
+ "haveValue": "args.hasDefined(%d)" % distinguishingIndex,
+ "passedToJSImpl": toStringBool(
+ isJSImplementedDescriptor(descriptor)
+ ),
+ },
+ checkForValue=argIsOptional,
+ )
+ caseBody.append(CGIndenter(testCode, indent))
+
+ # If we got this far, we know we unwrapped to the right
+ # C++ type, so just do the call. Start conversion with
+ # distinguishingIndex + 1, since we already converted
+ # distinguishingIndex.
+ caseBody.append(
+ CGIndenter(
+ getPerSignatureCall(signature, distinguishingIndex + 1), indent
+ )
+ )
+
+ def hasConditionalConversion(type):
+ """
+ Return whether the argument conversion for this type will be
+ conditional on the type of incoming JS value. For example, for
+ interface types the conversion is conditional on the incoming
+ value being isObject().
+
+ For the types for which this returns false, we do not have to
+ output extra isUndefined() or isNullOrUndefined() cases, because
+ null/undefined values will just fall through into our
+ unconditional conversion.
+ """
+ if type.isString() or type.isEnum():
+ return False
+ if type.isBoolean():
+ distinguishingTypes = (
+ distinguishingType(s) for s in possibleSignatures
+ )
+ return any(
+ t.isString() or t.isEnum() or t.isNumeric()
+ for t in distinguishingTypes
+ )
+ if type.isNumeric():
+ distinguishingTypes = (
+ distinguishingType(s) for s in possibleSignatures
+ )
+ return any(t.isString() or t.isEnum() for t in distinguishingTypes)
+ return True
+
+ def needsNullOrUndefinedCase(type):
+ """
+ Return true if the type needs a special isNullOrUndefined() case
+ """
+ return (
+ type.nullable() and hasConditionalConversion(type)
+ ) or type.isDictionary()
+
+ # First check for undefined and optional distinguishing arguments
+ # and output a special branch for that case. Note that we don't
+ # use distinguishingArgument here because we actualy want to
+ # exclude variadic arguments. Also note that we skip this check if
+ # we plan to output a isNullOrUndefined() special case for this
+ # argument anyway, since that will subsume our isUndefined() check.
+ # This is safe, because there can be at most one nullable
+ # distinguishing argument, so if we're it we'll definitely get
+ # picked up by the nullable handling. Also, we can skip this check
+ # if the argument has an unconditional conversion later on.
+ undefSigs = [
+ s
+ for s in possibleSignatures
+ if distinguishingIndex < len(s[1])
+ and s[1][distinguishingIndex].optional
+ and hasConditionalConversion(s[1][distinguishingIndex].type)
+ and not needsNullOrUndefinedCase(s[1][distinguishingIndex].type)
+ ]
+ # Can't have multiple signatures with an optional argument at the
+ # same index.
+ assert len(undefSigs) < 2
+ if len(undefSigs) > 0:
+ caseBody.append(
+ CGGeneric("if (%s.isUndefined()) {\n" % distinguishingArg)
+ )
+ tryCall(undefSigs[0], 2, isNullOrUndefined=True)
+ caseBody.append(CGGeneric("}\n"))
+
+ # Next, check for null or undefined. That means looking for
+ # nullable arguments at the distinguishing index and outputting a
+ # separate branch for them. But if the nullable argument has an
+ # unconditional conversion, we don't need to do that. The reason
+ # for that is that at most one argument at the distinguishing index
+ # is nullable (since two nullable arguments are not
+ # distinguishable), and null/undefined values will always fall
+ # through to the unconditional conversion we have, if any, since
+ # they will fail whatever the conditions on the input value are for
+ # our other conversions.
+ nullOrUndefSigs = [
+ s
+ for s in possibleSignatures
+ if needsNullOrUndefinedCase(distinguishingType(s))
+ ]
+ # Can't have multiple nullable types here
+ assert len(nullOrUndefSigs) < 2
+ if len(nullOrUndefSigs) > 0:
+ caseBody.append(
+ CGGeneric("if (%s.isNullOrUndefined()) {\n" % distinguishingArg)
+ )
+ tryCall(nullOrUndefSigs[0], 2, isNullOrUndefined=True)
+ caseBody.append(CGGeneric("}\n"))
+
+ # Now check for distinguishingArg being various kinds of objects.
+ # The spec says to check for the following things in order:
+ # 1) A platform object that's not a platform array object, being
+ # passed to an interface or "object" arg.
+ # 2) A callable object being passed to a callback or "object" arg.
+ # 3) An iterable object being passed to a sequence arg.
+ # 4) Any object being passed to a array or callback interface or
+ # dictionary or "object" arg.
+
+ # First grab all the overloads that have a non-callback interface
+ # (which includes SpiderMonkey interfaces) at the distinguishing
+ # index. We can also include the ones that have an "object" here,
+ # since if those are present no other object-typed argument will
+ # be.
+ objectSigs = [
+ s
+ for s in possibleSignatures
+ if (
+ distinguishingType(s).isObject()
+ or distinguishingType(s).isNonCallbackInterface()
+ )
+ ]
+
+ # And all the overloads that take callbacks
+ objectSigs.extend(
+ s for s in possibleSignatures if distinguishingType(s).isCallback()
+ )
+
+ # And all the overloads that take sequences
+ objectSigs.extend(
+ s for s in possibleSignatures if distinguishingType(s).isSequence()
+ )
+
+ # Now append all the overloads that take a dictionary or callback
+ # interface or record. There should be only one of these!
+ genericObjectSigs = [
+ s
+ for s in possibleSignatures
+ if (
+ distinguishingType(s).isDictionary()
+ or distinguishingType(s).isRecord()
+ or distinguishingType(s).isCallbackInterface()
+ )
+ ]
+ assert len(genericObjectSigs) <= 1
+ objectSigs.extend(genericObjectSigs)
+
+ # There might be more than one thing in objectSigs; we need to check
+ # which ones we unwrap to.
+ if len(objectSigs) > 0:
+ # Here it's enough to guard on our argument being an object.
+ # The code for unwrapping non-callback interfaces, spiderMonkey
+ # interfaces, and sequences will just bail out and move
+ # on to the next overload if the object fails to unwrap
+ # correctly, while "object" accepts any object anyway. We
+ # could even not do the isObject() check up front here, but in
+ # cases where we have multiple object overloads it makes sense
+ # to do it only once instead of for each overload. That will
+ # also allow the unwrapping test to skip having to do codegen
+ # for the null-or-undefined case, which we already handled
+ # above.
+ caseBody.append(CGGeneric("if (%s.isObject()) {\n" % distinguishingArg))
+ for sig in objectSigs:
+ caseBody.append(CGIndenter(CGGeneric("do {\n")))
+ # Indent by 4, since we need to indent further
+ # than our "do" statement
+ tryCall(sig, 4, isDefinitelyObject=True)
+ caseBody.append(CGIndenter(CGGeneric("} while (false);\n")))
+
+ caseBody.append(CGGeneric("}\n"))
+
+ # Now we only have to consider booleans, numerics, and strings. If
+ # we only have one of them, then we can just output it. But if not,
+ # then we need to output some of the cases conditionally: if we have
+ # a string overload, then boolean and numeric are conditional, and
+ # if not then boolean is conditional if we have a numeric overload.
+ def findUniqueSignature(filterLambda):
+ sigs = [s for s in possibleSignatures if filterLambda(s)]
+ assert len(sigs) < 2
+ if len(sigs) > 0:
+ return sigs[0]
+ return None
+
+ stringSignature = findUniqueSignature(
+ lambda s: (
+ distinguishingType(s).isString() or distinguishingType(s).isEnum()
+ )
+ )
+ numericSignature = findUniqueSignature(
+ lambda s: distinguishingType(s).isNumeric()
+ )
+ booleanSignature = findUniqueSignature(
+ lambda s: distinguishingType(s).isBoolean()
+ )
+
+ if stringSignature or numericSignature:
+ booleanCondition = "%s.isBoolean()"
+ else:
+ booleanCondition = None
+
+ if stringSignature:
+ numericCondition = "%s.isNumber()"
+ else:
+ numericCondition = None
+
+ def addCase(sig, condition):
+ sigCode = getPerSignatureCall(sig, distinguishingIndex)
+ if condition:
+ sigCode = CGIfWrapper(sigCode, condition % distinguishingArg)
+ caseBody.append(sigCode)
+
+ if booleanSignature:
+ addCase(booleanSignature, booleanCondition)
+ if numericSignature:
+ addCase(numericSignature, numericCondition)
+ if stringSignature:
+ addCase(stringSignature, None)
+
+ if not booleanSignature and not numericSignature and not stringSignature:
+ # Just throw; we have no idea what we're supposed to
+ # do with this.
+ caseBody.append(
+ CGGeneric(
+ 'return cx.ThrowErrorMessage<MSG_OVERLOAD_RESOLUTION_FAILED>("%d", "%d");\n'
+ % (distinguishingIndex + 1, argCount)
+ )
+ )
+
+ argCountCases.append(CGCase(str(argCount), CGList(caseBody)))
+
+ overloadCGThings = []
+ overloadCGThings.append(
+ CGGeneric(
+ "unsigned argcount = std::min(args.length(), %du);\n" % maxArgCount
+ )
+ )
+ overloadCGThings.append(
+ CGSwitch(
+ "argcount",
+ argCountCases,
+ CGGeneric(
+ dedent(
+ """
+ // Using nsPrintfCString here would require including that
+ // header. Let's not worry about it.
+ nsAutoCString argCountStr;
+ argCountStr.AppendPrintf("%u", args.length());
+ return cx.ThrowErrorMessage<MSG_INVALID_OVERLOAD_ARGCOUNT>(argCountStr.get());
+ """
+ )
+ ),
+ )
+ )
+ overloadCGThings.append(
+ CGGeneric(
+ 'MOZ_CRASH("We have an always-returning default case");\n'
+ "return false;\n"
+ )
+ )
+ self.cgRoot = CGList(overloadCGThings)
+
+ def define(self):
+ return self.cgRoot.define()
+
+
+class CGGetterCall(CGPerSignatureCall):
+ """
+ A class to generate a native object getter call for a particular IDL
+ getter.
+ """
+
+ def __init__(
+ self,
+ returnType,
+ nativeMethodName,
+ descriptor,
+ attr,
+ dontSetSlot=False,
+ extendedAttributes=None,
+ ):
+ if attr.getExtendedAttribute("UseCounter"):
+ useCounterName = "%s_%s_getter" % (
+ descriptor.interface.identifier.name,
+ attr.identifier.name,
+ )
+ else:
+ useCounterName = None
+ if attr.isStatic():
+ nativeMethodName = "%s::%s" % (descriptor.nativeType, nativeMethodName)
+ CGPerSignatureCall.__init__(
+ self,
+ returnType,
+ [],
+ nativeMethodName,
+ attr.isStatic(),
+ descriptor,
+ attr,
+ getter=True,
+ useCounterName=useCounterName,
+ dontSetSlot=dontSetSlot,
+ extendedAttributes=extendedAttributes,
+ )
+
+
+class FakeIdentifier:
+ def __init__(self, name):
+ self.name = name
+
+
+class FakeArgument:
+ """
+ A class that quacks like an IDLArgument. This is used to make
+ setters look like method calls or for special operations.
+ """
+
+ def __init__(self, type, name="arg", allowTreatNonCallableAsNull=False):
+ self.type = type
+ self.optional = False
+ self.variadic = False
+ self.defaultValue = None
+ self._allowTreatNonCallableAsNull = allowTreatNonCallableAsNull
+
+ self.identifier = FakeIdentifier(name)
+
+ def allowTreatNonCallableAsNull(self):
+ return self._allowTreatNonCallableAsNull
+
+ def canHaveMissingValue(self):
+ return False
+
+
+class CGSetterCall(CGPerSignatureCall):
+ """
+ A class to generate a native object setter call for a particular IDL
+ setter.
+ """
+
+ def __init__(self, argType, nativeMethodName, descriptor, attr):
+ if attr.getExtendedAttribute("UseCounter"):
+ useCounterName = "%s_%s_setter" % (
+ descriptor.interface.identifier.name,
+ attr.identifier.name,
+ )
+ else:
+ useCounterName = None
+ if attr.isStatic():
+ nativeMethodName = "%s::%s" % (descriptor.nativeType, nativeMethodName)
+ CGPerSignatureCall.__init__(
+ self,
+ None,
+ [FakeArgument(argType, allowTreatNonCallableAsNull=True)],
+ nativeMethodName,
+ attr.isStatic(),
+ descriptor,
+ attr,
+ setter=True,
+ useCounterName=useCounterName,
+ )
+
+ def wrap_return_value(self):
+ attr = self.idlNode
+ clearSlot = ""
+ if self.descriptor.wrapperCache and attr.slotIndices is not None:
+ if attr.getExtendedAttribute("StoreInSlot"):
+ clearSlot = "%s(cx, self);\n" % MakeClearCachedValueNativeName(
+ self.idlNode
+ )
+ elif attr.getExtendedAttribute("Cached"):
+ clearSlot = "%s(self);\n" % MakeClearCachedValueNativeName(self.idlNode)
+
+ # We have no return value
+ return "\n" "%s" "return true;\n" % clearSlot
+
+
+class CGAbstractBindingMethod(CGAbstractStaticMethod):
+ """
+ Common class to generate some of our class hooks. This will generate the
+ function declaration, get a reference to the JS object for our binding
+ object (which might be an argument of the class hook or something we get
+ from a JS::CallArgs), and unwrap into the right C++ type. Subclasses are
+ expected to override the generate_code function to do the rest of the work.
+ This function should return a CGThing which is already properly indented.
+
+ getThisObj should be code for getting a JSObject* for the binding
+ object. "" can be passed in if the binding object is already stored in
+ 'obj'.
+
+ callArgs should be code for getting a JS::CallArgs into a variable
+ called 'args'. This can be "" if there is already such a variable
+ around or if the body does not need a JS::CallArgs.
+
+ """
+
+ def __init__(
+ self,
+ descriptor,
+ name,
+ args,
+ getThisObj,
+ callArgs="JS::CallArgs args = JS::CallArgsFromVp(argc, vp);\n",
+ ):
+ CGAbstractStaticMethod.__init__(
+ self, descriptor, name, "bool", args, canRunScript=True
+ )
+
+ # This can't ever happen, because we only use this for class hooks.
+ self.unwrapFailureCode = fill(
+ """
+ MOZ_CRASH("Unexpected object in '${name}' hook");
+ return false;
+ """,
+ name=name,
+ )
+
+ if getThisObj == "":
+ self.getThisObj = None
+ else:
+ self.getThisObj = CGGeneric(
+ "JS::Rooted<JSObject*> obj(cx, %s);\n" % getThisObj
+ )
+ self.callArgs = callArgs
+
+ def definition_body(self):
+ body = self.callArgs
+ if self.getThisObj is not None:
+ body += self.getThisObj.define() + "\n"
+ body += "%s* self;\n" % self.descriptor.nativeType
+ body += dedent(
+ """
+ JS::Rooted<JS::Value> rootSelf(cx, JS::ObjectValue(*obj));
+ """
+ )
+
+ body += str(
+ CastableObjectUnwrapper(
+ self.descriptor, "rootSelf", "&rootSelf", "self", self.unwrapFailureCode
+ )
+ )
+
+ return body + self.generate_code().define()
+
+ def generate_code(self):
+ assert False # Override me
+
+
+class CGAbstractStaticBindingMethod(CGAbstractStaticMethod):
+ """
+ Common class to generate the JSNatives for all our static methods, getters
+ and setters. This will generate the function declaration and unwrap the
+ global object. Subclasses are expected to override the generate_code
+ function to do the rest of the work. This function should return a
+ CGThing which is already properly indented.
+ """
+
+ def __init__(self, descriptor, name):
+ CGAbstractStaticMethod.__init__(
+ self, descriptor, name, "bool", JSNativeArguments(), canRunScript=True
+ )
+
+ def definition_body(self):
+ # Make sure that "obj" is in the same compartment as "cx", since we'll
+ # later use it to wrap return values.
+ unwrap = dedent(
+ """
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JS::Rooted<JSObject*> obj(cx, &args.callee());
+
+ """
+ )
+ return unwrap + self.generate_code().define()
+
+ def generate_code(self):
+ assert False # Override me
+
+
+def MakeNativeName(name):
+ return name[0].upper() + IDLToCIdentifier(name[1:])
+
+
+def GetWebExposedName(idlObject, descriptor):
+ if idlObject == descriptor.operations["Stringifier"]:
+ return "toString"
+ name = idlObject.identifier.name
+ if name == "__namedsetter":
+ return "named setter"
+ if name == "__namedgetter":
+ return "named getter"
+ if name == "__indexedsetter":
+ return "indexed setter"
+ if name == "__indexedgetter":
+ return "indexed getter"
+ if name == "__legacycaller":
+ return "legacy caller"
+ return name
+
+
+def GetConstructorNameForReporting(descriptor, ctor):
+ # Figure out the name of our constructor for reporting purposes.
+ # For unnamed webidl constructors, identifier.name is "constructor" but
+ # the name JS sees is the interface name; for legacy factory functions
+ # identifier.name is the actual name.
+ ctorName = ctor.identifier.name
+ if ctorName == "constructor":
+ return descriptor.interface.identifier.name
+ return ctorName
+
+
+def GetLabelForErrorReporting(descriptor, idlObject, isConstructor):
+ """
+ descriptor is the descriptor for the interface involved
+
+ idlObject is the method (regular or static), attribute (regular or
+ static), or constructor (named or not) involved.
+
+ isConstructor is true if idlObject is a constructor and false otherwise.
+ """
+ if isConstructor:
+ return "%s constructor" % GetConstructorNameForReporting(descriptor, idlObject)
+
+ namePrefix = descriptor.interface.identifier.name
+ name = GetWebExposedName(idlObject, descriptor)
+ if " " in name:
+ # It's got a space already, so just space-separate.
+ return "%s %s" % (namePrefix, name)
+
+ return "%s.%s" % (namePrefix, name)
+
+
+class CGSpecializedMethod(CGAbstractStaticMethod):
+ """
+ A class for generating the C++ code for a specialized method that the JIT
+ can call with lower overhead.
+ """
+
+ def __init__(self, descriptor, method):
+ self.method = method
+ name = CppKeywords.checkMethodName(IDLToCIdentifier(method.identifier.name))
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "obj"),
+ Argument("void*", "void_self"),
+ Argument("const JSJitMethodCallArgs&", "args"),
+ ]
+ CGAbstractStaticMethod.__init__(
+ self, descriptor, name, "bool", args, canRunScript=True
+ )
+
+ def definition_body(self):
+ nativeName = CGSpecializedMethod.makeNativeName(self.descriptor, self.method)
+ call = CGMethodCall(
+ nativeName, self.method.isStatic(), self.descriptor, self.method
+ ).define()
+ prefix = ""
+ if self.method.getExtendedAttribute("CrossOriginCallable"):
+ for signature in self.method.signatures():
+ # non-undefined signatures would require us to deal with remote proxies for the
+ # return value here.
+ if not signature[0].isUndefined():
+ raise TypeError(
+ "We don't support a method marked as CrossOriginCallable "
+ "with non-undefined return type"
+ )
+ prototypeID, _ = PrototypeIDAndDepth(self.descriptor)
+ prefix = fill(
+ """
+ // CrossOriginThisPolicy::UnwrapThisObject stores a ${nativeType}::RemoteProxy in void_self
+ // if obj is a proxy with a RemoteObjectProxy handler for the right type, or else it stores
+ // a ${nativeType}. If we get here from the JIT (without going through UnwrapThisObject) we
+ // know void_self contains a ${nativeType}; we don't have special cases in the JIT to deal
+ // with remote object proxies.
+ if (IsRemoteObjectProxy(obj, ${prototypeID})) {
+ auto* self = static_cast<${nativeType}::RemoteProxy*>(void_self);
+ $*{call}
+ }
+ """,
+ prototypeID=prototypeID,
+ nativeType=self.descriptor.nativeType,
+ call=call,
+ )
+ return prefix + fill(
+ """
+ auto* self = static_cast<${nativeType}*>(void_self);
+ $*{call}
+ """,
+ nativeType=self.descriptor.nativeType,
+ call=call,
+ )
+
+ def auto_profiler_label(self):
+ interface_name = self.descriptor.interface.identifier.name
+ method_name = self.method.identifier.name
+ return fill(
+ """
+ AUTO_PROFILER_LABEL_DYNAMIC_FAST(
+ "${interface_name}", "${method_name}", DOM, cx,
+ uint32_t(js::ProfilingStackFrame::Flags::STRING_TEMPLATE_METHOD) |
+ uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS));
+ """,
+ interface_name=interface_name,
+ method_name=method_name,
+ )
+
+ @staticmethod
+ def should_have_method_description(descriptor, idlMethod):
+ """
+ Returns whether the given IDL method (static, non-static, constructor)
+ should have a method description declaration, for use in error
+ reporting.
+ """
+ # If a method has overloads, it needs a method description, because it
+ # can throw MSG_INVALID_OVERLOAD_ARGCOUNT at the very least.
+ if len(idlMethod.signatures()) != 1:
+ return True
+
+ # Methods with only one signature need a method description if one of
+ # their args needs it.
+ sig = idlMethod.signatures()[0]
+ args = sig[1]
+ return any(
+ idlTypeNeedsCallContext(
+ arg.type,
+ descriptor,
+ allowTreatNonCallableAsNull=arg.allowTreatNonCallableAsNull(),
+ )
+ for arg in args
+ )
+
+ @staticmethod
+ def error_reporting_label_helper(descriptor, idlMethod, isConstructor):
+ """
+ Returns the method description to use for error reporting for the given
+ IDL method. Used to implement common error_reporting_label() functions
+ across different classes.
+ """
+ if not CGSpecializedMethod.should_have_method_description(
+ descriptor, idlMethod
+ ):
+ return None
+ return GetLabelForErrorReporting(descriptor, idlMethod, isConstructor)
+
+ def error_reporting_label(self):
+ return CGSpecializedMethod.error_reporting_label_helper(
+ self.descriptor, self.method, isConstructor=False
+ )
+
+ @staticmethod
+ def makeNativeName(descriptor, method):
+ if method.underlyingAttr:
+ return CGSpecializedGetter.makeNativeName(descriptor, method.underlyingAttr)
+ name = method.identifier.name
+ return MakeNativeName(descriptor.binaryNameFor(name, method.isStatic()))
+
+
+class CGMethodPromiseWrapper(CGAbstractStaticMethod):
+ """
+ A class for generating a wrapper around another method that will
+ convert exceptions to promises.
+ """
+
+ def __init__(self, descriptor, methodToWrap):
+ self.method = methodToWrap
+ name = self.makeName(methodToWrap.name)
+ args = list(methodToWrap.args)
+ CGAbstractStaticMethod.__init__(
+ self, descriptor, name, "bool", args, canRunScript=True
+ )
+
+ def definition_body(self):
+ return fill(
+ """
+ bool ok = ${methodName}(${args});
+ if (ok) {
+ return true;
+ }
+ return ConvertExceptionToPromise(cx, args.rval());
+ """,
+ methodName=self.method.name,
+ args=", ".join(arg.name for arg in self.args),
+ )
+
+ @staticmethod
+ def makeName(methodName):
+ return methodName + "_promiseWrapper"
+
+
+class CGDefaultToJSONMethod(CGSpecializedMethod):
+ def __init__(self, descriptor, method):
+ assert method.isDefaultToJSON()
+ CGSpecializedMethod.__init__(self, descriptor, method)
+
+ def definition_body(self):
+ ret = fill(
+ """
+ auto* self = static_cast<${nativeType}*>(void_self);
+ JS::Rooted<JSObject*> result(cx, JS_NewPlainObject(cx));
+ if (!result) {
+ return false;
+ }
+ """,
+ nativeType=self.descriptor.nativeType,
+ )
+
+ jsonDescriptors = [self.descriptor]
+ interface = self.descriptor.interface.parent
+ while interface:
+ descriptor = self.descriptor.getDescriptor(interface.identifier.name)
+ if descriptor.hasDefaultToJSON:
+ jsonDescriptors.append(descriptor)
+ interface = interface.parent
+
+ # Iterate the array in reverse: oldest ancestor first
+ for descriptor in jsonDescriptors[::-1]:
+ ret += fill(
+ """
+ if (!${parentclass}::CollectJSONAttributes(cx, obj, MOZ_KnownLive(self), result)) {
+ return false;
+ }
+ """,
+ parentclass=toBindingNamespace(descriptor.name),
+ )
+ ret += "args.rval().setObject(*result);\n" "return true;\n"
+ return ret
+
+
+class CGLegacyCallHook(CGAbstractBindingMethod):
+ """
+ Call hook for our object
+ """
+
+ def __init__(self, descriptor):
+ self._legacycaller = descriptor.operations["LegacyCaller"]
+ # Our "self" is actually the callee in this case, not the thisval.
+ CGAbstractBindingMethod.__init__(
+ self,
+ descriptor,
+ LEGACYCALLER_HOOK_NAME,
+ JSNativeArguments(),
+ getThisObj="&args.callee()",
+ )
+
+ def define(self):
+ if not self._legacycaller:
+ return ""
+ return CGAbstractBindingMethod.define(self)
+
+ def generate_code(self):
+ name = self._legacycaller.identifier.name
+ nativeName = MakeNativeName(self.descriptor.binaryNameFor(name, False))
+ return CGMethodCall(nativeName, False, self.descriptor, self._legacycaller)
+
+ def error_reporting_label(self):
+ # Should act like methods.
+ return CGSpecializedMethod.error_reporting_label_helper(
+ self.descriptor, self._legacycaller, isConstructor=False
+ )
+
+
+class CGResolveHook(CGAbstractClassHook):
+ """
+ Resolve hook for objects that have the NeedResolve extended attribute.
+ """
+
+ def __init__(self, descriptor):
+ assert descriptor.interface.getExtendedAttribute("NeedResolve")
+
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "obj"),
+ Argument("JS::Handle<jsid>", "id"),
+ Argument("bool*", "resolvedp"),
+ ]
+ CGAbstractClassHook.__init__(self, descriptor, RESOLVE_HOOK_NAME, "bool", args)
+
+ def generate_code(self):
+ return dedent(
+ """
+ JS::Rooted<mozilla::Maybe<JS::PropertyDescriptor>> desc(cx);
+ if (!self->DoResolve(cx, obj, id, &desc)) {
+ return false;
+ }
+ if (desc.isNothing()) {
+ return true;
+ }
+ // If desc.value() is undefined, then the DoResolve call
+ // has already defined it on the object. Don't try to also
+ // define it.
+ MOZ_ASSERT(desc->isDataDescriptor());
+ if (!desc->value().isUndefined()) {
+ JS::Rooted<JS::PropertyDescriptor> defineDesc(cx, *desc);
+ defineDesc.setResolving(true);
+ if (!JS_DefinePropertyById(cx, obj, id, defineDesc)) {
+ return false;
+ }
+ }
+ *resolvedp = true;
+ return true;
+ """
+ )
+
+ def definition_body(self):
+ if self.descriptor.isGlobal():
+ # Resolve standard classes
+ prefix = dedent(
+ """
+ if (!ResolveGlobal(cx, obj, id, resolvedp)) {
+ return false;
+ }
+ if (*resolvedp) {
+ return true;
+ }
+
+ """
+ )
+ else:
+ prefix = ""
+ return prefix + CGAbstractClassHook.definition_body(self)
+
+
+class CGMayResolveHook(CGAbstractStaticMethod):
+ """
+ Resolve hook for objects that have the NeedResolve extended attribute.
+ """
+
+ def __init__(self, descriptor):
+ assert descriptor.interface.getExtendedAttribute("NeedResolve")
+
+ args = [
+ Argument("const JSAtomState&", "names"),
+ Argument("jsid", "id"),
+ Argument("JSObject*", "maybeObj"),
+ ]
+ CGAbstractStaticMethod.__init__(
+ self, descriptor, MAY_RESOLVE_HOOK_NAME, "bool", args
+ )
+
+ def definition_body(self):
+ if self.descriptor.isGlobal():
+ # Check whether this would resolve as a standard class.
+ prefix = dedent(
+ """
+ if (MayResolveGlobal(names, id, maybeObj)) {
+ return true;
+ }
+
+ """
+ )
+ else:
+ prefix = ""
+ return prefix + "return %s::MayResolve(id);\n" % self.descriptor.nativeType
+
+
+class CGEnumerateHook(CGAbstractBindingMethod):
+ """
+ Enumerate hook for objects with custom hooks.
+ """
+
+ def __init__(self, descriptor):
+ assert descriptor.interface.getExtendedAttribute("NeedResolve")
+
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "obj"),
+ Argument("JS::MutableHandleVector<jsid>", "properties"),
+ Argument("bool", "enumerableOnly"),
+ ]
+ # Our "self" is actually the "obj" argument in this case, not the thisval.
+ CGAbstractBindingMethod.__init__(
+ self, descriptor, NEW_ENUMERATE_HOOK_NAME, args, getThisObj="", callArgs=""
+ )
+
+ def generate_code(self):
+ return CGGeneric(
+ dedent(
+ """
+ FastErrorResult rv;
+ self->GetOwnPropertyNames(cx, properties, enumerableOnly, rv);
+ if (rv.MaybeSetPendingException(cx)) {
+ return false;
+ }
+ return true;
+ """
+ )
+ )
+
+ def definition_body(self):
+ if self.descriptor.isGlobal():
+ # Enumerate standard classes
+ prefix = dedent(
+ """
+ if (!EnumerateGlobal(cx, obj, properties, enumerableOnly)) {
+ return false;
+ }
+
+ """
+ )
+ else:
+ prefix = ""
+ return prefix + CGAbstractBindingMethod.definition_body(self)
+
+
+class CppKeywords:
+ """
+ A class for checking if method names declared in webidl
+ are not in conflict with C++ keywords.
+ """
+
+ keywords = frozenset(
+ [
+ "alignas",
+ "alignof",
+ "and",
+ "and_eq",
+ "asm",
+ "assert",
+ "auto",
+ "bitand",
+ "bitor",
+ "bool",
+ "break",
+ "case",
+ "catch",
+ "char",
+ "char16_t",
+ "char32_t",
+ "class",
+ "compl",
+ "const",
+ "constexpr",
+ "const_cast",
+ "continue",
+ "decltype",
+ "default",
+ "delete",
+ "do",
+ "double",
+ "dynamic_cast",
+ "else",
+ "enum",
+ "explicit",
+ "export",
+ "extern",
+ "false",
+ "final",
+ "float",
+ "for",
+ "friend",
+ "goto",
+ "if",
+ "inline",
+ "int",
+ "long",
+ "mutable",
+ "namespace",
+ "new",
+ "noexcept",
+ "not",
+ "not_eq",
+ "nullptr",
+ "operator",
+ "or",
+ "or_eq",
+ "override",
+ "private",
+ "protected",
+ "public",
+ "register",
+ "reinterpret_cast",
+ "return",
+ "short",
+ "signed",
+ "sizeof",
+ "static",
+ "static_assert",
+ "static_cast",
+ "struct",
+ "switch",
+ "template",
+ "this",
+ "thread_local",
+ "throw",
+ "true",
+ "try",
+ "typedef",
+ "typeid",
+ "typename",
+ "union",
+ "unsigned",
+ "using",
+ "virtual",
+ "void",
+ "volatile",
+ "wchar_t",
+ "while",
+ "xor",
+ "xor_eq",
+ ]
+ )
+
+ @staticmethod
+ def checkMethodName(name):
+ # Double '_' because 'assert' and '_assert' cannot be used in MS2013 compiler.
+ # Bug 964892 and bug 963560.
+ if name in CppKeywords.keywords:
+ name = "_" + name + "_"
+ return name
+
+
+class CGStaticMethod(CGAbstractStaticBindingMethod):
+ """
+ A class for generating the C++ code for an IDL static method.
+ """
+
+ def __init__(self, descriptor, method):
+ self.method = method
+ name = CppKeywords.checkMethodName(IDLToCIdentifier(method.identifier.name))
+ CGAbstractStaticBindingMethod.__init__(self, descriptor, name)
+
+ def generate_code(self):
+ nativeName = CGSpecializedMethod.makeNativeName(self.descriptor, self.method)
+ return CGMethodCall(nativeName, True, self.descriptor, self.method)
+
+ def auto_profiler_label(self):
+ interface_name = self.descriptor.interface.identifier.name
+ method_name = self.method.identifier.name
+ return fill(
+ """
+ AUTO_PROFILER_LABEL_DYNAMIC_FAST(
+ "${interface_name}", "${method_name}", DOM, cx,
+ uint32_t(js::ProfilingStackFrame::Flags::STRING_TEMPLATE_METHOD) |
+ uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS));
+ """,
+ interface_name=interface_name,
+ method_name=method_name,
+ )
+
+ def error_reporting_label(self):
+ return CGSpecializedMethod.error_reporting_label_helper(
+ self.descriptor, self.method, isConstructor=False
+ )
+
+
+class CGSpecializedGetter(CGAbstractStaticMethod):
+ """
+ A class for generating the code for a specialized attribute getter
+ that the JIT can call with lower overhead.
+ """
+
+ def __init__(self, descriptor, attr):
+ self.attr = attr
+ name = "get_" + IDLToCIdentifier(attr.identifier.name)
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "obj"),
+ Argument("void*", "void_self"),
+ Argument("JSJitGetterCallArgs", "args"),
+ ]
+ # StoreInSlot attributes have their getters called from Wrap(). We
+ # really hope they can't run script, and don't want to annotate Wrap()
+ # methods as doing that anyway, so let's not annotate them as
+ # MOZ_CAN_RUN_SCRIPT.
+ CGAbstractStaticMethod.__init__(
+ self,
+ descriptor,
+ name,
+ "bool",
+ args,
+ canRunScript=not attr.getExtendedAttribute("StoreInSlot"),
+ )
+
+ def definition_body(self):
+ prefix = fill(
+ """
+ auto* self = static_cast<${nativeType}*>(void_self);
+ """,
+ nativeType=self.descriptor.nativeType,
+ )
+
+ if self.attr.isMaplikeOrSetlikeAttr():
+ assert not self.attr.getExtendedAttribute("CrossOriginReadable")
+ # 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 prefix + getMaplikeOrSetlikeSizeGetterBody(
+ self.descriptor, self.attr
+ )
+
+ if self.attr.type.isObservableArray():
+ assert not self.attr.getExtendedAttribute("CrossOriginReadable")
+ # If the attribute is observableArray, due to having to unpack the
+ # backing object from the slot, this requires its own generator.
+ return prefix + getObservableArrayGetterBody(self.descriptor, self.attr)
+
+ nativeName = CGSpecializedGetter.makeNativeName(self.descriptor, self.attr)
+ type = self.attr.type
+ if self.attr.getExtendedAttribute("CrossOriginReadable"):
+ remoteType = type
+ extendedAttributes = self.descriptor.getExtendedAttributes(
+ self.attr, getter=True
+ )
+ if (
+ remoteType.isGeckoInterface()
+ and not remoteType.unroll().inner.isExternal()
+ and remoteType.unroll().inner.getExtendedAttribute("ChromeOnly") is None
+ ):
+ # We'll use a JSObject. It might make more sense to use remoteType's
+ # RemoteProxy, but it's not easy to construct a type for that from here.
+ remoteType = BuiltinTypes[IDLBuiltinType.Types.object]
+ if "needsErrorResult" not in extendedAttributes:
+ extendedAttributes.append("needsErrorResult")
+ prototypeID, _ = PrototypeIDAndDepth(self.descriptor)
+ prefix = (
+ fill(
+ """
+ if (IsRemoteObjectProxy(obj, ${prototypeID})) {
+ ${nativeType}::RemoteProxy* self = static_cast<${nativeType}::RemoteProxy*>(void_self);
+ $*{call}
+ }
+ """,
+ prototypeID=prototypeID,
+ nativeType=self.descriptor.nativeType,
+ call=CGGetterCall(
+ remoteType,
+ nativeName,
+ self.descriptor,
+ self.attr,
+ dontSetSlot=True,
+ extendedAttributes=extendedAttributes,
+ ).define(),
+ )
+ + prefix
+ )
+
+ if self.attr.slotIndices is not None:
+ # We're going to store this return value in a slot on some object,
+ # to cache it. The question is, which object? For dictionary and
+ # sequence return values, we want to use a slot on the Xray expando
+ # if we're called via Xrays, and a slot on our reflector otherwise.
+ # On the other hand, when dealing with some interfacce types
+ # (e.g. window.document) we want to avoid calling the getter more
+ # than once. In the case of window.document, it's because the
+ # getter can start returning null, which would get hidden in the
+ # non-Xray case by the fact that it's [StoreOnSlot], so the cached
+ # version is always around.
+ #
+ # The upshot is that we use the reflector slot for any getter whose
+ # type is a gecko interface, whether we're called via Xrays or not.
+ # Since [Cached] and [StoreInSlot] cannot be used with "NewObject",
+ # we know that in the interface type case the returned object is
+ # wrappercached. So creating Xrays to it is reasonable.
+ if mayUseXrayExpandoSlots(self.descriptor, self.attr):
+ prefix += fill(
+ """
+ // Have to either root across the getter call or reget after.
+ bool isXray;
+ JS::Rooted<JSObject*> slotStorage(cx, GetCachedSlotStorageObject(cx, obj, &isXray));
+ if (!slotStorage) {
+ return false;
+ }
+ const size_t slotIndex = isXray ? ${xraySlotIndex} : ${slotIndex};
+ """,
+ xraySlotIndex=memberXrayExpandoReservedSlot(
+ self.attr, self.descriptor
+ ),
+ slotIndex=memberReservedSlot(self.attr, self.descriptor),
+ )
+ else:
+ prefix += fill(
+ """
+ // Have to either root across the getter call or reget after.
+ JS::Rooted<JSObject*> slotStorage(cx, js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false));
+ MOZ_ASSERT(IsDOMObject(slotStorage));
+ const size_t slotIndex = ${slotIndex};
+ """,
+ slotIndex=memberReservedSlot(self.attr, self.descriptor),
+ )
+
+ prefix += fill(
+ """
+ MOZ_ASSERT(JSCLASS_RESERVED_SLOTS(JS::GetClass(slotStorage)) > slotIndex);
+ {
+ // Scope for cachedVal
+ JS::Value cachedVal = JS::GetReservedSlot(slotStorage, slotIndex);
+ if (!cachedVal.isUndefined()) {
+ args.rval().set(cachedVal);
+ // The cached value is in the compartment of slotStorage,
+ // so wrap into the caller compartment as needed.
+ return ${maybeWrap}(cx, args.rval());
+ }
+ }
+
+ """,
+ maybeWrap=getMaybeWrapValueFuncForType(self.attr.type),
+ )
+
+ return (
+ prefix + CGGetterCall(type, nativeName, self.descriptor, self.attr).define()
+ )
+
+ def auto_profiler_label(self):
+ interface_name = self.descriptor.interface.identifier.name
+ attr_name = self.attr.identifier.name
+ return fill(
+ """
+ AUTO_PROFILER_LABEL_DYNAMIC_FAST(
+ "${interface_name}", "${attr_name}", DOM, cx,
+ uint32_t(js::ProfilingStackFrame::Flags::STRING_TEMPLATE_GETTER) |
+ uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS));
+ """,
+ interface_name=interface_name,
+ attr_name=attr_name,
+ )
+
+ def error_reporting_label(self):
+ # Getters never need a BindingCallContext.
+ return None
+
+ @staticmethod
+ def makeNativeName(descriptor, attr):
+ name = attr.identifier.name
+ nativeName = MakeNativeName(descriptor.binaryNameFor(name, attr.isStatic()))
+ _, resultOutParam, _, _, _ = getRetvalDeclarationForType(attr.type, descriptor)
+ extendedAttrs = descriptor.getExtendedAttributes(attr, getter=True)
+ canFail = "needsErrorResult" in extendedAttrs or "canOOM" in extendedAttrs
+ if resultOutParam or attr.type.nullable() or canFail:
+ nativeName = "Get" + nativeName
+ return nativeName
+
+
+class CGGetterPromiseWrapper(CGAbstractStaticMethod):
+ """
+ A class for generating a wrapper around another getter that will
+ convert exceptions to promises.
+ """
+
+ def __init__(self, descriptor, getterToWrap):
+ self.getter = getterToWrap
+ name = self.makeName(getterToWrap.name)
+ args = list(getterToWrap.args)
+ CGAbstractStaticMethod.__init__(
+ self, descriptor, name, "bool", args, canRunScript=True
+ )
+
+ def definition_body(self):
+ return fill(
+ """
+ bool ok = ${getterName}(${args});
+ if (ok) {
+ return true;
+ }
+ return ConvertExceptionToPromise(cx, args.rval());
+ """,
+ getterName=self.getter.name,
+ args=", ".join(arg.name for arg in self.args),
+ )
+
+ @staticmethod
+ def makeName(getterName):
+ return getterName + "_promiseWrapper"
+
+
+class CGStaticGetter(CGAbstractStaticBindingMethod):
+ """
+ A class for generating the C++ code for an IDL static attribute getter.
+ """
+
+ def __init__(self, descriptor, attr):
+ self.attr = attr
+ name = "get_" + IDLToCIdentifier(attr.identifier.name)
+ CGAbstractStaticBindingMethod.__init__(self, descriptor, name)
+
+ def generate_code(self):
+ nativeName = CGSpecializedGetter.makeNativeName(self.descriptor, self.attr)
+ return CGGetterCall(self.attr.type, nativeName, self.descriptor, self.attr)
+
+ def auto_profiler_label(self):
+ interface_name = self.descriptor.interface.identifier.name
+ attr_name = self.attr.identifier.name
+ return fill(
+ """
+ AUTO_PROFILER_LABEL_DYNAMIC_FAST(
+ "${interface_name}", "${attr_name}", DOM, cx,
+ uint32_t(js::ProfilingStackFrame::Flags::STRING_TEMPLATE_GETTER) |
+ uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS));
+ """,
+ interface_name=interface_name,
+ attr_name=attr_name,
+ )
+
+ def error_reporting_label(self):
+ # Getters never need a BindingCallContext.
+ return None
+
+
+class CGSpecializedSetter(CGAbstractStaticMethod):
+ """
+ A class for generating the code for a specialized attribute setter
+ that the JIT can call with lower overhead.
+ """
+
+ def __init__(self, descriptor, attr):
+ self.attr = attr
+ name = "set_" + IDLToCIdentifier(attr.identifier.name)
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "obj"),
+ Argument("void*", "void_self"),
+ Argument("JSJitSetterCallArgs", "args"),
+ ]
+ CGAbstractStaticMethod.__init__(
+ self, descriptor, name, "bool", args, canRunScript=True
+ )
+
+ def definition_body(self):
+ nativeName = CGSpecializedSetter.makeNativeName(self.descriptor, self.attr)
+ type = self.attr.type
+ call = CGSetterCall(type, nativeName, self.descriptor, self.attr).define()
+ prefix = ""
+ if self.attr.getExtendedAttribute("CrossOriginWritable"):
+ if type.isGeckoInterface() and not type.unroll().inner.isExternal():
+ # a setter taking a Gecko interface would require us to deal with remote
+ # proxies for the value here.
+ raise TypeError(
+ "We don't support the setter of %s marked as "
+ "CrossOriginWritable because it takes a Gecko interface "
+ "as the value",
+ self.attr.identifier.name,
+ )
+ prototypeID, _ = PrototypeIDAndDepth(self.descriptor)
+ prefix = fill(
+ """
+ if (IsRemoteObjectProxy(obj, ${prototypeID})) {
+ auto* self = static_cast<${nativeType}::RemoteProxy*>(void_self);
+ $*{call}
+ }
+ """,
+ prototypeID=prototypeID,
+ nativeType=self.descriptor.nativeType,
+ call=call,
+ )
+
+ return prefix + fill(
+ """
+ auto* self = static_cast<${nativeType}*>(void_self);
+ $*{call}
+ """,
+ nativeType=self.descriptor.nativeType,
+ call=call,
+ )
+
+ def auto_profiler_label(self):
+ interface_name = self.descriptor.interface.identifier.name
+ attr_name = self.attr.identifier.name
+ return fill(
+ """
+ AUTO_PROFILER_LABEL_DYNAMIC_FAST(
+ "${interface_name}", "${attr_name}", DOM, cx,
+ uint32_t(js::ProfilingStackFrame::Flags::STRING_TEMPLATE_SETTER) |
+ uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS));
+ """,
+ interface_name=interface_name,
+ attr_name=attr_name,
+ )
+
+ @staticmethod
+ def error_reporting_label_helper(descriptor, attr):
+ # Setters need a BindingCallContext if the type of the attribute needs
+ # one.
+ if not idlTypeNeedsCallContext(
+ attr.type, descriptor, allowTreatNonCallableAsNull=True
+ ):
+ return None
+ return (
+ GetLabelForErrorReporting(descriptor, attr, isConstructor=False) + " setter"
+ )
+
+ def error_reporting_label(self):
+ return CGSpecializedSetter.error_reporting_label_helper(
+ self.descriptor, self.attr
+ )
+
+ @staticmethod
+ def makeNativeName(descriptor, attr):
+ name = attr.identifier.name
+ return "Set" + MakeNativeName(descriptor.binaryNameFor(name, attr.isStatic()))
+
+
+class CGStaticSetter(CGAbstractStaticBindingMethod):
+ """
+ A class for generating the C++ code for an IDL static attribute setter.
+ """
+
+ def __init__(self, descriptor, attr):
+ self.attr = attr
+ name = "set_" + IDLToCIdentifier(attr.identifier.name)
+ CGAbstractStaticBindingMethod.__init__(self, descriptor, name)
+
+ def generate_code(self):
+ nativeName = CGSpecializedSetter.makeNativeName(self.descriptor, self.attr)
+ checkForArg = CGGeneric(
+ fill(
+ """
+ if (!args.requireAtLeast(cx, "${name} setter", 1)) {
+ return false;
+ }
+ """,
+ name=self.attr.identifier.name,
+ )
+ )
+ call = CGSetterCall(self.attr.type, nativeName, self.descriptor, self.attr)
+ return CGList([checkForArg, call])
+
+ def auto_profiler_label(self):
+ interface_name = self.descriptor.interface.identifier.name
+ attr_name = self.attr.identifier.name
+ return fill(
+ """
+ AUTO_PROFILER_LABEL_DYNAMIC_FAST(
+ "${interface_name}", "${attr_name}", DOM, cx,
+ uint32_t(js::ProfilingStackFrame::Flags::STRING_TEMPLATE_SETTER) |
+ uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS));
+ """,
+ interface_name=interface_name,
+ attr_name=attr_name,
+ )
+
+ def error_reporting_label(self):
+ return CGSpecializedSetter.error_reporting_label_helper(
+ self.descriptor, self.attr
+ )
+
+
+class CGSpecializedForwardingSetter(CGSpecializedSetter):
+ """
+ A class for generating the code for a specialized attribute setter with
+ PutForwards that the JIT can call with lower overhead.
+ """
+
+ def __init__(self, descriptor, attr):
+ CGSpecializedSetter.__init__(self, descriptor, attr)
+
+ def definition_body(self):
+ attrName = self.attr.identifier.name
+ forwardToAttrName = self.attr.getExtendedAttribute("PutForwards")[0]
+ # JS_GetProperty and JS_SetProperty can only deal with ASCII
+ assert all(ord(c) < 128 for c in attrName)
+ assert all(ord(c) < 128 for c in forwardToAttrName)
+ return fill(
+ """
+ JS::Rooted<JS::Value> v(cx);
+ if (!JS_GetProperty(cx, obj, "${attr}", &v)) {
+ return false;
+ }
+
+ if (!v.isObject()) {
+ return cx.ThrowErrorMessage<MSG_NOT_OBJECT>("${interface}.${attr}");
+ }
+
+ JS::Rooted<JSObject*> targetObj(cx, &v.toObject());
+ return JS_SetProperty(cx, targetObj, "${forwardToAttrName}", args[0]);
+ """,
+ attr=attrName,
+ interface=self.descriptor.interface.identifier.name,
+ forwardToAttrName=forwardToAttrName,
+ )
+
+ def error_reporting_label(self):
+ # We always need to be able to throw.
+ return (
+ GetLabelForErrorReporting(self.descriptor, self.attr, isConstructor=False)
+ + " setter"
+ )
+
+
+class CGSpecializedReplaceableSetter(CGSpecializedSetter):
+ """
+ A class for generating the code for a specialized attribute setter with
+ Replaceable that the JIT can call with lower overhead.
+ """
+
+ def __init__(self, descriptor, attr):
+ CGSpecializedSetter.__init__(self, descriptor, attr)
+
+ def definition_body(self):
+ attrName = self.attr.identifier.name
+ # JS_DefineProperty can only deal with ASCII
+ assert all(ord(c) < 128 for c in attrName)
+ return (
+ 'return JS_DefineProperty(cx, obj, "%s", args[0], JSPROP_ENUMERATE);\n'
+ % attrName
+ )
+
+ def error_reporting_label(self):
+ # We never throw directly.
+ return None
+
+
+class CGSpecializedLenientSetter(CGSpecializedSetter):
+ """
+ A class for generating the code for a specialized attribute setter with
+ LenientSetter that the JIT can call with lower overhead.
+ """
+
+ def __init__(self, descriptor, attr):
+ CGSpecializedSetter.__init__(self, descriptor, attr)
+
+ def definition_body(self):
+ attrName = self.attr.identifier.name
+ # JS_DefineProperty can only deal with ASCII
+ assert all(ord(c) < 128 for c in attrName)
+ return dedent(
+ """
+ DeprecationWarning(cx, obj, DeprecatedOperations::eLenientSetter);
+ return true;
+ """
+ )
+
+ def error_reporting_label(self):
+ # We never throw; that's the whole point.
+ return None
+
+
+def memberReturnsNewObject(member):
+ return member.getExtendedAttribute("NewObject") is not None
+
+
+class CGMemberJITInfo(CGThing):
+ """
+ A class for generating the JITInfo for a property that points to
+ our specialized getter and setter.
+ """
+
+ def __init__(self, descriptor, member):
+ self.member = member
+ self.descriptor = descriptor
+
+ def declare(self):
+ return ""
+
+ def defineJitInfo(
+ self,
+ infoName,
+ opName,
+ opType,
+ infallible,
+ movable,
+ eliminatable,
+ aliasSet,
+ alwaysInSlot,
+ lazilyInSlot,
+ slotIndex,
+ returnTypes,
+ args,
+ ):
+ """
+ aliasSet is a JSJitInfo::AliasSet value, without the "JSJitInfo::" bit.
+
+ args is None if we don't want to output argTypes for some
+ reason (e.g. we have overloads or we're not a method) and
+ otherwise an iterable of the arguments for this method.
+ """
+ assert (
+ not movable or aliasSet != "AliasEverything"
+ ) # Can't move write-aliasing things
+ assert (
+ not alwaysInSlot or movable
+ ) # Things always in slots had better be movable
+ assert (
+ not eliminatable or aliasSet != "AliasEverything"
+ ) # Can't eliminate write-aliasing things
+ assert (
+ not alwaysInSlot or eliminatable
+ ) # Things always in slots had better be eliminatable
+
+ def jitInfoInitializer(isTypedMethod):
+ initializer = fill(
+ """
+ {
+ { ${opName} },
+ { prototypes::id::${name} },
+ { PrototypeTraits<prototypes::id::${name}>::Depth },
+ JSJitInfo::${opType},
+ JSJitInfo::${aliasSet}, /* aliasSet. Not relevant for setters. */
+ ${returnType}, /* returnType. Not relevant for setters. */
+ ${isInfallible}, /* isInfallible. False in setters. */
+ ${isMovable}, /* isMovable. Not relevant for setters. */
+ ${isEliminatable}, /* isEliminatable. Not relevant for setters. */
+ ${isAlwaysInSlot}, /* isAlwaysInSlot. Only relevant for getters. */
+ ${isLazilyCachedInSlot}, /* isLazilyCachedInSlot. Only relevant for getters. */
+ ${isTypedMethod}, /* isTypedMethod. Only relevant for methods. */
+ ${slotIndex} /* Reserved slot index, if we're stored in a slot, else 0. */
+ }
+ """,
+ opName=opName,
+ name=self.descriptor.name,
+ opType=opType,
+ aliasSet=aliasSet,
+ returnType=functools.reduce(
+ CGMemberJITInfo.getSingleReturnType, returnTypes, ""
+ ),
+ isInfallible=toStringBool(infallible),
+ isMovable=toStringBool(movable),
+ isEliminatable=toStringBool(eliminatable),
+ isAlwaysInSlot=toStringBool(alwaysInSlot),
+ isLazilyCachedInSlot=toStringBool(lazilyInSlot),
+ isTypedMethod=toStringBool(isTypedMethod),
+ slotIndex=slotIndex,
+ )
+ return initializer.rstrip()
+
+ slotAssert = fill(
+ """
+ static_assert(${slotIndex} <= JSJitInfo::maxSlotIndex, "We won't fit");
+ static_assert(${slotIndex} < ${classReservedSlots}, "There is no slot for us");
+ """,
+ slotIndex=slotIndex,
+ classReservedSlots=INSTANCE_RESERVED_SLOTS
+ + self.descriptor.interface.totalMembersInSlots,
+ )
+ if args is not None:
+ argTypes = "%s_argTypes" % infoName
+ args = [CGMemberJITInfo.getJSArgType(arg.type) for arg in args]
+ args.append("JSJitInfo::ArgTypeListEnd")
+ argTypesDecl = "static const JSJitInfo::ArgType %s[] = { %s };\n" % (
+ argTypes,
+ ", ".join(args),
+ )
+ return fill(
+ """
+ $*{argTypesDecl}
+ static const JSTypedMethodJitInfo ${infoName} = {
+ ${jitInfo},
+ ${argTypes}
+ };
+ $*{slotAssert}
+ """,
+ argTypesDecl=argTypesDecl,
+ infoName=infoName,
+ jitInfo=indent(jitInfoInitializer(True)),
+ argTypes=argTypes,
+ slotAssert=slotAssert,
+ )
+
+ # Unexposed things are meant to be used from C++ directly, so we make
+ # their jitinfo non-static. That way C++ can get at it.
+ if self.member.getExtendedAttribute("Unexposed"):
+ storageClass = "extern"
+ else:
+ storageClass = "static"
+
+ return fill(
+ """
+ ${storageClass} const JSJitInfo ${infoName} = ${jitInfo};
+ $*{slotAssert}
+ """,
+ storageClass=storageClass,
+ infoName=infoName,
+ jitInfo=jitInfoInitializer(False),
+ slotAssert=slotAssert,
+ )
+
+ def define(self):
+ if self.member.isAttr():
+ getterinfo = "%s_getterinfo" % IDLToCIdentifier(self.member.identifier.name)
+ name = IDLToCIdentifier(self.member.identifier.name)
+ if self.member.type.isPromise():
+ name = CGGetterPromiseWrapper.makeName(name)
+ getter = "get_%s" % name
+ extendedAttrs = self.descriptor.getExtendedAttributes(
+ self.member, getter=True
+ )
+ getterinfal = "needsErrorResult" not in extendedAttrs
+
+ # At this point getterinfal is true if our getter either can't throw
+ # at all, or can only throw OOM. In both cases, it's safe to move,
+ # or dead-code-eliminate, the getter, because throwing OOM is not
+ # semantically meaningful, so code can't rely on it happening. Note
+ # that this makes the behavior consistent for OOM thrown from the
+ # getter itself and OOM thrown from the to-JS conversion of the
+ # return value (see the "canOOM" and "infallibleForMember" checks
+ # below).
+ movable = self.mayBeMovable() and getterinfal
+ eliminatable = self.mayBeEliminatable() and getterinfal
+ aliasSet = self.aliasSet()
+
+ # Now we have to set getterinfal to whether we can _really_ ever
+ # throw, from the point of view of the JS engine.
+ getterinfal = (
+ getterinfal
+ and "canOOM" not in extendedAttrs
+ and infallibleForMember(self.member, self.member.type, self.descriptor)
+ )
+ isAlwaysInSlot = self.member.getExtendedAttribute("StoreInSlot")
+
+ if self.member.slotIndices is not None:
+ assert (
+ isAlwaysInSlot
+ or self.member.getExtendedAttribute("Cached")
+ or self.member.type.isObservableArray()
+ )
+ isLazilyCachedInSlot = not isAlwaysInSlot
+ slotIndex = memberReservedSlot(self.member, self.descriptor)
+ # We'll statically assert that this is not too big in
+ # CGUpdateMemberSlotsMethod, in the case when
+ # isAlwaysInSlot is true.
+ else:
+ isLazilyCachedInSlot = False
+ slotIndex = "0"
+
+ result = self.defineJitInfo(
+ getterinfo,
+ getter,
+ "Getter",
+ getterinfal,
+ movable,
+ eliminatable,
+ aliasSet,
+ isAlwaysInSlot,
+ isLazilyCachedInSlot,
+ slotIndex,
+ [self.member.type],
+ None,
+ )
+ if (
+ not self.member.readonly
+ or self.member.getExtendedAttribute("PutForwards") is not None
+ or self.member.getExtendedAttribute("Replaceable") is not None
+ or self.member.getExtendedAttribute("LegacyLenientSetter") is not None
+ ):
+ setterinfo = "%s_setterinfo" % IDLToCIdentifier(
+ self.member.identifier.name
+ )
+ # Actually a JSJitSetterOp, but JSJitGetterOp is first in the
+ # union.
+ setter = "(JSJitGetterOp)set_%s" % IDLToCIdentifier(
+ self.member.identifier.name
+ )
+ # Setters are always fallible, since they have to do a typed unwrap.
+ result += self.defineJitInfo(
+ setterinfo,
+ setter,
+ "Setter",
+ False,
+ False,
+ False,
+ "AliasEverything",
+ False,
+ False,
+ "0",
+ [BuiltinTypes[IDLBuiltinType.Types.undefined]],
+ None,
+ )
+ return result
+ if self.member.isMethod():
+ methodinfo = "%s_methodinfo" % IDLToCIdentifier(self.member.identifier.name)
+ name = CppKeywords.checkMethodName(
+ IDLToCIdentifier(self.member.identifier.name)
+ )
+ if self.member.returnsPromise():
+ name = CGMethodPromiseWrapper.makeName(name)
+ # Actually a JSJitMethodOp, but JSJitGetterOp is first in the union.
+ method = "(JSJitGetterOp)%s" % name
+
+ # Methods are infallible if they are infallible, have no arguments
+ # to unwrap, and have a return type that's infallible to wrap up for
+ # return.
+ sigs = self.member.signatures()
+ if len(sigs) != 1:
+ # Don't handle overloading. If there's more than one signature,
+ # one of them must take arguments.
+ methodInfal = False
+ args = None
+ movable = False
+ eliminatable = False
+ else:
+ sig = sigs[0]
+ # For methods that affect nothing, it's OK to set movable to our
+ # notion of infallible on the C++ side, without considering
+ # argument conversions, since argument conversions that can
+ # reliably throw would be effectful anyway and the jit doesn't
+ # move effectful things.
+ extendedAttrs = self.descriptor.getExtendedAttributes(self.member)
+ hasInfallibleImpl = "needsErrorResult" not in extendedAttrs
+ # At this point hasInfallibleImpl is true if our method either
+ # can't throw at all, or can only throw OOM. In both cases, it
+ # may be safe to move, or dead-code-eliminate, the method,
+ # because throwing OOM is not semantically meaningful, so code
+ # can't rely on it happening. Note that this makes the behavior
+ # consistent for OOM thrown from the method itself and OOM
+ # thrown from the to-JS conversion of the return value (see the
+ # "canOOM" and "infallibleForMember" checks below).
+ movable = self.mayBeMovable() and hasInfallibleImpl
+ eliminatable = self.mayBeEliminatable() and hasInfallibleImpl
+ # XXXbz can we move the smarts about fallibility due to arg
+ # conversions into the JIT, using our new args stuff?
+ if len(sig[1]) != 0 or not infallibleForMember(
+ self.member, sig[0], self.descriptor
+ ):
+ # We have arguments or our return-value boxing can fail
+ methodInfal = False
+ else:
+ methodInfal = hasInfallibleImpl and "canOOM" not in extendedAttrs
+ # For now, only bother to output args if we're side-effect-free.
+ if self.member.affects == "Nothing":
+ args = sig[1]
+ else:
+ args = None
+
+ aliasSet = self.aliasSet()
+ result = self.defineJitInfo(
+ methodinfo,
+ method,
+ "Method",
+ methodInfal,
+ movable,
+ eliminatable,
+ aliasSet,
+ False,
+ False,
+ "0",
+ [s[0] for s in sigs],
+ args,
+ )
+ return result
+ raise TypeError("Illegal member type to CGPropertyJITInfo")
+
+ def mayBeMovable(self):
+ """
+ Returns whether this attribute or method may be movable, just
+ based on Affects/DependsOn annotations.
+ """
+ affects = self.member.affects
+ dependsOn = self.member.dependsOn
+ assert affects in IDLInterfaceMember.AffectsValues
+ assert dependsOn in IDLInterfaceMember.DependsOnValues
+ # Things that are DependsOn=DeviceState are not movable, because we
+ # don't want them coalesced with each other or loop-hoisted, since
+ # their return value can change even if nothing is going on from our
+ # point of view.
+ return affects == "Nothing" and (
+ dependsOn != "Everything" and dependsOn != "DeviceState"
+ )
+
+ def mayBeEliminatable(self):
+ """
+ Returns whether this attribute or method may be eliminatable, just
+ based on Affects/DependsOn annotations.
+ """
+ # dependsOn shouldn't affect this decision at all, except in jitinfo we
+ # have no way to express "Depends on everything, affects nothing",
+ # because we only have three alias set values: AliasNone ("depends on
+ # nothing, affects nothing"), AliasDOMSets ("depends on DOM sets,
+ # affects nothing"), AliasEverything ("depends on everything, affects
+ # everything"). So the [Affects=Nothing, DependsOn=Everything] case
+ # gets encoded as AliasEverything and defineJitInfo asserts that if our
+ # alias state is AliasEverything then we're not eliminatable (because it
+ # thinks we might have side-effects at that point). Bug 1155796 is
+ # tracking possible solutions for this.
+ affects = self.member.affects
+ dependsOn = self.member.dependsOn
+ assert affects in IDLInterfaceMember.AffectsValues
+ assert dependsOn in IDLInterfaceMember.DependsOnValues
+ return affects == "Nothing" and dependsOn != "Everything"
+
+ def aliasSet(self):
+ """
+ Returns the alias set to store in the jitinfo. This may not be the
+ effective alias set the JIT uses, depending on whether we have enough
+ information about our args to allow the JIT to prove that effectful
+ argument conversions won't happen.
+ """
+ dependsOn = self.member.dependsOn
+ assert dependsOn in IDLInterfaceMember.DependsOnValues
+
+ if dependsOn == "Nothing" or dependsOn == "DeviceState":
+ assert self.member.affects == "Nothing"
+ return "AliasNone"
+
+ if dependsOn == "DOMState":
+ assert self.member.affects == "Nothing"
+ return "AliasDOMSets"
+
+ return "AliasEverything"
+
+ @staticmethod
+ def getJSReturnTypeTag(t):
+ if t.nullable():
+ # Sometimes it might return null, sometimes not
+ return "JSVAL_TYPE_UNKNOWN"
+ if t.isUndefined():
+ # No return, every time
+ return "JSVAL_TYPE_UNDEFINED"
+ if t.isSequence():
+ return "JSVAL_TYPE_OBJECT"
+ if t.isRecord():
+ return "JSVAL_TYPE_OBJECT"
+ if t.isPromise():
+ return "JSVAL_TYPE_OBJECT"
+ if t.isGeckoInterface():
+ return "JSVAL_TYPE_OBJECT"
+ if t.isString():
+ return "JSVAL_TYPE_STRING"
+ if t.isEnum():
+ return "JSVAL_TYPE_STRING"
+ if t.isCallback():
+ return "JSVAL_TYPE_OBJECT"
+ if t.isAny():
+ # The whole point is to return various stuff
+ return "JSVAL_TYPE_UNKNOWN"
+ if t.isObject():
+ return "JSVAL_TYPE_OBJECT"
+ if t.isSpiderMonkeyInterface():
+ return "JSVAL_TYPE_OBJECT"
+ if t.isUnion():
+ u = t.unroll()
+ if u.hasNullableType:
+ # Might be null or not
+ return "JSVAL_TYPE_UNKNOWN"
+ return functools.reduce(
+ CGMemberJITInfo.getSingleReturnType, u.flatMemberTypes, ""
+ )
+ if t.isDictionary():
+ return "JSVAL_TYPE_OBJECT"
+ if t.isObservableArray():
+ return "JSVAL_TYPE_OBJECT"
+ if not t.isPrimitive():
+ raise TypeError("No idea what type " + str(t) + " is.")
+ tag = t.tag()
+ if tag == IDLType.Tags.bool:
+ return "JSVAL_TYPE_BOOLEAN"
+ if tag in [
+ IDLType.Tags.int8,
+ IDLType.Tags.uint8,
+ IDLType.Tags.int16,
+ IDLType.Tags.uint16,
+ IDLType.Tags.int32,
+ ]:
+ return "JSVAL_TYPE_INT32"
+ if tag in [
+ IDLType.Tags.int64,
+ IDLType.Tags.uint64,
+ IDLType.Tags.unrestricted_float,
+ IDLType.Tags.float,
+ IDLType.Tags.unrestricted_double,
+ IDLType.Tags.double,
+ ]:
+ # These all use JS_NumberValue, which can return int or double.
+ # But TI treats "double" as meaning "int or double", so we're
+ # good to return JSVAL_TYPE_DOUBLE here.
+ return "JSVAL_TYPE_DOUBLE"
+ if tag != IDLType.Tags.uint32:
+ raise TypeError("No idea what type " + str(t) + " is.")
+ # uint32 is sometimes int and sometimes double.
+ return "JSVAL_TYPE_DOUBLE"
+
+ @staticmethod
+ def getSingleReturnType(existingType, t):
+ type = CGMemberJITInfo.getJSReturnTypeTag(t)
+ if existingType == "":
+ # First element of the list; just return its type
+ return type
+
+ if type == existingType:
+ return existingType
+ if (type == "JSVAL_TYPE_DOUBLE" and existingType == "JSVAL_TYPE_INT32") or (
+ existingType == "JSVAL_TYPE_DOUBLE" and type == "JSVAL_TYPE_INT32"
+ ):
+ # Promote INT32 to DOUBLE as needed
+ return "JSVAL_TYPE_DOUBLE"
+ # Different types
+ return "JSVAL_TYPE_UNKNOWN"
+
+ @staticmethod
+ def getJSArgType(t):
+ assert not t.isUndefined()
+ if t.nullable():
+ # Sometimes it might return null, sometimes not
+ return (
+ "JSJitInfo::ArgType(JSJitInfo::Null | %s)"
+ % CGMemberJITInfo.getJSArgType(t.inner)
+ )
+ if t.isSequence():
+ return "JSJitInfo::Object"
+ if t.isPromise():
+ return "JSJitInfo::Object"
+ if t.isGeckoInterface():
+ return "JSJitInfo::Object"
+ if t.isString():
+ return "JSJitInfo::String"
+ if t.isEnum():
+ return "JSJitInfo::String"
+ if t.isCallback():
+ return "JSJitInfo::Object"
+ if t.isAny():
+ # The whole point is to return various stuff
+ return "JSJitInfo::Any"
+ if t.isObject():
+ return "JSJitInfo::Object"
+ if t.isSpiderMonkeyInterface():
+ return "JSJitInfo::Object"
+ if t.isUnion():
+ u = t.unroll()
+ type = "JSJitInfo::Null" if u.hasNullableType else ""
+ return "JSJitInfo::ArgType(%s)" % functools.reduce(
+ CGMemberJITInfo.getSingleArgType, u.flatMemberTypes, type
+ )
+ if t.isDictionary():
+ return "JSJitInfo::Object"
+ if not t.isPrimitive():
+ raise TypeError("No idea what type " + str(t) + " is.")
+ tag = t.tag()
+ if tag == IDLType.Tags.bool:
+ return "JSJitInfo::Boolean"
+ if tag in [
+ IDLType.Tags.int8,
+ IDLType.Tags.uint8,
+ IDLType.Tags.int16,
+ IDLType.Tags.uint16,
+ IDLType.Tags.int32,
+ ]:
+ return "JSJitInfo::Integer"
+ if tag in [
+ IDLType.Tags.int64,
+ IDLType.Tags.uint64,
+ IDLType.Tags.unrestricted_float,
+ IDLType.Tags.float,
+ IDLType.Tags.unrestricted_double,
+ IDLType.Tags.double,
+ ]:
+ # These all use JS_NumberValue, which can return int or double.
+ # But TI treats "double" as meaning "int or double", so we're
+ # good to return JSVAL_TYPE_DOUBLE here.
+ return "JSJitInfo::Double"
+ if tag != IDLType.Tags.uint32:
+ raise TypeError("No idea what type " + str(t) + " is.")
+ # uint32 is sometimes int and sometimes double.
+ return "JSJitInfo::Double"
+
+ @staticmethod
+ def getSingleArgType(existingType, t):
+ type = CGMemberJITInfo.getJSArgType(t)
+ if existingType == "":
+ # First element of the list; just return its type
+ return type
+
+ if type == existingType:
+ return existingType
+ return "%s | %s" % (existingType, type)
+
+
+class CGStaticMethodJitinfo(CGGeneric):
+ """
+ A class for generating the JITInfo for a promise-returning static method.
+ """
+
+ def __init__(self, method):
+ CGGeneric.__init__(
+ self,
+ "\n"
+ "static const JSJitInfo %s_methodinfo = {\n"
+ " { (JSJitGetterOp)%s },\n"
+ " { prototypes::id::_ID_Count }, { 0 }, JSJitInfo::StaticMethod,\n"
+ " JSJitInfo::AliasEverything, JSVAL_TYPE_OBJECT, false, false,\n"
+ " false, false, 0\n"
+ "};\n"
+ % (
+ IDLToCIdentifier(method.identifier.name),
+ CppKeywords.checkMethodName(IDLToCIdentifier(method.identifier.name)),
+ ),
+ )
+
+
+def getEnumValueName(value):
+ # Some enum values can be empty strings. Others might have weird
+ # characters in them. Deal with the former by returning "_empty",
+ # deal with possible name collisions from that by throwing if the
+ # enum value is actually "_empty", and throw on any value
+ # containing non-ASCII chars for now. Replace all chars other than
+ # [0-9A-Za-z_] with '_'.
+ if re.match("[^\x20-\x7E]", value):
+ raise SyntaxError('Enum value "' + value + '" contains non-ASCII characters')
+ if re.match("^[0-9]", value):
+ value = "_" + value
+ value = re.sub(r"[^0-9A-Za-z_]", "_", value)
+ if re.match("^_[A-Z]|__", value):
+ raise SyntaxError('Enum value "' + value + '" is reserved by the C++ spec')
+ if value == "_empty":
+ raise SyntaxError('"_empty" is not an IDL enum value we support yet')
+ if value == "":
+ return "_empty"
+ nativeName = MakeNativeName(value)
+ if nativeName == "EndGuard_":
+ raise SyntaxError(
+ 'Enum value "' + value + '" cannot be used because it'
+ " collides with our internal EndGuard_ value. Please"
+ " rename our internal EndGuard_ to something else"
+ )
+ return nativeName
+
+
+class CGEnumToJSValue(CGAbstractMethod):
+ def __init__(self, enum):
+ enumType = enum.identifier.name
+ self.stringsArray = enumType + "Values::" + ENUM_ENTRY_VARIABLE_NAME
+ CGAbstractMethod.__init__(
+ self,
+ None,
+ "ToJSValue",
+ "bool",
+ [
+ Argument("JSContext*", "aCx"),
+ Argument(enumType, "aArgument"),
+ Argument("JS::MutableHandle<JS::Value>", "aValue"),
+ ],
+ )
+
+ def definition_body(self):
+ return fill(
+ """
+ MOZ_ASSERT(uint32_t(aArgument) < ArrayLength(${strings}));
+ JSString* resultStr =
+ JS_NewStringCopyN(aCx, ${strings}[uint32_t(aArgument)].value,
+ ${strings}[uint32_t(aArgument)].length);
+ if (!resultStr) {
+ return false;
+ }
+ aValue.setString(resultStr);
+ return true;
+ """,
+ strings=self.stringsArray,
+ )
+
+
+class CGEnum(CGThing):
+ def __init__(self, enum):
+ CGThing.__init__(self)
+ self.enum = enum
+ entryDecl = fill(
+ """
+ extern const EnumEntry ${entry_array}[${entry_count}];
+
+ static constexpr size_t Count = ${real_entry_count};
+
+ // Our "${entry_array}" contains an extra entry with a null string.
+ static_assert(mozilla::ArrayLength(${entry_array}) - 1 == Count,
+ "Mismatch between enum strings and enum count");
+
+ static_assert(static_cast<size_t>(${name}::EndGuard_) == Count,
+ "Mismatch between enum value and enum count");
+
+ inline auto GetString(${name} stringId) {
+ MOZ_ASSERT(static_cast<${type}>(stringId) < Count);
+ const EnumEntry& entry = ${entry_array}[static_cast<${type}>(stringId)];
+ return Span<const char>{entry.value, entry.length};
+ }
+ """,
+ entry_array=ENUM_ENTRY_VARIABLE_NAME,
+ entry_count=self.nEnumStrings(),
+ # -1 because nEnumStrings() includes a string for EndGuard_
+ real_entry_count=self.nEnumStrings() - 1,
+ name=self.enum.identifier.name,
+ type=self.underlyingType(),
+ )
+ strings = CGNamespace(
+ self.stringsNamespace(),
+ CGGeneric(
+ declare=entryDecl,
+ define=fill(
+ """
+ extern const EnumEntry ${name}[${count}] = {
+ $*{entries}
+ { nullptr, 0 }
+ };
+ """,
+ name=ENUM_ENTRY_VARIABLE_NAME,
+ count=self.nEnumStrings(),
+ entries="".join(
+ '{"%s", %d},\n' % (val, len(val)) for val in self.enum.values()
+ ),
+ ),
+ ),
+ )
+ toJSValue = CGEnumToJSValue(enum)
+ self.cgThings = CGList([strings, toJSValue], "\n")
+
+ def stringsNamespace(self):
+ return self.enum.identifier.name + "Values"
+
+ def nEnumStrings(self):
+ return len(self.enum.values()) + 1
+
+ def underlyingType(self):
+ count = self.nEnumStrings()
+ if count <= 256:
+ return "uint8_t"
+ if count <= 65536:
+ return "uint16_t"
+ raise ValueError(
+ "Enum " + self.enum.identifier.name + " has more than 65536 values"
+ )
+
+ def declare(self):
+ decl = fill(
+ """
+ enum class ${name} : ${ty} {
+ $*{enums}
+ EndGuard_
+ };
+ """,
+ name=self.enum.identifier.name,
+ ty=self.underlyingType(),
+ enums=",\n".join(map(getEnumValueName, self.enum.values())) + ",\n",
+ )
+
+ return decl + "\n" + self.cgThings.declare()
+
+ def define(self):
+ return self.cgThings.define()
+
+ def deps(self):
+ return self.enum.getDeps()
+
+
+def getUnionAccessorSignatureType(type, descriptorProvider):
+ """
+ Returns the types that are used in the getter and setter signatures for
+ union types
+ """
+ # Flat member types have already unwrapped nullables.
+ assert not type.nullable()
+
+ # Promise types can never appear in unions, because Promise is not
+ # distinguishable from anything.
+ assert not type.isPromise()
+
+ if type.isSequence() or type.isRecord():
+ if type.isSequence():
+ wrapperType = "Sequence"
+ else:
+ wrapperType = "Record"
+ # We don't use the returned template here, so it's OK to just pass no
+ # sourceDescription.
+ elementInfo = getJSToNativeConversionInfo(
+ type.inner, descriptorProvider, isMember=wrapperType
+ )
+ if wrapperType == "Sequence":
+ innerType = elementInfo.declType
+ else:
+ innerType = [recordKeyDeclType(type), elementInfo.declType]
+
+ return CGTemplatedType(wrapperType, innerType, isConst=True, isReference=True)
+
+ # Nested unions are unwrapped automatically into our flatMemberTypes.
+ assert not type.isUnion()
+
+ if type.isGeckoInterface():
+ descriptor = descriptorProvider.getDescriptor(
+ type.unroll().inner.identifier.name
+ )
+ typeName = CGGeneric(descriptor.nativeType)
+ if not type.unroll().inner.isExternal():
+ typeName = CGWrapper(typeName, post="&")
+ elif descriptor.interface.identifier.name == "WindowProxy":
+ typeName = CGGeneric("WindowProxyHolder const&")
+ else:
+ # Allow null pointers for old-binding classes.
+ typeName = CGWrapper(typeName, post="*")
+ return typeName
+
+ if type.isSpiderMonkeyInterface():
+ typeName = CGGeneric(type.name)
+ return CGWrapper(typeName, post=" const &")
+
+ if type.isJSString():
+ raise TypeError("JSString not supported in unions")
+
+ if type.isDOMString() or type.isUSVString():
+ return CGGeneric("const nsAString&")
+
+ if type.isUTF8String():
+ return CGGeneric("const nsACString&")
+
+ if type.isByteString():
+ return CGGeneric("const nsCString&")
+
+ if type.isEnum():
+ return CGGeneric(type.inner.identifier.name)
+
+ if type.isCallback():
+ return CGGeneric("%s&" % type.unroll().callback.identifier.name)
+
+ if type.isAny():
+ return CGGeneric("JS::Value")
+
+ if type.isObject():
+ return CGGeneric("JSObject*")
+
+ if type.isDictionary():
+ return CGGeneric("const %s&" % type.inner.identifier.name)
+
+ if not type.isPrimitive():
+ raise TypeError("Need native type for argument type '%s'" % str(type))
+
+ return CGGeneric(builtinNames[type.tag()])
+
+
+def getUnionTypeTemplateVars(unionType, type, descriptorProvider, isMember=False):
+ assert not type.isUndefined()
+ assert not isMember or isMember in ("Union", "OwningUnion")
+
+ ownsMembers = isMember == "OwningUnion"
+ name = getUnionMemberName(type)
+ holderName = "m" + name + "Holder"
+
+ # By the time tryNextCode is invoked, we're guaranteed the union has been
+ # constructed as some type, since we've been trying to convert into the
+ # corresponding member.
+ tryNextCode = fill(
+ """
+ Destroy${name}();
+ tryNext = true;
+ return true;
+ """,
+ name=name,
+ )
+
+ sourceDescription = "%s branch of %s" % (type.prettyName(), unionType.prettyName())
+
+ conversionInfo = getJSToNativeConversionInfo(
+ type,
+ descriptorProvider,
+ failureCode=tryNextCode,
+ isDefinitelyObject=not type.isDictionary(),
+ isMember=isMember,
+ sourceDescription=sourceDescription,
+ )
+
+ ctorNeedsCx = conversionInfo.declArgs == "cx"
+ ctorArgs = "cx" if ctorNeedsCx else ""
+
+ structType = conversionInfo.declType.define()
+ externalType = getUnionAccessorSignatureType(type, descriptorProvider).define()
+
+ if type.isObject():
+ if ownsMembers:
+ setValue = "mValue.mObject.SetValue(obj);"
+ else:
+ setValue = "mValue.mObject.SetValue(cx, obj);"
+
+ body = fill(
+ """
+ MOZ_ASSERT(mType == eUninitialized);
+ ${setValue}
+ mType = eObject;
+ """,
+ setValue=setValue,
+ )
+
+ # It's a bit sketchy to do the security check after setting the value,
+ # but it keeps the code cleaner and lets us avoid rooting |obj| over the
+ # call to CallerSubsumes().
+ body = body + fill(
+ """
+ if (passedToJSImpl && !CallerSubsumes(obj)) {
+ cx.ThrowErrorMessage<MSG_PERMISSION_DENIED_TO_PASS_ARG>("${sourceDescription}");
+ return false;
+ }
+ return true;
+ """,
+ sourceDescription=sourceDescription,
+ )
+
+ setters = [
+ ClassMethod(
+ "SetToObject",
+ "bool",
+ [
+ Argument("BindingCallContext&", "cx"),
+ Argument("JSObject*", "obj"),
+ Argument("bool", "passedToJSImpl", default="false"),
+ ],
+ inline=True,
+ bodyInHeader=True,
+ body=body,
+ )
+ ]
+ elif type.isDictionary() and not type.inner.needsConversionFromJS:
+ # In this case we are never initialized from JS to start with
+ setters = None
+ else:
+ # Important: we need to not have our declName involve
+ # maybe-GCing operations.
+ jsConversion = fill(
+ conversionInfo.template,
+ val="value",
+ maybeMutableVal="value",
+ declName="memberSlot",
+ holderName=(holderName if ownsMembers else "%s.ref()" % holderName),
+ passedToJSImpl="passedToJSImpl",
+ )
+
+ jsConversion = fill(
+ """
+ tryNext = false;
+ { // scope for memberSlot
+ ${structType}& memberSlot = RawSetAs${name}(${ctorArgs});
+ $*{jsConversion}
+ }
+ return true;
+ """,
+ structType=structType,
+ name=name,
+ ctorArgs=ctorArgs,
+ jsConversion=jsConversion,
+ )
+
+ needCallContext = idlTypeNeedsCallContext(type)
+ if needCallContext:
+ cxType = "BindingCallContext&"
+ else:
+ cxType = "JSContext*"
+ setters = [
+ ClassMethod(
+ "TrySetTo" + name,
+ "bool",
+ [
+ Argument(cxType, "cx"),
+ Argument("JS::Handle<JS::Value>", "value"),
+ Argument("bool&", "tryNext"),
+ Argument("bool", "passedToJSImpl", default="false"),
+ ],
+ visibility="private",
+ body=jsConversion,
+ )
+ ]
+ if needCallContext:
+ # Add a method for non-binding uses of unions to allow them to set
+ # things in the union without providing a call context (though if
+ # they want good error reporting they'll provide one anyway).
+ shimBody = fill(
+ """
+ BindingCallContext cx(cx_, nullptr);
+ return TrySetTo${name}(cx, value, tryNext, passedToJSImpl);
+ """,
+ name=name,
+ )
+ setters.append(
+ ClassMethod(
+ "TrySetTo" + name,
+ "bool",
+ [
+ Argument("JSContext*", "cx_"),
+ Argument("JS::Handle<JS::Value>", "value"),
+ Argument("bool&", "tryNext"),
+ Argument("bool", "passedToJSImpl", default="false"),
+ ],
+ visibility="private",
+ body=shimBody,
+ )
+ )
+
+ return {
+ "name": name,
+ "structType": structType,
+ "externalType": externalType,
+ "setters": setters,
+ "ctorArgs": ctorArgs,
+ "ctorArgList": [Argument("JSContext*", "cx")] if ctorNeedsCx else [],
+ }
+
+
+def getUnionConversionTemplate(type):
+ assert type.isUnion()
+ assert not type.nullable()
+
+ memberTypes = type.flatMemberTypes
+ prettyNames = []
+
+ interfaceMemberTypes = [t for t in memberTypes if t.isNonCallbackInterface()]
+ if len(interfaceMemberTypes) > 0:
+ interfaceObject = []
+ for memberType in interfaceMemberTypes:
+ name = getUnionMemberName(memberType)
+ interfaceObject.append(
+ CGGeneric(
+ "(failed = !TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext"
+ % name
+ )
+ )
+ prettyNames.append(memberType.prettyName())
+ interfaceObject = CGWrapper(
+ CGList(interfaceObject, " ||\n"),
+ pre="done = ",
+ post=";\n",
+ reindent=True,
+ )
+ else:
+ interfaceObject = None
+
+ sequenceObjectMemberTypes = [t for t in memberTypes if t.isSequence()]
+ if len(sequenceObjectMemberTypes) > 0:
+ assert len(sequenceObjectMemberTypes) == 1
+ memberType = sequenceObjectMemberTypes[0]
+ name = getUnionMemberName(memberType)
+ sequenceObject = CGGeneric(
+ "done = (failed = !TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n"
+ % name
+ )
+ prettyNames.append(memberType.prettyName())
+ else:
+ sequenceObject = None
+
+ callbackMemberTypes = [
+ t for t in memberTypes if t.isCallback() or t.isCallbackInterface()
+ ]
+ if len(callbackMemberTypes) > 0:
+ assert len(callbackMemberTypes) == 1
+ memberType = callbackMemberTypes[0]
+ name = getUnionMemberName(memberType)
+ callbackObject = CGGeneric(
+ "done = (failed = !TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n"
+ % name
+ )
+ prettyNames.append(memberType.prettyName())
+ else:
+ callbackObject = None
+
+ dictionaryMemberTypes = [t for t in memberTypes if t.isDictionary()]
+ if len(dictionaryMemberTypes) > 0:
+ assert len(dictionaryMemberTypes) == 1
+ memberType = dictionaryMemberTypes[0]
+ name = getUnionMemberName(memberType)
+ setDictionary = CGGeneric(
+ "done = (failed = !TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n"
+ % name
+ )
+ prettyNames.append(memberType.prettyName())
+ else:
+ setDictionary = None
+
+ recordMemberTypes = [t for t in memberTypes if t.isRecord()]
+ if len(recordMemberTypes) > 0:
+ assert len(recordMemberTypes) == 1
+ memberType = recordMemberTypes[0]
+ name = getUnionMemberName(memberType)
+ recordObject = CGGeneric(
+ "done = (failed = !TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n"
+ % name
+ )
+ prettyNames.append(memberType.prettyName())
+ else:
+ recordObject = None
+
+ objectMemberTypes = [t for t in memberTypes if t.isObject()]
+ if len(objectMemberTypes) > 0:
+ assert len(objectMemberTypes) == 1
+ # Very important to NOT construct a temporary Rooted here, since the
+ # SetToObject call can call a Rooted constructor and we need to keep
+ # stack discipline for Rooted.
+ object = CGGeneric(
+ "if (!SetToObject(cx, &${val}.toObject(), ${passedToJSImpl})) {\n"
+ " return false;\n"
+ "}\n"
+ "done = true;\n"
+ )
+ prettyNames.append(objectMemberTypes[0].prettyName())
+ else:
+ object = None
+
+ hasObjectTypes = (
+ interfaceObject or sequenceObject or callbackObject or object or recordObject
+ )
+ if hasObjectTypes:
+ # "object" is not distinguishable from other types
+ assert not object or not (
+ interfaceObject or sequenceObject or callbackObject or recordObject
+ )
+ if sequenceObject or callbackObject:
+ # An object can be both an sequence object and a callback or
+ # dictionary, but we shouldn't have both in the union's members
+ # because they are not distinguishable.
+ assert not (sequenceObject and callbackObject)
+ templateBody = CGElseChain([sequenceObject, callbackObject])
+ else:
+ templateBody = None
+ if interfaceObject:
+ assert not object
+ if templateBody:
+ templateBody = CGIfWrapper(templateBody, "!done")
+ templateBody = CGList([interfaceObject, templateBody])
+ else:
+ templateBody = CGList([templateBody, object])
+
+ if recordObject:
+ templateBody = CGList([templateBody, CGIfWrapper(recordObject, "!done")])
+
+ templateBody = CGIfWrapper(templateBody, "${val}.isObject()")
+ else:
+ templateBody = CGGeneric()
+
+ if setDictionary:
+ assert not object
+ templateBody = CGList([templateBody, CGIfWrapper(setDictionary, "!done")])
+
+ stringTypes = [t for t in memberTypes if t.isString() or t.isEnum()]
+ numericTypes = [t for t in memberTypes if t.isNumeric()]
+ booleanTypes = [t for t in memberTypes if t.isBoolean()]
+ if stringTypes or numericTypes or booleanTypes:
+ assert len(stringTypes) <= 1
+ assert len(numericTypes) <= 1
+ assert len(booleanTypes) <= 1
+
+ # We will wrap all this stuff in a do { } while (0); so we
+ # can use "break" for flow control.
+ def getStringOrPrimitiveConversion(memberType):
+ name = getUnionMemberName(memberType)
+ return CGGeneric(
+ "done = (failed = !TrySetTo%s(cx, ${val}, tryNext)) || !tryNext;\n"
+ "break;\n" % name
+ )
+
+ other = CGList([])
+ stringConversion = [getStringOrPrimitiveConversion(t) for t in stringTypes]
+ numericConversion = [getStringOrPrimitiveConversion(t) for t in numericTypes]
+ booleanConversion = [getStringOrPrimitiveConversion(t) for t in booleanTypes]
+ if stringConversion:
+ if booleanConversion:
+ other.append(CGIfWrapper(booleanConversion[0], "${val}.isBoolean()"))
+ if numericConversion:
+ other.append(CGIfWrapper(numericConversion[0], "${val}.isNumber()"))
+ other.append(stringConversion[0])
+ elif numericConversion:
+ if booleanConversion:
+ other.append(CGIfWrapper(booleanConversion[0], "${val}.isBoolean()"))
+ other.append(numericConversion[0])
+ else:
+ assert booleanConversion
+ other.append(booleanConversion[0])
+
+ other = CGWrapper(CGIndenter(other), pre="do {\n", post="} while (false);\n")
+ if hasObjectTypes or setDictionary:
+ other = CGWrapper(CGIndenter(other), "{\n", post="}\n")
+ if object:
+ templateBody = CGElseChain([templateBody, other])
+ else:
+ other = CGWrapper(other, pre="if (!done) ")
+ templateBody = CGList([templateBody, other])
+ else:
+ assert templateBody.define() == ""
+ templateBody = other
+ else:
+ other = None
+
+ templateBody = CGWrapper(
+ templateBody, pre="bool done = false, failed = false, tryNext;\n"
+ )
+ throw = CGGeneric(
+ fill(
+ """
+ if (failed) {
+ return false;
+ }
+ if (!done) {
+ cx.ThrowErrorMessage<MSG_NOT_IN_UNION>(sourceDescription, "${names}");
+ return false;
+ }
+ """,
+ names=", ".join(prettyNames),
+ )
+ )
+
+ templateBody = CGList([templateBody, throw])
+
+ hasUndefinedType = any(t.isUndefined() for t in memberTypes)
+ elseChain = []
+
+ # The spec does this before anything else, but we do it after checking
+ # for null in the case of a nullable union. In practice this shouldn't
+ # make a difference, but it makes things easier because we first need to
+ # call Construct on our Maybe<...>, before we can set the union type to
+ # undefined, and we do that below after checking for null (see the
+ # 'if nullable:' block below).
+ if hasUndefinedType:
+ elseChain.append(
+ CGIfWrapper(
+ CGGeneric("SetUndefined();\n"),
+ "${val}.isUndefined()",
+ )
+ )
+
+ if type.hasNullableType:
+ nullTest = (
+ "${val}.isNull()" if hasUndefinedType else "${val}.isNullOrUndefined()"
+ )
+ elseChain.append(
+ CGIfWrapper(
+ CGGeneric("SetNull();\n"),
+ nullTest,
+ )
+ )
+
+ if len(elseChain) > 0:
+ elseChain.append(CGWrapper(CGIndenter(templateBody), pre="{\n", post="}\n"))
+ templateBody = CGElseChain(elseChain)
+
+ return templateBody
+
+
+def getUnionInitMethods(type, isOwningUnion=False):
+ assert type.isUnion()
+
+ template = getUnionConversionTemplate(type).define()
+
+ replacements = {
+ "val": "value",
+ "passedToJSImpl": "passedToJSImpl",
+ }
+
+ initBody = fill(
+ """
+ MOZ_ASSERT(mType == eUninitialized);
+
+ $*{conversion}
+ return true;
+ """,
+ conversion=string.Template(template).substitute(replacements),
+ )
+
+ return [
+ ClassMethod(
+ "Init",
+ "bool",
+ [
+ Argument("BindingCallContext&", "cx"),
+ Argument("JS::Handle<JS::Value>", "value"),
+ Argument("const char*", "sourceDescription", default='"Value"'),
+ Argument("bool", "passedToJSImpl", default="false"),
+ ],
+ visibility="public",
+ body=initBody,
+ ),
+ ClassMethod(
+ "Init",
+ "bool",
+ [
+ Argument("JSContext*", "cx_"),
+ Argument("JS::Handle<JS::Value>", "value"),
+ Argument("const char*", "sourceDescription", default='"Value"'),
+ Argument("bool", "passedToJSImpl", default="false"),
+ ],
+ visibility="public",
+ body=dedent(
+ """
+ BindingCallContext cx(cx_, nullptr);
+ return Init(cx, value, sourceDescription, passedToJSImpl);
+ """
+ ),
+ ),
+ ]
+
+
+class CGUnionStruct(CGThing):
+ def __init__(self, type, descriptorProvider, ownsMembers=False):
+ CGThing.__init__(self)
+ self.type = type.unroll()
+ self.descriptorProvider = descriptorProvider
+ self.ownsMembers = ownsMembers
+ self.struct = self.getStruct()
+
+ def declare(self):
+ return self.struct.declare()
+
+ def define(self):
+ return self.struct.define()
+
+ def deps(self):
+ return self.type.getDeps()
+
+ def getStruct(self):
+
+ members = [
+ ClassMember("mType", "TypeOrUninit", body="eUninitialized"),
+ ClassMember("mValue", "Value"),
+ ]
+ ctor = ClassConstructor(
+ [], bodyInHeader=True, visibility="public", explicit=True
+ )
+
+ methods = []
+ enumValues = ["eUninitialized"]
+ toJSValCases = [
+ CGCase(
+ "eUninitialized", CGGeneric("return false;\n"), CGCase.DONT_ADD_BREAK
+ )
+ ]
+ destructorCases = [CGCase("eUninitialized", None)]
+ assignmentCase = CGCase(
+ "eUninitialized",
+ CGGeneric(
+ "MOZ_ASSERT(mType == eUninitialized,\n"
+ ' "We need to destroy ourselves?");\n'
+ ),
+ )
+ assignmentCases = [assignmentCase]
+ moveCases = [assignmentCase]
+ traceCases = []
+ unionValues = []
+
+ def addSpecialType(typename):
+ enumValue = "e" + typename
+ enumValues.append(enumValue)
+ methods.append(
+ ClassMethod(
+ "Is" + typename,
+ "bool",
+ [],
+ const=True,
+ inline=True,
+ body="return mType == %s;\n" % enumValue,
+ bodyInHeader=True,
+ )
+ )
+ methods.append(
+ ClassMethod(
+ "Set" + typename,
+ "void",
+ [],
+ inline=True,
+ body=fill(
+ """
+ Uninit();
+ mType = ${enumValue};
+ """,
+ enumValue=enumValue,
+ ),
+ bodyInHeader=True,
+ )
+ )
+ destructorCases.append(CGCase(enumValue, None))
+ assignmentCase = CGCase(
+ enumValue,
+ CGGeneric(
+ fill(
+ """
+ MOZ_ASSERT(mType == eUninitialized);
+ mType = ${enumValue};
+ """,
+ enumValue=enumValue,
+ )
+ ),
+ )
+ assignmentCases.append(assignmentCase)
+ moveCases.append(assignmentCase)
+ toJSValCases.append(
+ CGCase(
+ enumValue,
+ CGGeneric(
+ fill(
+ """
+ rval.set${typename}();
+ return true;
+ """,
+ typename=typename,
+ )
+ ),
+ CGCase.DONT_ADD_BREAK,
+ )
+ )
+
+ if self.type.hasNullableType:
+ addSpecialType("Null")
+
+ hasObjectType = any(t.isObject() for t in self.type.flatMemberTypes)
+ skipToJSVal = False
+ for t in self.type.flatMemberTypes:
+ if t.isUndefined():
+ addSpecialType("Undefined")
+ continue
+
+ vars = getUnionTypeTemplateVars(
+ self.type,
+ t,
+ self.descriptorProvider,
+ isMember="OwningUnion" if self.ownsMembers else "Union",
+ )
+ if vars["setters"]:
+ methods.extend(vars["setters"])
+ uninit = "Uninit();"
+ if hasObjectType and not self.ownsMembers:
+ uninit = (
+ 'MOZ_ASSERT(mType != eObject, "This will not play well with Rooted");\n'
+ + uninit
+ )
+ if not t.isObject() or self.ownsMembers:
+ body = fill(
+ """
+ if (mType == e${name}) {
+ return mValue.m${name}.Value();
+ }
+ %s
+ mType = e${name};
+ return mValue.m${name}.SetValue(${ctorArgs});
+ """,
+ **vars,
+ )
+
+ # bodyInHeader must be false for return values because they own
+ # their union members and we don't want include headers in
+ # UnionTypes.h just to call Addref/Release
+ methods.append(
+ ClassMethod(
+ "RawSetAs" + vars["name"],
+ vars["structType"] + "&",
+ vars["ctorArgList"],
+ bodyInHeader=not self.ownsMembers,
+ body=body % "MOZ_ASSERT(mType == eUninitialized);",
+ )
+ )
+ methods.append(
+ ClassMethod(
+ "SetAs" + vars["name"],
+ vars["structType"] + "&",
+ vars["ctorArgList"],
+ bodyInHeader=not self.ownsMembers,
+ body=body % uninit,
+ )
+ )
+
+ # Provide a SetStringLiteral() method to support string defaults.
+ if t.isByteString() or t.isUTF8String():
+ charType = "const nsCString::char_type"
+ elif t.isString():
+ charType = "const nsString::char_type"
+ else:
+ charType = None
+
+ if charType:
+ methods.append(
+ ClassMethod(
+ "SetStringLiteral",
+ "void",
+ # Hack, but it works...
+ [Argument(charType, "(&aData)[N]")],
+ inline=True,
+ bodyInHeader=True,
+ templateArgs=["int N"],
+ body="RawSetAs%s().AssignLiteral(aData);\n" % t.name,
+ )
+ )
+
+ body = fill("return mType == e${name};\n", **vars)
+ methods.append(
+ ClassMethod(
+ "Is" + vars["name"],
+ "bool",
+ [],
+ const=True,
+ bodyInHeader=True,
+ body=body,
+ )
+ )
+
+ body = fill(
+ """
+ MOZ_RELEASE_ASSERT(Is${name}(), "Wrong type!");
+ mValue.m${name}.Destroy();
+ mType = eUninitialized;
+ """,
+ **vars,
+ )
+ methods.append(
+ ClassMethod(
+ "Destroy" + vars["name"],
+ "void",
+ [],
+ visibility="private",
+ bodyInHeader=not self.ownsMembers,
+ body=body,
+ )
+ )
+
+ body = fill(
+ """
+ MOZ_RELEASE_ASSERT(Is${name}(), "Wrong type!");
+ return mValue.m${name}.Value();
+ """,
+ **vars,
+ )
+ # The non-const version of GetAs* returns our internal type
+ getterReturnType = "%s&" % vars["structType"]
+ methods.append(
+ ClassMethod(
+ "GetAs" + vars["name"],
+ getterReturnType,
+ [],
+ bodyInHeader=True,
+ body=body,
+ )
+ )
+ # The const version of GetAs* returns our internal type
+ # for owning unions, but our external type for non-owning
+ # ones.
+ if self.ownsMembers:
+ getterReturnType = "%s const &" % vars["structType"]
+ else:
+ getterReturnType = vars["externalType"]
+ methods.append(
+ ClassMethod(
+ "GetAs" + vars["name"],
+ getterReturnType,
+ [],
+ const=True,
+ bodyInHeader=True,
+ body=body,
+ )
+ )
+
+ unionValues.append(fill("UnionMember<${structType} > m${name}", **vars))
+ destructorCases.append(
+ CGCase("e" + vars["name"], CGGeneric("Destroy%s();\n" % vars["name"]))
+ )
+
+ enumValues.append("e" + vars["name"])
+
+ conversionToJS = self.getConversionToJS(vars, t)
+ if conversionToJS:
+ toJSValCases.append(
+ CGCase("e" + vars["name"], conversionToJS, CGCase.DONT_ADD_BREAK)
+ )
+ else:
+ skipToJSVal = True
+
+ assignmentCases.append(
+ CGCase(
+ "e" + vars["name"],
+ CGGeneric(
+ "SetAs%s() = aOther.GetAs%s();\n" % (vars["name"], vars["name"])
+ ),
+ )
+ )
+ moveCases.append(
+ CGCase(
+ "e" + vars["name"],
+ CGGeneric(
+ "mType = e%s;\n" % vars["name"]
+ + "mValue.m%s.SetValue(std::move(aOther.mValue.m%s.Value()));\n"
+ % (vars["name"], vars["name"])
+ ),
+ )
+ )
+ if self.ownsMembers and typeNeedsRooting(t):
+ if t.isObject():
+ traceCases.append(
+ CGCase(
+ "e" + vars["name"],
+ CGGeneric(
+ 'JS::TraceRoot(trc, %s, "%s");\n'
+ % (
+ "&mValue.m" + vars["name"] + ".Value()",
+ "mValue.m" + vars["name"],
+ )
+ ),
+ )
+ )
+ elif t.isDictionary():
+ traceCases.append(
+ CGCase(
+ "e" + vars["name"],
+ CGGeneric(
+ "mValue.m%s.Value().TraceDictionary(trc);\n"
+ % vars["name"]
+ ),
+ )
+ )
+ elif t.isSequence():
+ traceCases.append(
+ CGCase(
+ "e" + vars["name"],
+ CGGeneric(
+ "DoTraceSequence(trc, mValue.m%s.Value());\n"
+ % vars["name"]
+ ),
+ )
+ )
+ elif t.isRecord():
+ traceCases.append(
+ CGCase(
+ "e" + vars["name"],
+ CGGeneric(
+ "TraceRecord(trc, mValue.m%s.Value());\n" % vars["name"]
+ ),
+ )
+ )
+ else:
+ assert t.isSpiderMonkeyInterface()
+ traceCases.append(
+ CGCase(
+ "e" + vars["name"],
+ CGGeneric(
+ "mValue.m%s.Value().TraceSelf(trc);\n" % vars["name"]
+ ),
+ )
+ )
+
+ dtor = CGSwitch("mType", destructorCases).define()
+
+ methods.extend(getUnionInitMethods(self.type, isOwningUnion=self.ownsMembers))
+ methods.append(
+ ClassMethod(
+ "Uninit",
+ "void",
+ [],
+ visibility="public",
+ body=dtor,
+ bodyInHeader=not self.ownsMembers,
+ inline=not self.ownsMembers,
+ )
+ )
+
+ if not skipToJSVal:
+ methods.append(
+ ClassMethod(
+ "ToJSVal",
+ "bool",
+ [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "scopeObj"),
+ Argument("JS::MutableHandle<JS::Value>", "rval"),
+ ],
+ body=CGSwitch(
+ "mType", toJSValCases, default=CGGeneric("return false;\n")
+ ).define(),
+ const=True,
+ )
+ )
+
+ constructors = [ctor]
+ selfName = CGUnionStruct.unionTypeName(self.type, self.ownsMembers)
+ if self.ownsMembers:
+ if traceCases:
+ traceBody = CGSwitch(
+ "mType", traceCases, default=CGGeneric("")
+ ).define()
+ methods.append(
+ ClassMethod(
+ "TraceUnion",
+ "void",
+ [Argument("JSTracer*", "trc")],
+ body=traceBody,
+ )
+ )
+
+ op_body = CGList([])
+ op_body.append(CGSwitch("aOther.mType", moveCases))
+ constructors.append(
+ ClassConstructor(
+ [Argument("%s&&" % selfName, "aOther")],
+ visibility="public",
+ body=op_body.define(),
+ )
+ )
+
+ methods.append(
+ ClassMethod(
+ "operator=",
+ "%s&" % selfName,
+ [Argument("%s&&" % selfName, "aOther")],
+ body="this->~%s();\nnew (this) %s (std::move(aOther));\nreturn *this;\n"
+ % (selfName, selfName),
+ )
+ )
+
+ body = dedent(
+ """
+ MOZ_RELEASE_ASSERT(mType != eUninitialized);
+ return static_cast<Type>(mType);
+ """
+ )
+ methods.append(
+ ClassMethod(
+ "GetType",
+ "Type",
+ [],
+ bodyInHeader=True,
+ body=body,
+ const=True,
+ )
+ )
+
+ if CGUnionStruct.isUnionCopyConstructible(self.type):
+ constructors.append(
+ ClassConstructor(
+ [Argument("const %s&" % selfName, "aOther")],
+ bodyInHeader=True,
+ visibility="public",
+ explicit=True,
+ body="*this = aOther;\n",
+ )
+ )
+ op_body = CGList([])
+ op_body.append(CGSwitch("aOther.mType", assignmentCases))
+ op_body.append(CGGeneric("return *this;\n"))
+ methods.append(
+ ClassMethod(
+ "operator=",
+ "%s&" % selfName,
+ [Argument("const %s&" % selfName, "aOther")],
+ body=op_body.define(),
+ )
+ )
+ disallowCopyConstruction = False
+ else:
+ disallowCopyConstruction = True
+ else:
+ disallowCopyConstruction = True
+
+ if self.ownsMembers and idlTypeNeedsCycleCollection(self.type):
+ friend = (
+ " friend void ImplCycleCollectionUnlink(%s& aUnion);\n"
+ % CGUnionStruct.unionTypeName(self.type, True)
+ )
+ else:
+ friend = ""
+
+ enumValuesNoUninit = [x for x in enumValues if x != "eUninitialized"]
+
+ bases = [ClassBase("AllOwningUnionBase")] if self.ownsMembers else []
+ enums = [
+ ClassEnum("TypeOrUninit", enumValues, visibility="private"),
+ ClassEnum(
+ "Type",
+ enumValuesNoUninit,
+ visibility="public",
+ enumClass=True,
+ values=["TypeOrUninit::" + x for x in enumValuesNoUninit],
+ ),
+ ]
+ return CGClass(
+ selfName,
+ bases=bases,
+ members=members,
+ constructors=constructors,
+ methods=methods,
+ disallowCopyConstruction=disallowCopyConstruction,
+ extradeclarations=friend,
+ destructor=ClassDestructor(
+ visibility="public", body="Uninit();\n", bodyInHeader=True
+ ),
+ enums=enums,
+ unions=[ClassUnion("Value", unionValues, visibility="private")],
+ )
+
+ def getConversionToJS(self, templateVars, type):
+ if type.isDictionary() and not type.inner.needsConversionToJS:
+ # We won't be able to convert this dictionary to a JS value, nor
+ # will we need to, since we don't need a ToJSVal method at all.
+ return None
+
+ assert not type.nullable() # flatMemberTypes never has nullable types
+ val = "mValue.m%(name)s.Value()" % templateVars
+ wrapCode = wrapForType(
+ type,
+ self.descriptorProvider,
+ {
+ "jsvalRef": "rval",
+ "jsvalHandle": "rval",
+ "obj": "scopeObj",
+ "result": val,
+ "spiderMonkeyInterfacesAreStructs": True,
+ },
+ )
+ return CGGeneric(wrapCode)
+
+ @staticmethod
+ def isUnionCopyConstructible(type):
+ return all(isTypeCopyConstructible(t) for t in type.flatMemberTypes)
+
+ @staticmethod
+ def unionTypeName(type, ownsMembers):
+ """
+ Returns a string name for this known union type.
+ """
+ assert type.isUnion() and not type.nullable()
+ return ("Owning" if ownsMembers else "") + type.name
+
+ @staticmethod
+ def unionTypeDecl(type, ownsMembers):
+ """
+ Returns a string for declaring this possibly-nullable union type.
+ """
+ assert type.isUnion()
+ nullable = type.nullable()
+ if nullable:
+ type = type.inner
+ decl = CGGeneric(CGUnionStruct.unionTypeName(type, ownsMembers))
+ if nullable:
+ decl = CGTemplatedType("Nullable", decl)
+ return decl.define()
+
+
+class ClassItem:
+ """Use with CGClass"""
+
+ def __init__(self, name, visibility):
+ self.name = name
+ self.visibility = visibility
+
+ def declare(self, cgClass):
+ assert False
+
+ def define(self, cgClass):
+ assert False
+
+
+class ClassBase(ClassItem):
+ def __init__(self, name, visibility="public"):
+ ClassItem.__init__(self, name, visibility)
+
+ def declare(self, cgClass):
+ return "%s %s" % (self.visibility, self.name)
+
+ def define(self, cgClass):
+ # Only in the header
+ return ""
+
+
+class ClassMethod(ClassItem):
+ def __init__(
+ self,
+ name,
+ returnType,
+ args,
+ inline=False,
+ static=False,
+ virtual=False,
+ const=False,
+ bodyInHeader=False,
+ templateArgs=None,
+ visibility="public",
+ body=None,
+ breakAfterReturnDecl="\n",
+ breakAfterSelf="\n",
+ override=False,
+ canRunScript=False,
+ ):
+ """
+ override indicates whether to flag the method as override
+ """
+ assert not override or virtual
+ assert not (override and static)
+ self.returnType = returnType
+ self.args = args
+ self.inline = inline or bodyInHeader
+ self.static = static
+ self.virtual = virtual
+ self.const = const
+ self.bodyInHeader = bodyInHeader
+ self.templateArgs = templateArgs
+ self.body = body
+ self.breakAfterReturnDecl = breakAfterReturnDecl
+ self.breakAfterSelf = breakAfterSelf
+ self.override = override
+ self.canRunScript = canRunScript
+ ClassItem.__init__(self, name, visibility)
+
+ def getDecorators(self, declaring):
+ decorators = []
+ if self.canRunScript:
+ decorators.append("MOZ_CAN_RUN_SCRIPT")
+ if self.inline:
+ decorators.append("inline")
+ if declaring:
+ if self.static:
+ decorators.append("static")
+ if self.virtual and not self.override:
+ decorators.append("virtual")
+ if decorators:
+ return " ".join(decorators) + " "
+ return ""
+
+ def getBody(self):
+ # Override me or pass a string to constructor
+ assert self.body is not None
+ return self.body
+
+ def declare(self, cgClass):
+ templateClause = (
+ "template <%s>\n" % ", ".join(self.templateArgs)
+ if self.bodyInHeader and self.templateArgs
+ else ""
+ )
+ args = ", ".join([a.declare() for a in self.args])
+ if self.bodyInHeader:
+ body = indent(self.getBody())
+ body = "\n{\n" + body + "}\n"
+ else:
+ body = ";\n"
+
+ return fill(
+ "${templateClause}${decorators}${returnType}${breakAfterReturnDecl}"
+ "${name}(${args})${const}${override}${body}"
+ "${breakAfterSelf}",
+ templateClause=templateClause,
+ decorators=self.getDecorators(True),
+ returnType=self.returnType,
+ breakAfterReturnDecl=self.breakAfterReturnDecl,
+ name=self.name,
+ args=args,
+ const=" const" if self.const else "",
+ override=" override" if self.override else "",
+ body=body,
+ breakAfterSelf=self.breakAfterSelf,
+ )
+
+ def define(self, cgClass):
+ if self.bodyInHeader:
+ return ""
+
+ templateArgs = cgClass.templateArgs
+ if templateArgs:
+ if cgClass.templateSpecialization:
+ templateArgs = templateArgs[len(cgClass.templateSpecialization) :]
+
+ if templateArgs:
+ templateClause = "template <%s>\n" % ", ".join(
+ [str(a) for a in templateArgs]
+ )
+ else:
+ templateClause = ""
+
+ return fill(
+ """
+ ${templateClause}${decorators}${returnType}
+ ${className}::${name}(${args})${const}
+ {
+ $*{body}
+ }
+ """,
+ templateClause=templateClause,
+ decorators=self.getDecorators(False),
+ returnType=self.returnType,
+ className=cgClass.getNameString(),
+ name=self.name,
+ args=", ".join([a.define() for a in self.args]),
+ const=" const" if self.const else "",
+ body=self.getBody(),
+ )
+
+
+class ClassUsingDeclaration(ClassItem):
+ """
+ Used for importing a name from a base class into a CGClass
+
+ baseClass is the name of the base class to import the name from
+
+ name is the name to import
+
+ visibility determines the visibility of the name (public,
+ protected, private), defaults to public.
+ """
+
+ def __init__(self, baseClass, name, visibility="public"):
+ self.baseClass = baseClass
+ ClassItem.__init__(self, name, visibility)
+
+ def declare(self, cgClass):
+ return "using %s::%s;\n\n" % (self.baseClass, self.name)
+
+ def define(self, cgClass):
+ return ""
+
+
+class ClassConstructor(ClassItem):
+ """
+ Used for adding a constructor to a CGClass.
+
+ args is a list of Argument objects that are the arguments taken by the
+ constructor.
+
+ inline should be True if the constructor should be marked inline.
+
+ bodyInHeader should be True if the body should be placed in the class
+ declaration in the header.
+
+ default should be True if the definition of the constructor should be
+ `= default;`.
+
+ visibility determines the visibility of the constructor (public,
+ protected, private), defaults to private.
+
+ explicit should be True if the constructor should be marked explicit.
+
+ baseConstructors is a list of strings containing calls to base constructors,
+ defaults to None.
+
+ body contains a string with the code for the constructor, defaults to empty.
+ """
+
+ def __init__(
+ self,
+ args,
+ inline=False,
+ bodyInHeader=False,
+ default=False,
+ visibility="private",
+ explicit=False,
+ constexpr=False,
+ baseConstructors=None,
+ body="",
+ ):
+ assert not (inline and constexpr)
+ assert not (bodyInHeader and constexpr)
+ assert not (default and body)
+ self.args = args
+ self.inline = inline or bodyInHeader
+ self.bodyInHeader = bodyInHeader or constexpr or default
+ self.default = default
+ self.explicit = explicit
+ self.constexpr = constexpr
+ self.baseConstructors = baseConstructors or []
+ self.body = body
+ ClassItem.__init__(self, None, visibility)
+
+ def getDecorators(self, declaring):
+ decorators = []
+ if declaring:
+ if self.explicit:
+ decorators.append("explicit")
+ if self.inline:
+ decorators.append("inline")
+ if self.constexpr:
+ decorators.append("constexpr")
+ if decorators:
+ return " ".join(decorators) + " "
+ return ""
+
+ def getInitializationList(self, cgClass):
+ items = [str(c) for c in self.baseConstructors]
+ for m in cgClass.members:
+ if not m.static:
+ initialize = m.body
+ if initialize:
+ items.append(m.name + "(" + initialize + ")")
+
+ if len(items) > 0:
+ return "\n : " + ",\n ".join(items)
+ return ""
+
+ def getBody(self):
+ return self.body
+
+ def declare(self, cgClass):
+ args = ", ".join([a.declare() for a in self.args])
+ if self.bodyInHeader:
+ if self.default:
+ body = " = default;\n"
+ else:
+ body = (
+ self.getInitializationList(cgClass)
+ + "\n{\n"
+ + indent(self.getBody())
+ + "}\n"
+ )
+ else:
+ body = ";\n"
+
+ return fill(
+ "${decorators}${className}(${args})${body}\n",
+ decorators=self.getDecorators(True),
+ className=cgClass.getNameString(),
+ args=args,
+ body=body,
+ )
+
+ def define(self, cgClass):
+ if self.bodyInHeader:
+ return ""
+
+ return fill(
+ """
+ ${decorators}
+ ${className}::${className}(${args})${initializationList}
+ {
+ $*{body}
+ }
+ """,
+ decorators=self.getDecorators(False),
+ className=cgClass.getNameString(),
+ args=", ".join([a.define() for a in self.args]),
+ initializationList=self.getInitializationList(cgClass),
+ body=self.getBody(),
+ )
+
+
+class ClassDestructor(ClassItem):
+ """
+ Used for adding a destructor to a CGClass.
+
+ inline should be True if the destructor should be marked inline.
+
+ bodyInHeader should be True if the body should be placed in the class
+ declaration in the header.
+
+ visibility determines the visibility of the destructor (public,
+ protected, private), defaults to private.
+
+ body contains a string with the code for the destructor, defaults to empty.
+
+ virtual determines whether the destructor is virtual, defaults to False.
+ """
+
+ def __init__(
+ self,
+ inline=False,
+ bodyInHeader=False,
+ visibility="private",
+ body="",
+ virtual=False,
+ ):
+ self.inline = inline or bodyInHeader
+ self.bodyInHeader = bodyInHeader
+ self.body = body
+ self.virtual = virtual
+ ClassItem.__init__(self, None, visibility)
+
+ def getDecorators(self, declaring):
+ decorators = []
+ if self.virtual and declaring:
+ decorators.append("virtual")
+ if self.inline and declaring:
+ decorators.append("inline")
+ if decorators:
+ return " ".join(decorators) + " "
+ return ""
+
+ def getBody(self):
+ return self.body
+
+ def declare(self, cgClass):
+ if self.bodyInHeader:
+ body = "\n{\n" + indent(self.getBody()) + "}\n"
+ else:
+ body = ";\n"
+
+ return fill(
+ "${decorators}~${className}()${body}\n",
+ decorators=self.getDecorators(True),
+ className=cgClass.getNameString(),
+ body=body,
+ )
+
+ def define(self, cgClass):
+ if self.bodyInHeader:
+ return ""
+ return fill(
+ """
+ ${decorators}
+ ${className}::~${className}()
+ {
+ $*{body}
+ }
+ """,
+ decorators=self.getDecorators(False),
+ className=cgClass.getNameString(),
+ body=self.getBody(),
+ )
+
+
+class ClassMember(ClassItem):
+ def __init__(
+ self,
+ name,
+ type,
+ visibility="private",
+ static=False,
+ body=None,
+ hasIgnoreInitCheckFlag=False,
+ ):
+ self.type = type
+ self.static = static
+ self.body = body
+ self.hasIgnoreInitCheckFlag = hasIgnoreInitCheckFlag
+ ClassItem.__init__(self, name, visibility)
+
+ def declare(self, cgClass):
+ return "%s%s%s %s;\n" % (
+ "static " if self.static else "",
+ "MOZ_INIT_OUTSIDE_CTOR " if self.hasIgnoreInitCheckFlag else "",
+ self.type,
+ self.name,
+ )
+
+ def define(self, cgClass):
+ if not self.static:
+ return ""
+ if self.body:
+ body = " = " + self.body
+ else:
+ body = ""
+ return "%s %s::%s%s;\n" % (self.type, cgClass.getNameString(), self.name, body)
+
+
+class ClassEnum(ClassItem):
+ def __init__(
+ self, name, entries, values=None, visibility="public", enumClass=False
+ ):
+ self.entries = entries
+ self.values = values
+ self.enumClass = enumClass
+ ClassItem.__init__(self, name, visibility)
+
+ def declare(self, cgClass):
+ entries = []
+ for i in range(0, len(self.entries)):
+ if not self.values or i >= len(self.values):
+ entry = "%s" % self.entries[i]
+ else:
+ entry = "%s = %s" % (self.entries[i], self.values[i])
+ entries.append(entry)
+
+ decl = ["enum"]
+ self.enumClass and decl.append("class")
+ self.name and decl.append(self.name)
+ decl = " ".join(decl)
+
+ return "%s\n{\n%s\n};\n" % (decl, indent(",\n".join(entries)))
+
+ def define(self, cgClass):
+ # Only goes in the header
+ return ""
+
+
+class ClassUnion(ClassItem):
+ def __init__(self, name, entries, visibility="public"):
+ self.entries = [entry + ";\n" for entry in entries]
+ ClassItem.__init__(self, name, visibility)
+
+ def declare(self, cgClass):
+ return "union %s\n{\n%s\n};\n" % (self.name, indent("".join(self.entries)))
+
+ def define(self, cgClass):
+ # Only goes in the header
+ return ""
+
+
+class CGClass(CGThing):
+ def __init__(
+ self,
+ name,
+ bases=[],
+ members=[],
+ constructors=[],
+ destructor=None,
+ methods=[],
+ enums=[],
+ unions=[],
+ templateArgs=[],
+ templateSpecialization=[],
+ isStruct=False,
+ disallowCopyConstruction=False,
+ indent="",
+ decorators="",
+ extradeclarations="",
+ extradefinitions="",
+ ):
+ CGThing.__init__(self)
+ self.name = name
+ self.bases = bases
+ self.members = members
+ self.constructors = constructors
+ # We store our single destructor in a list, since all of our
+ # code wants lists of members.
+ self.destructors = [destructor] if destructor else []
+ self.methods = methods
+ self.enums = enums
+ self.unions = unions
+ self.templateArgs = templateArgs
+ self.templateSpecialization = templateSpecialization
+ self.isStruct = isStruct
+ self.disallowCopyConstruction = disallowCopyConstruction
+ self.indent = indent
+ self.defaultVisibility = "public" if isStruct else "private"
+ self.decorators = decorators
+ self.extradeclarations = extradeclarations
+ self.extradefinitions = extradefinitions
+
+ def getNameString(self):
+ className = self.name
+ if self.templateSpecialization:
+ className += "<%s>" % ", ".join(
+ [str(a) for a in self.templateSpecialization]
+ )
+ return className
+
+ def declare(self):
+ result = ""
+ if self.templateArgs:
+ templateArgs = [a.declare() for a in self.templateArgs]
+ templateArgs = templateArgs[len(self.templateSpecialization) :]
+ result += "template <%s>\n" % ",".join([str(a) for a in templateArgs])
+
+ type = "struct" if self.isStruct else "class"
+
+ if self.templateSpecialization:
+ specialization = "<%s>" % ", ".join(
+ [str(a) for a in self.templateSpecialization]
+ )
+ else:
+ specialization = ""
+
+ myself = "%s %s%s" % (type, self.name, specialization)
+ if self.decorators != "":
+ myself += " " + self.decorators
+ result += myself
+
+ if self.bases:
+ inherit = " : "
+ result += inherit
+ # Grab our first base
+ baseItems = [CGGeneric(b.declare(self)) for b in self.bases]
+ bases = baseItems[:1]
+ # Indent the rest
+ bases.extend(
+ CGIndenter(b, len(myself) + len(inherit)) for b in baseItems[1:]
+ )
+ result += ",\n".join(b.define() for b in bases)
+
+ result += "\n{\n"
+
+ result += self.extradeclarations
+
+ def declareMembers(cgClass, memberList, defaultVisibility):
+ members = {"private": [], "protected": [], "public": []}
+
+ for member in memberList:
+ members[member.visibility].append(member)
+
+ if defaultVisibility == "public":
+ order = ["public", "protected", "private"]
+ else:
+ order = ["private", "protected", "public"]
+
+ result = ""
+
+ lastVisibility = defaultVisibility
+ for visibility in order:
+ list = members[visibility]
+ if list:
+ if visibility != lastVisibility:
+ result += visibility + ":\n"
+ for member in list:
+ result += indent(member.declare(cgClass))
+ lastVisibility = visibility
+ return (result, lastVisibility)
+
+ if self.disallowCopyConstruction:
+
+ class DisallowedCopyConstructor(object):
+ def __init__(self):
+ self.visibility = "private"
+
+ def declare(self, cgClass):
+ name = cgClass.getNameString()
+ return (
+ "%s(const %s&) = delete;\n"
+ "%s& operator=(const %s&) = delete;\n"
+ % (name, name, name, name)
+ )
+
+ disallowedCopyConstructors = [DisallowedCopyConstructor()]
+ else:
+ disallowedCopyConstructors = []
+
+ order = [
+ self.enums,
+ self.unions,
+ self.members,
+ self.constructors + disallowedCopyConstructors,
+ self.destructors,
+ self.methods,
+ ]
+
+ lastVisibility = self.defaultVisibility
+ pieces = []
+ for memberList in order:
+ code, lastVisibility = declareMembers(self, memberList, lastVisibility)
+
+ if code:
+ code = code.rstrip() + "\n" # remove extra blank lines at the end
+ pieces.append(code)
+
+ result += "\n".join(pieces)
+ result += "};\n"
+ result = indent(result, len(self.indent))
+ return result
+
+ def define(self):
+ def defineMembers(cgClass, memberList, itemCount, separator=""):
+ result = ""
+ for member in memberList:
+ if itemCount != 0:
+ result = result + separator
+ definition = member.define(cgClass)
+ if definition:
+ # Member variables would only produce empty lines here.
+ result += definition
+ itemCount += 1
+ return (result, itemCount)
+
+ order = [
+ (self.members, ""),
+ (self.constructors, "\n"),
+ (self.destructors, "\n"),
+ (self.methods, "\n"),
+ ]
+
+ result = self.extradefinitions
+ itemCount = 0
+ for memberList, separator in order:
+ memberString, itemCount = defineMembers(
+ self, memberList, itemCount, separator
+ )
+ result = result + memberString
+ return result
+
+
+class CGResolveOwnPropertyViaResolve(CGAbstractBindingMethod):
+ """
+ An implementation of Xray ResolveOwnProperty stuff for things that have a
+ resolve hook.
+ """
+
+ def __init__(self, descriptor):
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "wrapper"),
+ Argument("JS::Handle<JSObject*>", "obj"),
+ Argument("JS::Handle<jsid>", "id"),
+ Argument("JS::MutableHandle<Maybe<JS::PropertyDescriptor>>", "desc"),
+ ]
+ CGAbstractBindingMethod.__init__(
+ self,
+ descriptor,
+ "ResolveOwnPropertyViaResolve",
+ args,
+ getThisObj="",
+ callArgs="",
+ )
+
+ def generate_code(self):
+ return CGGeneric(
+ dedent(
+ """
+ {
+ // Since we're dealing with an Xray, do the resolve on the
+ // underlying object first. That gives it a chance to
+ // define properties on the actual object as needed, and
+ // then use the fact that it created the objects as a flag
+ // to avoid re-resolving the properties if someone deletes
+ // them.
+ JSAutoRealm ar(cx, obj);
+ JS_MarkCrossZoneId(cx, id);
+ JS::Rooted<mozilla::Maybe<JS::PropertyDescriptor>> objDesc(cx);
+ if (!self->DoResolve(cx, obj, id, &objDesc)) {
+ return false;
+ }
+ // If desc->value() is undefined, then the DoResolve call
+ // has already defined the property on the object. Don't
+ // try to also define it.
+ if (objDesc.isSome() &&
+ !objDesc->value().isUndefined()) {
+ JS::Rooted<JS::PropertyDescriptor> defineDesc(cx, *objDesc);
+ if (!JS_DefinePropertyById(cx, obj, id, defineDesc)) {
+ return false;
+ }
+ }
+ }
+ return self->DoResolve(cx, wrapper, id, desc);
+ """
+ )
+ )
+
+
+class CGEnumerateOwnPropertiesViaGetOwnPropertyNames(CGAbstractBindingMethod):
+ """
+ An implementation of Xray EnumerateOwnProperties stuff for things
+ that have a resolve hook.
+ """
+
+ def __init__(self, descriptor):
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "wrapper"),
+ Argument("JS::Handle<JSObject*>", "obj"),
+ Argument("JS::MutableHandleVector<jsid>", "props"),
+ ]
+ CGAbstractBindingMethod.__init__(
+ self,
+ descriptor,
+ "EnumerateOwnPropertiesViaGetOwnPropertyNames",
+ args,
+ getThisObj="",
+ callArgs="",
+ )
+
+ def generate_code(self):
+ return CGGeneric(
+ dedent(
+ """
+ FastErrorResult rv;
+ // This wants all own props, not just enumerable ones.
+ self->GetOwnPropertyNames(cx, props, false, rv);
+ if (rv.MaybeSetPendingException(cx)) {
+ return false;
+ }
+ return true;
+ """
+ )
+ )
+
+
+class CGPrototypeTraitsClass(CGClass):
+ def __init__(self, descriptor, indent=""):
+ templateArgs = [Argument("prototypes::ID", "PrototypeID")]
+ templateSpecialization = ["prototypes::id::" + descriptor.name]
+ enums = [ClassEnum("", ["Depth"], [descriptor.interface.inheritanceDepth()])]
+ CGClass.__init__(
+ self,
+ "PrototypeTraits",
+ indent=indent,
+ templateArgs=templateArgs,
+ templateSpecialization=templateSpecialization,
+ enums=enums,
+ isStruct=True,
+ )
+
+ def deps(self):
+ return set()
+
+
+class CGClassForwardDeclare(CGThing):
+ def __init__(self, name, isStruct=False):
+ CGThing.__init__(self)
+ self.name = name
+ self.isStruct = isStruct
+
+ def declare(self):
+ type = "struct" if self.isStruct else "class"
+ return "%s %s;\n" % (type, self.name)
+
+ def define(self):
+ # Header only
+ return ""
+
+ def deps(self):
+ return set()
+
+
+class CGProxySpecialOperation(CGPerSignatureCall):
+ """
+ Base class for classes for calling an indexed or named special operation
+ (don't use this directly, use the derived classes below).
+
+ If checkFound is False, will just assert that the prop is found instead of
+ checking that it is before wrapping the value.
+
+ resultVar: See the docstring for CGCallGenerator.
+
+ foundVar: For getters and deleters, the generated code can also set a bool
+ variable, declared by the caller, if the given indexed or named property
+ already existed. If the caller wants this, it should pass the name of the
+ bool variable as the foundVar keyword argument to the constructor. The
+ caller is responsible for declaring the variable and initializing it to
+ false.
+ """
+
+ def __init__(
+ self,
+ descriptor,
+ operation,
+ checkFound=True,
+ argumentHandleValue=None,
+ resultVar=None,
+ foundVar=None,
+ ):
+ self.checkFound = checkFound
+ self.foundVar = foundVar or "found"
+
+ nativeName = MakeNativeName(descriptor.binaryNameFor(operation, False))
+ operation = descriptor.operations[operation]
+ assert len(operation.signatures()) == 1
+ signature = operation.signatures()[0]
+
+ returnType, arguments = signature
+
+ # We pass len(arguments) as the final argument so that the
+ # CGPerSignatureCall won't do any argument conversion of its own.
+ CGPerSignatureCall.__init__(
+ self,
+ returnType,
+ arguments,
+ nativeName,
+ False,
+ descriptor,
+ operation,
+ len(arguments),
+ resultVar=resultVar,
+ objectName="proxy",
+ )
+
+ if operation.isSetter():
+ # arguments[0] is the index or name of the item that we're setting.
+ argument = arguments[1]
+ info = getJSToNativeConversionInfo(
+ argument.type,
+ descriptor,
+ sourceDescription=(
+ "value being assigned to %s setter"
+ % descriptor.interface.identifier.name
+ ),
+ )
+ if argumentHandleValue is None:
+ argumentHandleValue = "desc.value()"
+ rootedValue = fill(
+ """
+ JS::Rooted<JS::Value> rootedValue(cx, ${argumentHandleValue});
+ """,
+ argumentHandleValue=argumentHandleValue,
+ )
+ templateValues = {
+ "declName": argument.identifier.name,
+ "holderName": argument.identifier.name + "_holder",
+ "val": argumentHandleValue,
+ "maybeMutableVal": "&rootedValue",
+ "obj": "obj",
+ "passedToJSImpl": "false",
+ }
+ self.cgRoot.prepend(instantiateJSToNativeConversion(info, templateValues))
+ # rootedValue needs to come before the conversion, so we
+ # need to prepend it last.
+ self.cgRoot.prepend(CGGeneric(rootedValue))
+ elif operation.isGetter() or operation.isDeleter():
+ if foundVar is None:
+ self.cgRoot.prepend(CGGeneric("bool found = false;\n"))
+
+ def getArguments(self):
+ args = [(a, a.identifier.name) for a in self.arguments]
+ if self.idlNode.isGetter() or self.idlNode.isDeleter():
+ args.append(
+ (
+ FakeArgument(BuiltinTypes[IDLBuiltinType.Types.boolean]),
+ self.foundVar,
+ )
+ )
+ return args
+
+ def wrap_return_value(self):
+ if not self.idlNode.isGetter() or self.templateValues is None:
+ return ""
+
+ wrap = CGGeneric(
+ wrapForType(self.returnType, self.descriptor, self.templateValues)
+ )
+ if self.checkFound:
+ wrap = CGIfWrapper(wrap, self.foundVar)
+ else:
+ wrap = CGList([CGGeneric("MOZ_ASSERT(" + self.foundVar + ");\n"), wrap])
+ return "\n" + wrap.define()
+
+
+class CGProxyIndexedOperation(CGProxySpecialOperation):
+ """
+ Class to generate a call to an indexed operation.
+
+ If doUnwrap is False, the caller is responsible for making sure a variable
+ named 'self' holds the C++ object somewhere where the code we generate
+ will see it.
+
+ If checkFound is False, will just assert that the prop is found instead of
+ checking that it is before wrapping the value.
+
+ resultVar: See the docstring for CGCallGenerator.
+
+ foundVar: See the docstring for CGProxySpecialOperation.
+ """
+
+ def __init__(
+ self,
+ descriptor,
+ name,
+ doUnwrap=True,
+ checkFound=True,
+ argumentHandleValue=None,
+ resultVar=None,
+ foundVar=None,
+ ):
+ self.doUnwrap = doUnwrap
+ CGProxySpecialOperation.__init__(
+ self,
+ descriptor,
+ name,
+ checkFound,
+ argumentHandleValue=argumentHandleValue,
+ resultVar=resultVar,
+ foundVar=foundVar,
+ )
+
+ def define(self):
+ # Our first argument is the id we're getting.
+ argName = self.arguments[0].identifier.name
+ if argName == "index":
+ # We already have our index in a variable with that name
+ setIndex = ""
+ else:
+ setIndex = "uint32_t %s = index;\n" % argName
+ if self.doUnwrap:
+ unwrap = "%s* self = UnwrapProxy(proxy);\n" % self.descriptor.nativeType
+ else:
+ unwrap = ""
+ return setIndex + unwrap + CGProxySpecialOperation.define(self)
+
+
+class CGProxyIndexedGetter(CGProxyIndexedOperation):
+ """
+ Class to generate a call to an indexed getter. If templateValues is not None
+ the returned value will be wrapped with wrapForType using templateValues.
+
+ If doUnwrap is False, the caller is responsible for making sure a variable
+ named 'self' holds the C++ object somewhere where the code we generate
+ will see it.
+
+ If checkFound is False, will just assert that the prop is found instead of
+ checking that it is before wrapping the value.
+
+ foundVar: See the docstring for CGProxySpecialOperation.
+ """
+
+ def __init__(
+ self,
+ descriptor,
+ templateValues=None,
+ doUnwrap=True,
+ checkFound=True,
+ foundVar=None,
+ ):
+ self.templateValues = templateValues
+ CGProxyIndexedOperation.__init__(
+ self, descriptor, "IndexedGetter", doUnwrap, checkFound, foundVar=foundVar
+ )
+
+
+class CGProxyIndexedPresenceChecker(CGProxyIndexedGetter):
+ """
+ Class to generate a call that checks whether an indexed property exists.
+
+ For now, we just delegate to CGProxyIndexedGetter
+
+ foundVar: See the docstring for CGProxySpecialOperation.
+ """
+
+ def __init__(self, descriptor, foundVar):
+ CGProxyIndexedGetter.__init__(self, descriptor, foundVar=foundVar)
+ self.cgRoot.append(CGGeneric("(void)result;\n"))
+
+
+class CGProxyIndexedSetter(CGProxyIndexedOperation):
+ """
+ Class to generate a call to an indexed setter.
+ """
+
+ def __init__(self, descriptor, argumentHandleValue=None):
+ CGProxyIndexedOperation.__init__(
+ self, descriptor, "IndexedSetter", argumentHandleValue=argumentHandleValue
+ )
+
+
+class CGProxyNamedOperation(CGProxySpecialOperation):
+ """
+ Class to generate a call to a named operation.
+
+ 'value' is the jsval to use for the name; None indicates that it should be
+ gotten from the property id.
+
+ resultVar: See the docstring for CGCallGenerator.
+
+ foundVar: See the docstring for CGProxySpecialOperation.
+
+ tailCode: if we end up with a non-symbol string id, run this code after
+ we do all our other work.
+ """
+
+ def __init__(
+ self,
+ descriptor,
+ name,
+ value=None,
+ argumentHandleValue=None,
+ resultVar=None,
+ foundVar=None,
+ tailCode="",
+ ):
+ CGProxySpecialOperation.__init__(
+ self,
+ descriptor,
+ name,
+ argumentHandleValue=argumentHandleValue,
+ resultVar=resultVar,
+ foundVar=foundVar,
+ )
+ self.value = value
+ self.tailCode = tailCode
+
+ def define(self):
+ # Our first argument is the id we're getting.
+ argName = self.arguments[0].identifier.name
+ if argName == "id":
+ # deal with the name collision
+ decls = "JS::Rooted<jsid> id_(cx, id);\n"
+ idName = "id_"
+ else:
+ decls = ""
+ idName = "id"
+
+ decls += "FakeString<char16_t> %s;\n" % argName
+
+ main = fill(
+ """
+ ${nativeType}* self = UnwrapProxy(proxy);
+ $*{op}
+ $*{tailCode}
+ """,
+ nativeType=self.descriptor.nativeType,
+ op=CGProxySpecialOperation.define(self),
+ tailCode=self.tailCode,
+ )
+
+ if self.value is None:
+ return fill(
+ """
+ $*{decls}
+ bool isSymbol;
+ if (!ConvertIdToString(cx, ${idName}, ${argName}, isSymbol)) {
+ return false;
+ }
+ if (!isSymbol) {
+ $*{main}
+ }
+ """,
+ decls=decls,
+ idName=idName,
+ argName=argName,
+ main=main,
+ )
+
+ # Sadly, we have to set up nameVal even if we have an atom id,
+ # because we don't know for sure, and we can end up needing it
+ # so it needs to be higher up the stack. Using a Maybe here
+ # seems like probable overkill.
+ return fill(
+ """
+ $*{decls}
+ JS::Rooted<JS::Value> nameVal(cx, ${value});
+ if (!nameVal.isSymbol()) {
+ if (!ConvertJSValueToString(cx, nameVal, eStringify, eStringify,
+ ${argName})) {
+ return false;
+ }
+ $*{main}
+ }
+ """,
+ decls=decls,
+ value=self.value,
+ argName=argName,
+ main=main,
+ )
+
+
+class CGProxyNamedGetter(CGProxyNamedOperation):
+ """
+ Class to generate a call to an named getter. If templateValues is not None
+ the returned value will be wrapped with wrapForType using templateValues.
+ 'value' is the jsval to use for the name; None indicates that it should be
+ gotten from the property id.
+
+ foundVar: See the docstring for CGProxySpecialOperation.
+ """
+
+ def __init__(self, descriptor, templateValues=None, value=None, foundVar=None):
+ self.templateValues = templateValues
+ CGProxyNamedOperation.__init__(
+ self, descriptor, "NamedGetter", value, foundVar=foundVar
+ )
+
+
+class CGProxyNamedPresenceChecker(CGProxyNamedGetter):
+ """
+ Class to generate a call that checks whether a named property exists.
+
+ For now, we just delegate to CGProxyNamedGetter
+
+ foundVar: See the docstring for CGProxySpecialOperation.
+ """
+
+ def __init__(self, descriptor, foundVar=None):
+ CGProxyNamedGetter.__init__(self, descriptor, foundVar=foundVar)
+ self.cgRoot.append(CGGeneric("(void)result;\n"))
+
+
+class CGProxyNamedSetter(CGProxyNamedOperation):
+ """
+ Class to generate a call to a named setter.
+ """
+
+ def __init__(self, descriptor, tailCode, argumentHandleValue=None):
+ CGProxyNamedOperation.__init__(
+ self,
+ descriptor,
+ "NamedSetter",
+ argumentHandleValue=argumentHandleValue,
+ tailCode=tailCode,
+ )
+
+
+class CGProxyNamedDeleter(CGProxyNamedOperation):
+ """
+ Class to generate a call to a named deleter.
+
+ resultVar: See the docstring for CGCallGenerator.
+
+ foundVar: See the docstring for CGProxySpecialOperation.
+ """
+
+ def __init__(self, descriptor, resultVar=None, foundVar=None):
+ CGProxyNamedOperation.__init__(
+ self, descriptor, "NamedDeleter", resultVar=resultVar, foundVar=foundVar
+ )
+
+
+class CGProxyIsProxy(CGAbstractMethod):
+ def __init__(self, descriptor):
+ args = [Argument("JSObject*", "obj")]
+ CGAbstractMethod.__init__(
+ self, descriptor, "IsProxy", "bool", args, alwaysInline=True
+ )
+
+ def declare(self):
+ return ""
+
+ def definition_body(self):
+ return "return js::IsProxy(obj) && js::GetProxyHandler(obj) == DOMProxyHandler::getInstance();\n"
+
+
+class CGProxyUnwrap(CGAbstractMethod):
+ def __init__(self, descriptor):
+ args = [Argument("JSObject*", "obj")]
+ CGAbstractMethod.__init__(
+ self,
+ descriptor,
+ "UnwrapProxy",
+ descriptor.nativeType + "*",
+ args,
+ alwaysInline=True,
+ )
+
+ def declare(self):
+ return ""
+
+ def definition_body(self):
+ return fill(
+ """
+ MOZ_ASSERT(js::IsProxy(obj));
+ if (js::GetProxyHandler(obj) != DOMProxyHandler::getInstance()) {
+ MOZ_ASSERT(xpc::WrapperFactory::IsXrayWrapper(obj));
+ obj = js::UncheckedUnwrap(obj);
+ }
+ MOZ_ASSERT(IsProxy(obj));
+ return static_cast<${type}*>(js::GetProxyReservedSlot(obj, DOM_OBJECT_SLOT).toPrivate());
+ """,
+ type=self.descriptor.nativeType,
+ )
+
+
+MISSING_PROP_PREF = "dom.missing_prop_counters.enabled"
+
+
+def missingPropUseCountersForDescriptor(desc):
+ if not desc.needsMissingPropUseCounters:
+ return ""
+
+ return fill(
+ """
+ if (StaticPrefs::${pref}() && id.isAtom()) {
+ CountMaybeMissingProperty(proxy, id);
+ }
+
+ """,
+ pref=prefIdentifier(MISSING_PROP_PREF),
+ )
+
+
+def findAncestorWithInstrumentedProps(desc):
+ """
+ Find an ancestor of desc.interface (not including desc.interface
+ itself) that has instrumented properties on it. May return None
+ if there is no such ancestor.
+ """
+ ancestor = desc.interface.parent
+ while ancestor:
+ if ancestor.getExtendedAttribute("InstrumentedProps"):
+ return ancestor
+ ancestor = ancestor.parent
+ return None
+
+
+class CGCountMaybeMissingProperty(CGAbstractMethod):
+ def __init__(self, descriptor):
+ """
+ Returns whether we counted the property involved.
+ """
+ CGAbstractMethod.__init__(
+ self,
+ descriptor,
+ "CountMaybeMissingProperty",
+ "bool",
+ [
+ Argument("JS::Handle<JSObject*>", "proxy"),
+ Argument("JS::Handle<jsid>", "id"),
+ ],
+ )
+
+ def gen_switch(self, switchDecriptor):
+ """
+ Generate a switch from the switch descriptor. The descriptor
+ dictionary must have the following properties:
+
+ 1) A "precondition" property that contains code to run before the
+ switch statement. Its value ie a string.
+ 2) A "condition" property for the condition. Its value is a string.
+ 3) A "cases" property. Its value is an object that has property names
+ corresponding to the case labels. The values of those properties
+ are either new switch descriptor dictionaries (which will then
+ generate nested switches) or strings to use for case bodies.
+ """
+ cases = []
+ for label, body in sorted(six.iteritems(switchDecriptor["cases"])):
+ if isinstance(body, dict):
+ body = self.gen_switch(body)
+ cases.append(
+ fill(
+ """
+ case ${label}: {
+ $*{body}
+ break;
+ }
+ """,
+ label=label,
+ body=body,
+ )
+ )
+ return fill(
+ """
+ $*{precondition}
+ switch (${condition}) {
+ $*{cases}
+ }
+ """,
+ precondition=switchDecriptor["precondition"],
+ condition=switchDecriptor["condition"],
+ cases="".join(cases),
+ )
+
+ def charSwitch(self, props, charIndex):
+ """
+ Create a switch for the given props, based on the first char where
+ they start to differ at index charIndex or more. Each prop is a tuple
+ containing interface name and prop name.
+
+ Incoming props should be a sorted list.
+ """
+ if len(props) == 1:
+ # We're down to one string: just check whether we match it.
+ return fill(
+ """
+ if (JS_LinearStringEqualsLiteral(str, "${name}")) {
+ counter.emplace(eUseCounter_${iface}_${name});
+ }
+ """,
+ iface=self.descriptor.name,
+ name=props[0],
+ )
+
+ switch = dict()
+ if charIndex == 0:
+ switch["precondition"] = "StringIdChars chars(nogc, str);\n"
+ else:
+ switch["precondition"] = ""
+
+ # Find the first place where we might actually have a difference.
+ while all(prop[charIndex] == props[0][charIndex] for prop in props):
+ charIndex += 1
+
+ switch["condition"] = "chars[%d]" % charIndex
+ switch["cases"] = dict()
+ current_props = None
+ curChar = None
+ idx = 0
+ while idx < len(props):
+ nextChar = "'%s'" % props[idx][charIndex]
+ if nextChar != curChar:
+ if curChar:
+ switch["cases"][curChar] = self.charSwitch(
+ current_props, charIndex + 1
+ )
+ current_props = []
+ curChar = nextChar
+ current_props.append(props[idx])
+ idx += 1
+ switch["cases"][curChar] = self.charSwitch(current_props, charIndex + 1)
+ return switch
+
+ def definition_body(self):
+ ancestor = findAncestorWithInstrumentedProps(self.descriptor)
+
+ if ancestor:
+ body = fill(
+ """
+ if (${ancestor}_Binding::CountMaybeMissingProperty(proxy, id)) {
+ return true;
+ }
+
+ """,
+ ancestor=ancestor.identifier.name,
+ )
+ else:
+ body = ""
+
+ instrumentedProps = self.descriptor.instrumentedProps
+ if not instrumentedProps:
+ return body + dedent(
+ """
+ return false;
+ """
+ )
+
+ lengths = set(len(prop) for prop in instrumentedProps)
+ switchDesc = {"condition": "JS::GetLinearStringLength(str)", "precondition": ""}
+ switchDesc["cases"] = dict()
+ for length in sorted(lengths):
+ switchDesc["cases"][str(length)] = self.charSwitch(
+ list(sorted(prop for prop in instrumentedProps if len(prop) == length)),
+ 0,
+ )
+
+ return body + fill(
+ """
+ MOZ_ASSERT(StaticPrefs::${pref}() && id.isAtom());
+ Maybe<UseCounter> counter;
+ {
+ // Scope for our no-GC section, so we don't need to rely on SetUseCounter not GCing.
+ JS::AutoCheckCannotGC nogc;
+ JSLinearString* str = JS::AtomToLinearString(id.toAtom());
+ // Don't waste time fetching the chars until we've done the length switch.
+ $*{switch}
+ }
+ if (counter) {
+ SetUseCounter(proxy, *counter);
+ return true;
+ }
+
+ return false;
+ """,
+ pref=prefIdentifier(MISSING_PROP_PREF),
+ switch=self.gen_switch(switchDesc),
+ )
+
+
+class CGDOMJSProxyHandler_getOwnPropDescriptor(ClassMethod):
+ def __init__(self, descriptor):
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "proxy"),
+ Argument("JS::Handle<jsid>", "id"),
+ Argument("bool", "ignoreNamedProps"),
+ Argument("JS::MutableHandle<Maybe<JS::PropertyDescriptor>>", "desc"),
+ ]
+ ClassMethod.__init__(
+ self,
+ "getOwnPropDescriptor",
+ "bool",
+ args,
+ virtual=True,
+ override=True,
+ const=True,
+ )
+ self.descriptor = descriptor
+
+ def getBody(self):
+ indexedSetter = self.descriptor.operations["IndexedSetter"]
+
+ if self.descriptor.isMaybeCrossOriginObject():
+ xrayDecl = dedent(
+ """
+ MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy));
+ MOZ_ASSERT(IsPlatformObjectSameOrigin(cx, proxy),
+ "getOwnPropertyDescriptor() and set() should have dealt");
+ MOZ_ASSERT(js::IsObjectInContextCompartment(proxy, cx),
+ "getOwnPropertyDescriptor() and set() should have dealt");
+
+ """
+ )
+ xrayCheck = ""
+ else:
+ xrayDecl = "bool isXray = xpc::WrapperFactory::IsXrayWrapper(proxy);\n"
+ xrayCheck = "!isXray &&"
+
+ if self.descriptor.supportsIndexedProperties():
+ attributes = [
+ "JS::PropertyAttribute::Configurable",
+ "JS::PropertyAttribute::Enumerable",
+ ]
+ if indexedSetter is not None:
+ attributes.append("JS::PropertyAttribute::Writable")
+ setDescriptor = (
+ "desc.set(mozilla::Some(JS::PropertyDescriptor::Data(value, { %s })));\nreturn true;\n"
+ % ", ".join(attributes)
+ )
+ templateValues = {
+ "jsvalRef": "value",
+ "jsvalHandle": "&value",
+ "obj": "proxy",
+ "successCode": setDescriptor,
+ }
+ getIndexed = fill(
+ """
+ uint32_t index = GetArrayIndexFromId(id);
+ if (IsArrayIndex(index)) {
+ JS::Rooted<JS::Value> value(cx);
+ $*{callGetter}
+ }
+
+ """,
+ callGetter=CGProxyIndexedGetter(
+ self.descriptor, templateValues
+ ).define(),
+ )
+ else:
+ getIndexed = ""
+
+ missingPropUseCounters = missingPropUseCountersForDescriptor(self.descriptor)
+
+ if self.descriptor.supportsNamedProperties():
+ operations = self.descriptor.operations
+ attributes = ["JS::PropertyAttribute::Configurable"]
+ if self.descriptor.namedPropertiesEnumerable:
+ attributes.append("JS::PropertyAttribute::Enumerable")
+ if operations["NamedSetter"] is not None:
+ attributes.append("JS::PropertyAttribute::Writable")
+ setDescriptor = (
+ "desc.set(mozilla::Some(JS::PropertyDescriptor::Data(value, { %s })));\nreturn true;\n"
+ % ", ".join(attributes)
+ )
+ templateValues = {
+ "jsvalRef": "value",
+ "jsvalHandle": "&value",
+ "obj": "proxy",
+ "successCode": setDescriptor,
+ }
+
+ computeCondition = dedent(
+ """
+ bool hasOnProto;
+ if (!HasPropertyOnPrototype(cx, proxy, id, &hasOnProto)) {
+ return false;
+ }
+ callNamedGetter = !hasOnProto;
+ """
+ )
+ if self.descriptor.interface.getExtendedAttribute("LegacyOverrideBuiltIns"):
+ computeCondition = fill(
+ """
+ if (!isXray) {
+ callNamedGetter = true;
+ } else {
+ $*{hasOnProto}
+ }
+ """,
+ hasOnProto=computeCondition,
+ )
+
+ outerCondition = "!ignoreNamedProps"
+ if self.descriptor.supportsIndexedProperties():
+ outerCondition = "!IsArrayIndex(index) && " + outerCondition
+
+ namedGetCode = CGProxyNamedGetter(self.descriptor, templateValues).define()
+ namedGet = fill(
+ """
+ bool callNamedGetter = false;
+ if (${outerCondition}) {
+ $*{computeCondition}
+ }
+ if (callNamedGetter) {
+ JS::Rooted<JS::Value> value(cx);
+ $*{namedGetCode}
+ }
+ """,
+ outerCondition=outerCondition,
+ computeCondition=computeCondition,
+ namedGetCode=namedGetCode,
+ )
+ namedGet += "\n"
+ else:
+ namedGet = ""
+
+ return fill(
+ """
+ $*{xrayDecl}
+ $*{getIndexed}
+ $*{missingPropUseCounters}
+ JS::Rooted<JSObject*> expando(cx);
+ if (${xrayCheck}(expando = GetExpandoObject(proxy))) {
+ if (!JS_GetOwnPropertyDescriptorById(cx, expando, id, desc)) {
+ return false;
+ }
+ if (desc.isSome()) {
+ return true;
+ }
+ }
+
+ $*{namedGet}
+ desc.reset();
+ return true;
+ """,
+ xrayDecl=xrayDecl,
+ xrayCheck=xrayCheck,
+ getIndexed=getIndexed,
+ missingPropUseCounters=missingPropUseCounters,
+ namedGet=namedGet,
+ )
+
+
+class CGDOMJSProxyHandler_defineProperty(ClassMethod):
+ def __init__(self, descriptor):
+ # The usual convention is to name the ObjectOpResult out-parameter
+ # `result`, but that name is a bit overloaded around here.
+ args = [
+ Argument("JSContext*", "cx_"),
+ Argument("JS::Handle<JSObject*>", "proxy"),
+ Argument("JS::Handle<jsid>", "id"),
+ Argument("JS::Handle<JS::PropertyDescriptor>", "desc"),
+ Argument("JS::ObjectOpResult&", "opresult"),
+ Argument("bool*", "done"),
+ ]
+ ClassMethod.__init__(
+ self,
+ "defineProperty",
+ "bool",
+ args,
+ virtual=True,
+ override=True,
+ const=True,
+ )
+ self.descriptor = descriptor
+
+ def getBody(self):
+ set = ""
+
+ indexedSetter = self.descriptor.operations["IndexedSetter"]
+ if indexedSetter:
+ error_label = CGSpecializedMethod.error_reporting_label_helper(
+ self.descriptor, indexedSetter, isConstructor=False
+ )
+ if error_label:
+ cxDecl = fill(
+ """
+ BindingCallContext cx(cx_, "${error_label}");
+ """,
+ error_label=error_label,
+ )
+ else:
+ cxDecl = dedent(
+ """
+ JSContext* cx = cx_;
+ """
+ )
+ set += fill(
+ """
+ uint32_t index = GetArrayIndexFromId(id);
+ if (IsArrayIndex(index)) {
+ $*{cxDecl}
+ *done = true;
+ // https://heycam.github.io/webidl/#legacy-platform-object-defineownproperty
+ // Step 1.1. The no-indexed-setter case is handled by step 1.2.
+ if (!desc.isDataDescriptor()) {
+ return opresult.failNotDataDescriptor();
+ }
+
+ $*{callSetter}
+ return opresult.succeed();
+ }
+ """,
+ cxDecl=cxDecl,
+ callSetter=CGProxyIndexedSetter(self.descriptor).define(),
+ )
+ elif self.descriptor.supportsIndexedProperties():
+ # We allow untrusted content to prevent Xrays from setting a
+ # property if that property is an indexed property and we have no
+ # indexed setter. That's how the object would normally behave if
+ # you tried to set the property on it. That means we don't need to
+ # do anything special for Xrays here.
+ set += dedent(
+ """
+ if (IsArrayIndex(GetArrayIndexFromId(id))) {
+ *done = true;
+ return opresult.failNoIndexedSetter();
+ }
+ """
+ )
+
+ namedSetter = self.descriptor.operations["NamedSetter"]
+ if namedSetter:
+ error_label = CGSpecializedMethod.error_reporting_label_helper(
+ self.descriptor, namedSetter, isConstructor=False
+ )
+ if error_label:
+ set += fill(
+ """
+ BindingCallContext cx(cx_, "${error_label}");
+ """,
+ error_label=error_label,
+ )
+ else:
+ set += dedent(
+ """
+ JSContext* cx = cx_;
+ """
+ )
+ if self.descriptor.hasLegacyUnforgeableMembers:
+ raise TypeError(
+ "Can't handle a named setter on an interface "
+ "that has unforgeables. Figure out how that "
+ "should work!"
+ )
+ tailCode = dedent(
+ """
+ *done = true;
+ return opresult.succeed();
+ """
+ )
+ set += CGProxyNamedSetter(self.descriptor, tailCode).define()
+ else:
+ # We allow untrusted content to prevent Xrays from setting a
+ # property if that property is already a named property on the
+ # object and we have no named setter. That's how the object would
+ # normally behave if you tried to set the property on it. That
+ # means we don't need to do anything special for Xrays here.
+ if self.descriptor.supportsNamedProperties():
+ set += fill(
+ """
+ JSContext* cx = cx_;
+ bool found = false;
+ $*{presenceChecker}
+
+ if (found) {
+ *done = true;
+ return opresult.failNoNamedSetter();
+ }
+ """,
+ presenceChecker=CGProxyNamedPresenceChecker(
+ self.descriptor, foundVar="found"
+ ).define(),
+ )
+ if self.descriptor.isMaybeCrossOriginObject():
+ set += dedent(
+ """
+ MOZ_ASSERT(IsPlatformObjectSameOrigin(cx_, proxy),
+ "Why did the MaybeCrossOriginObject defineProperty override fail?");
+ MOZ_ASSERT(js::IsObjectInContextCompartment(proxy, cx_),
+ "Why did the MaybeCrossOriginObject defineProperty override fail?");
+ """
+ )
+
+ # In all cases we want to tail-call to our base class; we can
+ # always land here for symbols.
+ set += (
+ "return mozilla::dom::DOMProxyHandler::defineProperty(%s);\n"
+ % ", ".join(a.name for a in self.args)
+ )
+ return set
+
+
+def getDeleterBody(descriptor, type, foundVar=None):
+ """
+ type should be "Named" or "Indexed"
+
+ The possible outcomes:
+ - an error happened (the emitted code returns false)
+ - own property not found (foundVar=false, deleteSucceeded=true)
+ - own property found and deleted (foundVar=true, deleteSucceeded=true)
+ - own property found but can't be deleted (foundVar=true, deleteSucceeded=false)
+ """
+ assert type in ("Named", "Indexed")
+ deleter = descriptor.operations[type + "Deleter"]
+ if deleter:
+ assert type == "Named"
+ assert foundVar is not None
+ if descriptor.hasLegacyUnforgeableMembers:
+ raise TypeError(
+ "Can't handle a deleter on an interface "
+ "that has unforgeables. Figure out how "
+ "that should work!"
+ )
+ # See if the deleter method is fallible.
+ t = deleter.signatures()[0][0]
+ if t.isPrimitive() and not t.nullable() and t.tag() == IDLType.Tags.bool:
+ # The deleter method has a boolean return value. When a
+ # property is found, the return value indicates whether it
+ # was successfully deleted.
+ setDS = fill(
+ """
+ if (!${foundVar}) {
+ deleteSucceeded = true;
+ }
+ """,
+ foundVar=foundVar,
+ )
+ else:
+ # No boolean return value: if a property is found,
+ # deleting it always succeeds.
+ setDS = "deleteSucceeded = true;\n"
+
+ body = (
+ CGProxyNamedDeleter(
+ descriptor, resultVar="deleteSucceeded", foundVar=foundVar
+ ).define()
+ + setDS
+ )
+ elif getattr(descriptor, "supports%sProperties" % type)():
+ presenceCheckerClass = globals()["CGProxy%sPresenceChecker" % type]
+ foundDecl = ""
+ if foundVar is None:
+ foundVar = "found"
+ foundDecl = "bool found = false;\n"
+ body = fill(
+ """
+ $*{foundDecl}
+ $*{presenceChecker}
+ deleteSucceeded = !${foundVar};
+ """,
+ foundDecl=foundDecl,
+ presenceChecker=presenceCheckerClass(
+ descriptor, foundVar=foundVar
+ ).define(),
+ foundVar=foundVar,
+ )
+ else:
+ body = None
+ return body
+
+
+class CGDeleteNamedProperty(CGAbstractStaticMethod):
+ def __init__(self, descriptor):
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "xray"),
+ Argument("JS::Handle<JSObject*>", "proxy"),
+ Argument("JS::Handle<jsid>", "id"),
+ Argument("JS::ObjectOpResult&", "opresult"),
+ ]
+ CGAbstractStaticMethod.__init__(
+ self, descriptor, "DeleteNamedProperty", "bool", args
+ )
+
+ def definition_body(self):
+ return fill(
+ """
+ MOZ_ASSERT(xpc::WrapperFactory::IsXrayWrapper(xray));
+ MOZ_ASSERT(js::IsProxy(proxy));
+ MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy));
+ JSAutoRealm ar(cx, proxy);
+ bool deleteSucceeded = false;
+ bool found = false;
+ $*{namedBody}
+ if (!found || deleteSucceeded) {
+ return opresult.succeed();
+ }
+ return opresult.failCantDelete();
+ """,
+ namedBody=getDeleterBody(self.descriptor, "Named", foundVar="found"),
+ )
+
+
+class CGDOMJSProxyHandler_delete(ClassMethod):
+ def __init__(self, descriptor):
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "proxy"),
+ Argument("JS::Handle<jsid>", "id"),
+ Argument("JS::ObjectOpResult&", "opresult"),
+ ]
+ ClassMethod.__init__(
+ self, "delete_", "bool", args, virtual=True, override=True, const=True
+ )
+ self.descriptor = descriptor
+
+ def getBody(self):
+ delete = dedent(
+ """
+ MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
+ "Should not have a XrayWrapper here");
+
+ """
+ )
+
+ if self.descriptor.isMaybeCrossOriginObject():
+ delete += dedent(
+ """
+ if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+ return ReportCrossOriginDenial(cx, id, "delete"_ns);
+ }
+
+ // Safe to enter the Realm of proxy now.
+ JSAutoRealm ar(cx, proxy);
+ JS_MarkCrossZoneId(cx, id);
+ """
+ )
+
+ indexedBody = getDeleterBody(self.descriptor, "Indexed")
+ if indexedBody is not None:
+ # Can't handle cross-origin objects here.
+ assert not self.descriptor.isMaybeCrossOriginObject()
+ delete += fill(
+ """
+ uint32_t index = GetArrayIndexFromId(id);
+ if (IsArrayIndex(index)) {
+ bool deleteSucceeded;
+ $*{indexedBody}
+ return deleteSucceeded ? opresult.succeed() : opresult.failCantDelete();
+ }
+ """,
+ indexedBody=indexedBody,
+ )
+
+ namedBody = getDeleterBody(self.descriptor, "Named", foundVar="found")
+ if namedBody is not None:
+ delete += dedent(
+ """
+ // Try named delete only if the named property visibility
+ // algorithm says the property is visible.
+ bool tryNamedDelete = true;
+ { // Scope for expando
+ JS::Rooted<JSObject*> expando(cx, DOMProxyHandler::GetExpandoObject(proxy));
+ if (expando) {
+ bool hasProp;
+ if (!JS_HasPropertyById(cx, expando, id, &hasProp)) {
+ return false;
+ }
+ tryNamedDelete = !hasProp;
+ }
+ }
+ """
+ )
+
+ if not self.descriptor.interface.getExtendedAttribute(
+ "LegacyOverrideBuiltIns"
+ ):
+ delete += dedent(
+ """
+ if (tryNamedDelete) {
+ bool hasOnProto;
+ if (!HasPropertyOnPrototype(cx, proxy, id, &hasOnProto)) {
+ return false;
+ }
+ tryNamedDelete = !hasOnProto;
+ }
+ """
+ )
+
+ # We always return above for an index id in the case when we support
+ # indexed properties, so we can just treat the id as a name
+ # unconditionally here.
+ delete += fill(
+ """
+ if (tryNamedDelete) {
+ bool found = false;
+ bool deleteSucceeded;
+ $*{namedBody}
+ if (found) {
+ return deleteSucceeded ? opresult.succeed() : opresult.failCantDelete();
+ }
+ }
+ """,
+ namedBody=namedBody,
+ )
+
+ delete += dedent(
+ """
+
+ return dom::DOMProxyHandler::delete_(cx, proxy, id, opresult);
+ """
+ )
+
+ return delete
+
+
+class CGDOMJSProxyHandler_ownPropNames(ClassMethod):
+ def __init__(
+ self,
+ descriptor,
+ ):
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "proxy"),
+ Argument("unsigned", "flags"),
+ Argument("JS::MutableHandleVector<jsid>", "props"),
+ ]
+ ClassMethod.__init__(
+ self, "ownPropNames", "bool", args, virtual=True, override=True, const=True
+ )
+ self.descriptor = descriptor
+
+ def getBody(self):
+ if self.descriptor.isMaybeCrossOriginObject():
+ xrayDecl = dedent(
+ """
+ MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy));
+ if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+ if (!(flags & JSITER_HIDDEN)) {
+ // There are no enumerable cross-origin props, so we're done.
+ return true;
+ }
+
+ JS::Rooted<JSObject*> holder(cx);
+ if (!EnsureHolder(cx, proxy, &holder)) {
+ return false;
+ }
+
+ if (!js::GetPropertyKeys(cx, holder, flags, props)) {
+ return false;
+ }
+
+ return xpc::AppendCrossOriginWhitelistedPropNames(cx, props);
+ }
+
+ """
+ )
+ xrayCheck = ""
+ else:
+ xrayDecl = "bool isXray = xpc::WrapperFactory::IsXrayWrapper(proxy);\n"
+ xrayCheck = "!isXray &&"
+
+ # Per spec, we do indices, then named props, then everything else.
+ if self.descriptor.supportsIndexedProperties():
+ if self.descriptor.lengthNeedsCallerType():
+ callerType = callerTypeGetterForDescriptor(self.descriptor)
+ else:
+ callerType = ""
+ addIndices = fill(
+ """
+
+ uint32_t length = UnwrapProxy(proxy)->Length(${callerType});
+ MOZ_ASSERT(int32_t(length) >= 0);
+ for (int32_t i = 0; i < int32_t(length); ++i) {
+ if (!props.append(JS::PropertyKey::Int(i))) {
+ return false;
+ }
+ }
+ """,
+ callerType=callerType,
+ )
+ else:
+ addIndices = ""
+
+ if self.descriptor.supportsNamedProperties():
+ if self.descriptor.interface.getExtendedAttribute("LegacyOverrideBuiltIns"):
+ shadow = "!isXray"
+ else:
+ shadow = "false"
+
+ if self.descriptor.supportedNamesNeedCallerType():
+ callerType = ", " + callerTypeGetterForDescriptor(self.descriptor)
+ else:
+ callerType = ""
+
+ addNames = fill(
+ """
+ nsTArray<nsString> names;
+ UnwrapProxy(proxy)->GetSupportedNames(names${callerType});
+ if (!AppendNamedPropertyIds(cx, proxy, names, ${shadow}, props)) {
+ return false;
+ }
+ """,
+ callerType=callerType,
+ shadow=shadow,
+ )
+ if not self.descriptor.namedPropertiesEnumerable:
+ addNames = CGIfWrapper(
+ CGGeneric(addNames), "flags & JSITER_HIDDEN"
+ ).define()
+ addNames = "\n" + addNames
+ else:
+ addNames = ""
+
+ addExpandoProps = fill(
+ """
+ JS::Rooted<JSObject*> expando(cx);
+ if (${xrayCheck}(expando = DOMProxyHandler::GetExpandoObject(proxy)) &&
+ !js::GetPropertyKeys(cx, expando, flags, props)) {
+ return false;
+ }
+ """,
+ xrayCheck=xrayCheck,
+ )
+
+ if self.descriptor.isMaybeCrossOriginObject():
+ # We need to enter our compartment (which we might not be
+ # in right now) to get the expando props.
+ addExpandoProps = fill(
+ """
+ { // Scope for accessing the expando.
+ // Safe to enter our compartment, because IsPlatformObjectSameOrigin tested true.
+ JSAutoRealm ar(cx, proxy);
+ $*{addExpandoProps}
+ }
+ for (auto& id : props) {
+ JS_MarkCrossZoneId(cx, id);
+ }
+ """,
+ addExpandoProps=addExpandoProps,
+ )
+
+ return fill(
+ """
+ $*{xrayDecl}
+ $*{addIndices}
+ $*{addNames}
+
+ $*{addExpandoProps}
+
+ return true;
+ """,
+ xrayDecl=xrayDecl,
+ addIndices=addIndices,
+ addNames=addNames,
+ addExpandoProps=addExpandoProps,
+ )
+
+
+class CGDOMJSProxyHandler_hasOwn(ClassMethod):
+ def __init__(self, descriptor):
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "proxy"),
+ Argument("JS::Handle<jsid>", "id"),
+ Argument("bool*", "bp"),
+ ]
+ ClassMethod.__init__(
+ self, "hasOwn", "bool", args, virtual=True, override=True, const=True
+ )
+ self.descriptor = descriptor
+
+ def getBody(self):
+ if self.descriptor.isMaybeCrossOriginObject():
+ maybeCrossOrigin = dedent(
+ """
+ if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+ // Just hand this off to BaseProxyHandler to do the slow-path thing.
+ // The BaseProxyHandler code is OK with this happening without entering the
+ // compartment of "proxy", which is important to get the right answers.
+ return js::BaseProxyHandler::hasOwn(cx, proxy, id, bp);
+ }
+
+ // Now safe to enter the Realm of proxy and do the rest of the work there.
+ JSAutoRealm ar(cx, proxy);
+ JS_MarkCrossZoneId(cx, id);
+ """
+ )
+ else:
+ maybeCrossOrigin = ""
+
+ if self.descriptor.supportsIndexedProperties():
+ indexed = fill(
+ """
+ uint32_t index = GetArrayIndexFromId(id);
+ if (IsArrayIndex(index)) {
+ bool found = false;
+ $*{presenceChecker}
+
+ *bp = found;
+ return true;
+ }
+
+ """,
+ presenceChecker=CGProxyIndexedPresenceChecker(
+ self.descriptor, foundVar="found"
+ ).define(),
+ )
+ else:
+ indexed = ""
+
+ if self.descriptor.supportsNamedProperties():
+ # If we support indexed properties we always return above for index
+ # property names, so no need to check for those here.
+ named = fill(
+ """
+ bool found = false;
+ $*{presenceChecker}
+
+ *bp = found;
+ """,
+ presenceChecker=CGProxyNamedPresenceChecker(
+ self.descriptor, foundVar="found"
+ ).define(),
+ )
+ if not self.descriptor.interface.getExtendedAttribute(
+ "LegacyOverrideBuiltIns"
+ ):
+ named = fill(
+ """
+ bool hasOnProto;
+ if (!HasPropertyOnPrototype(cx, proxy, id, &hasOnProto)) {
+ return false;
+ }
+ if (!hasOnProto) {
+ $*{protoLacksProperty}
+ return true;
+ }
+ """,
+ protoLacksProperty=named,
+ )
+ named += "*bp = false;\n"
+ else:
+ named += "\n"
+ else:
+ named = "*bp = false;\n"
+
+ missingPropUseCounters = missingPropUseCountersForDescriptor(self.descriptor)
+
+ return fill(
+ """
+ MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
+ "Should not have a XrayWrapper here");
+ $*{maybeCrossOrigin}
+ $*{indexed}
+
+ $*{missingPropUseCounters}
+ JS::Rooted<JSObject*> expando(cx, GetExpandoObject(proxy));
+ if (expando) {
+ bool b = true;
+ bool ok = JS_HasPropertyById(cx, expando, id, &b);
+ *bp = !!b;
+ if (!ok || *bp) {
+ return ok;
+ }
+ }
+
+ $*{named}
+ return true;
+ """,
+ maybeCrossOrigin=maybeCrossOrigin,
+ indexed=indexed,
+ missingPropUseCounters=missingPropUseCounters,
+ named=named,
+ )
+
+
+class CGDOMJSProxyHandler_get(ClassMethod):
+ def __init__(self, descriptor):
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "proxy"),
+ Argument("JS::Handle<JS::Value>", "receiver"),
+ Argument("JS::Handle<jsid>", "id"),
+ Argument("JS::MutableHandle<JS::Value>", "vp"),
+ ]
+ ClassMethod.__init__(
+ self, "get", "bool", args, virtual=True, override=True, const=True
+ )
+ self.descriptor = descriptor
+
+ def getBody(self):
+ missingPropUseCounters = missingPropUseCountersForDescriptor(self.descriptor)
+
+ getUnforgeableOrExpando = dedent(
+ """
+ bool expandoHasProp = false;
+ { // Scope for expando
+ JS::Rooted<JSObject*> expando(cx, DOMProxyHandler::GetExpandoObject(proxy));
+ if (expando) {
+ if (!JS_HasPropertyById(cx, expando, id, &expandoHasProp)) {
+ return false;
+ }
+
+ if (expandoHasProp) {
+ // Forward the get to the expando object, but our receiver is whatever our
+ // receiver is.
+ if (!JS_ForwardGetPropertyTo(cx, expando, id, ${receiver}, vp)) {
+ return false;
+ }
+ }
+ }
+ }
+ """
+ )
+
+ getOnPrototype = dedent(
+ """
+ bool foundOnPrototype;
+ if (!GetPropertyOnPrototype(cx, proxy, ${receiver}, id, &foundOnPrototype, vp)) {
+ return false;
+ }
+ """
+ )
+
+ if self.descriptor.isMaybeCrossOriginObject():
+ # We can't handle these for cross-origin objects
+ assert not self.descriptor.supportsIndexedProperties()
+ assert not self.descriptor.supportsNamedProperties()
+
+ return fill(
+ """
+ MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
+ "Should not have a XrayWrapper here");
+
+ if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+ return CrossOriginGet(cx, proxy, receiver, id, vp);
+ }
+
+ $*{missingPropUseCounters}
+ { // Scope for the JSAutoRealm accessing expando and prototype.
+ JSAutoRealm ar(cx, proxy);
+ JS::Rooted<JS::Value> wrappedReceiver(cx, receiver);
+ if (!MaybeWrapValue(cx, &wrappedReceiver)) {
+ return false;
+ }
+ JS_MarkCrossZoneId(cx, id);
+
+ $*{getUnforgeableOrExpando}
+ if (!expandoHasProp) {
+ $*{getOnPrototype}
+ if (!foundOnPrototype) {
+ MOZ_ASSERT(vp.isUndefined());
+ return true;
+ }
+ }
+ }
+
+ return MaybeWrapValue(cx, vp);
+ """,
+ missingPropUseCounters=missingPropUseCountersForDescriptor(
+ self.descriptor
+ ),
+ getUnforgeableOrExpando=fill(
+ getUnforgeableOrExpando, receiver="wrappedReceiver"
+ ),
+ getOnPrototype=fill(getOnPrototype, receiver="wrappedReceiver"),
+ )
+
+ templateValues = {"jsvalRef": "vp", "jsvalHandle": "vp", "obj": "proxy"}
+
+ getUnforgeableOrExpando = fill(
+ getUnforgeableOrExpando, receiver="receiver"
+ ) + dedent(
+ """
+
+ if (expandoHasProp) {
+ return true;
+ }
+ """
+ )
+ if self.descriptor.supportsIndexedProperties():
+ getIndexedOrExpando = fill(
+ """
+ uint32_t index = GetArrayIndexFromId(id);
+ if (IsArrayIndex(index)) {
+ $*{callGetter}
+ // Even if we don't have this index, we don't forward the
+ // get on to our expando object.
+ } else {
+ $*{getUnforgeableOrExpando}
+ }
+ """,
+ callGetter=CGProxyIndexedGetter(
+ self.descriptor, templateValues
+ ).define(),
+ getUnforgeableOrExpando=getUnforgeableOrExpando,
+ )
+ else:
+ getIndexedOrExpando = getUnforgeableOrExpando
+
+ if self.descriptor.supportsNamedProperties():
+ getNamed = CGProxyNamedGetter(self.descriptor, templateValues)
+ if self.descriptor.supportsIndexedProperties():
+ getNamed = CGIfWrapper(getNamed, "!IsArrayIndex(index)")
+ getNamed = getNamed.define() + "\n"
+ else:
+ getNamed = ""
+
+ getOnPrototype = fill(getOnPrototype, receiver="receiver") + dedent(
+ """
+
+ if (foundOnPrototype) {
+ return true;
+ }
+
+ MOZ_ASSERT(vp.isUndefined());
+ """
+ )
+
+ if self.descriptor.interface.getExtendedAttribute("LegacyOverrideBuiltIns"):
+ getNamedOrOnPrototype = getNamed + getOnPrototype
+ else:
+ getNamedOrOnPrototype = getOnPrototype + getNamed
+
+ return fill(
+ """
+ MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
+ "Should not have a XrayWrapper here");
+
+ $*{missingPropUseCounters}
+ $*{indexedOrExpando}
+
+ $*{namedOrOnPropotype}
+ return true;
+ """,
+ missingPropUseCounters=missingPropUseCounters,
+ indexedOrExpando=getIndexedOrExpando,
+ namedOrOnPropotype=getNamedOrOnPrototype,
+ )
+
+
+class CGDOMJSProxyHandler_setCustom(ClassMethod):
+ def __init__(self, descriptor):
+ args = [
+ Argument("JSContext*", "cx_"),
+ Argument("JS::Handle<JSObject*>", "proxy"),
+ Argument("JS::Handle<jsid>", "id"),
+ Argument("JS::Handle<JS::Value>", "v"),
+ Argument("bool*", "done"),
+ ]
+ ClassMethod.__init__(
+ self, "setCustom", "bool", args, virtual=True, override=True, const=True
+ )
+ self.descriptor = descriptor
+
+ def getBody(self):
+ assertion = (
+ "MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),\n"
+ ' "Should not have a XrayWrapper here");\n'
+ )
+
+ # Correctness first. If we have a NamedSetter and [LegacyOverrideBuiltIns],
+ # always call the NamedSetter and never do anything else.
+ namedSetter = self.descriptor.operations["NamedSetter"]
+ if namedSetter is not None and self.descriptor.interface.getExtendedAttribute(
+ "LegacyOverrideBuiltIns"
+ ):
+ # Check assumptions.
+ if self.descriptor.supportsIndexedProperties():
+ raise ValueError(
+ "In interface "
+ + self.descriptor.name
+ + ": "
+ + "Can't cope with [LegacyOverrideBuiltIns] and an indexed getter"
+ )
+ if self.descriptor.hasLegacyUnforgeableMembers:
+ raise ValueError(
+ "In interface "
+ + self.descriptor.name
+ + ": "
+ + "Can't cope with [LegacyOverrideBuiltIns] and unforgeable members"
+ )
+
+ tailCode = dedent(
+ """
+ *done = true;
+ return true;
+ """
+ )
+ callSetter = CGProxyNamedSetter(
+ self.descriptor, tailCode, argumentHandleValue="v"
+ )
+ error_label = CGSpecializedMethod.error_reporting_label_helper(
+ self.descriptor, namedSetter, isConstructor=False
+ )
+ if error_label:
+ cxDecl = fill(
+ """
+ BindingCallContext cx(cx_, "${error_label}");
+ """,
+ error_label=error_label,
+ )
+ else:
+ cxDecl = dedent(
+ """
+ JSContext* cx = cx_;
+ """
+ )
+ return fill(
+ """
+ $*{assertion}
+ $*{cxDecl}
+ $*{callSetter}
+ *done = false;
+ return true;
+ """,
+ assertion=assertion,
+ cxDecl=cxDecl,
+ callSetter=callSetter.define(),
+ )
+
+ # As an optimization, if we are going to call an IndexedSetter, go
+ # ahead and call it and have done.
+ indexedSetter = self.descriptor.operations["IndexedSetter"]
+ if indexedSetter is not None:
+ error_label = CGSpecializedMethod.error_reporting_label_helper(
+ self.descriptor, indexedSetter, isConstructor=False
+ )
+ if error_label:
+ cxDecl = fill(
+ """
+ BindingCallContext cx(cx_, "${error_label}");
+ """,
+ error_label=error_label,
+ )
+ else:
+ cxDecl = dedent(
+ """
+ JSContext* cx = cx_;
+ """
+ )
+ setIndexed = fill(
+ """
+ uint32_t index = GetArrayIndexFromId(id);
+ if (IsArrayIndex(index)) {
+ $*{cxDecl}
+ $*{callSetter}
+ *done = true;
+ return true;
+ }
+
+ """,
+ cxDecl=cxDecl,
+ callSetter=CGProxyIndexedSetter(
+ self.descriptor, argumentHandleValue="v"
+ ).define(),
+ )
+ else:
+ setIndexed = ""
+
+ return assertion + setIndexed + "*done = false;\n" "return true;\n"
+
+
+class CGDOMJSProxyHandler_className(ClassMethod):
+ def __init__(self, descriptor):
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "proxy"),
+ ]
+ ClassMethod.__init__(
+ self,
+ "className",
+ "const char*",
+ args,
+ virtual=True,
+ override=True,
+ const=True,
+ )
+ self.descriptor = descriptor
+
+ def getBody(self):
+ if self.descriptor.isMaybeCrossOriginObject():
+ crossOrigin = dedent(
+ """
+ if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+ return "Object";
+ }
+
+ """
+ )
+ else:
+ crossOrigin = ""
+ return fill(
+ """
+ $*{crossOrigin}
+ return "${name}";
+ """,
+ crossOrigin=crossOrigin,
+ name=self.descriptor.name,
+ )
+
+
+class CGDOMJSProxyHandler_finalizeInBackground(ClassMethod):
+ def __init__(self, descriptor):
+ args = [Argument("const JS::Value&", "priv")]
+ ClassMethod.__init__(
+ self,
+ "finalizeInBackground",
+ "bool",
+ args,
+ virtual=True,
+ override=True,
+ const=True,
+ )
+ self.descriptor = descriptor
+
+ def getBody(self):
+ return "return false;\n"
+
+
+class CGDOMJSProxyHandler_finalize(ClassMethod):
+ def __init__(self, descriptor):
+ args = [Argument("JS::GCContext*", "gcx"), Argument("JSObject*", "proxy")]
+ ClassMethod.__init__(
+ self, "finalize", "void", args, virtual=True, override=True, const=True
+ )
+ self.descriptor = descriptor
+
+ def getBody(self):
+ return (
+ "%s* self = UnwrapPossiblyNotInitializedDOMObject<%s>(proxy);\n"
+ % (self.descriptor.nativeType, self.descriptor.nativeType)
+ ) + finalizeHook(
+ self.descriptor,
+ FINALIZE_HOOK_NAME,
+ self.args[0].name,
+ self.args[1].name,
+ ).define()
+
+
+class CGDOMJSProxyHandler_objectMoved(ClassMethod):
+ def __init__(self, descriptor):
+ args = [Argument("JSObject*", "obj"), Argument("JSObject*", "old")]
+ ClassMethod.__init__(
+ self, "objectMoved", "size_t", args, virtual=True, override=True, const=True
+ )
+ self.descriptor = descriptor
+
+ def getBody(self):
+ return (
+ "%s* self = UnwrapPossiblyNotInitializedDOMObject<%s>(obj);\n"
+ % (self.descriptor.nativeType, self.descriptor.nativeType)
+ ) + objectMovedHook(
+ self.descriptor,
+ OBJECT_MOVED_HOOK_NAME,
+ self.args[0].name,
+ self.args[1].name,
+ )
+
+
+class CGDOMJSProxyHandler_getElements(ClassMethod):
+ def __init__(self, descriptor):
+ assert descriptor.supportsIndexedProperties()
+
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "proxy"),
+ Argument("uint32_t", "begin"),
+ Argument("uint32_t", "end"),
+ Argument("js::ElementAdder*", "adder"),
+ ]
+ ClassMethod.__init__(
+ self, "getElements", "bool", args, virtual=True, override=True, const=True
+ )
+ self.descriptor = descriptor
+
+ def getBody(self):
+ # Just like ownPropertyKeys we'll assume that we have no holes, so
+ # we have all properties from 0 to length. If that ever changes
+ # (unlikely), we'll need to do something a bit more clever with how we
+ # forward on to our ancestor.
+
+ templateValues = {
+ "jsvalRef": "temp",
+ "jsvalHandle": "&temp",
+ "obj": "proxy",
+ "successCode": (
+ "if (!adder->append(cx, temp)) return false;\n" "continue;\n"
+ ),
+ }
+ get = CGProxyIndexedGetter(
+ self.descriptor, templateValues, False, False
+ ).define()
+
+ if self.descriptor.lengthNeedsCallerType():
+ callerType = callerTypeGetterForDescriptor(self.descriptor)
+ else:
+ callerType = ""
+
+ return fill(
+ """
+ JS::Rooted<JS::Value> temp(cx);
+ MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
+ "Should not have a XrayWrapper here");
+
+ ${nativeType}* self = UnwrapProxy(proxy);
+ uint32_t length = self->Length(${callerType});
+ // Compute the end of the indices we'll get ourselves
+ uint32_t ourEnd = std::max(begin, std::min(end, length));
+
+ for (uint32_t index = begin; index < ourEnd; ++index) {
+ $*{get}
+ }
+
+ if (end > ourEnd) {
+ JS::Rooted<JSObject*> proto(cx);
+ if (!js::GetObjectProto(cx, proxy, &proto)) {
+ return false;
+ }
+ return js::GetElementsWithAdder(cx, proto, proxy, ourEnd, end, adder);
+ }
+
+ return true;
+ """,
+ nativeType=self.descriptor.nativeType,
+ callerType=callerType,
+ get=get,
+ )
+
+
+class CGJSProxyHandler_getInstance(ClassMethod):
+ def __init__(self, type):
+ self.type = type
+ ClassMethod.__init__(
+ self, "getInstance", "const %s*" % self.type, [], static=True
+ )
+
+ def getBody(self):
+ return fill(
+ """
+ static const ${type} instance;
+ return &instance;
+ """,
+ type=self.type,
+ )
+
+
+class CGDOMJSProxyHandler_call(ClassMethod):
+ def __init__(self):
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "proxy"),
+ Argument("const JS::CallArgs&", "args"),
+ ]
+
+ ClassMethod.__init__(
+ self, "call", "bool", args, virtual=True, override=True, const=True
+ )
+
+ def getBody(self):
+ return fill(
+ """
+ return js::ForwardToNative(cx, ${legacyCaller}, args);
+ """,
+ legacyCaller=LEGACYCALLER_HOOK_NAME,
+ )
+
+
+class CGDOMJSProxyHandler_isCallable(ClassMethod):
+ def __init__(self):
+ ClassMethod.__init__(
+ self,
+ "isCallable",
+ "bool",
+ [Argument("JSObject*", "obj")],
+ virtual=True,
+ override=True,
+ const=True,
+ )
+
+ def getBody(self):
+ return dedent(
+ """
+ return true;
+ """
+ )
+
+
+class CGDOMJSProxyHandler_canNurseryAllocate(ClassMethod):
+ """
+ Override the default canNurseryAllocate in BaseProxyHandler, for cases when
+ we should be nursery-allocated.
+ """
+
+ def __init__(self):
+ ClassMethod.__init__(
+ self,
+ "canNurseryAllocate",
+ "bool",
+ [],
+ virtual=True,
+ override=True,
+ const=True,
+ )
+
+ def getBody(self):
+ return dedent(
+ """
+ return true;
+ """
+ )
+
+
+class CGDOMJSProxyHandler_getOwnPropertyDescriptor(ClassMethod):
+ """
+ Implementation of getOwnPropertyDescriptor. We only use this for
+ cross-origin objects.
+ """
+
+ def __init__(self, descriptor):
+ assert descriptor.isMaybeCrossOriginObject()
+
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "proxy"),
+ Argument("JS::Handle<jsid>", "id"),
+ Argument("JS::MutableHandle<Maybe<JS::PropertyDescriptor>>", "desc"),
+ ]
+ ClassMethod.__init__(
+ self,
+ "getOwnPropertyDescriptor",
+ "bool",
+ args,
+ virtual=True,
+ override=True,
+ const=True,
+ )
+ self.descriptor = descriptor
+
+ def getBody(self):
+ return dedent(
+ """
+ // Implementation of <https://html.spec.whatwg.org/multipage/history.html#location-getownproperty>.
+ MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy));
+
+ // Step 1.
+ if (IsPlatformObjectSameOrigin(cx, proxy)) {
+ { // Scope so we can wrap our PropertyDescriptor back into
+ // the caller compartment.
+ // Enter the Realm of "proxy" so we can work with it.
+ JSAutoRealm ar(cx, proxy);
+
+ JS_MarkCrossZoneId(cx, id);
+
+ // The spec messes around with configurability of the returned
+ // descriptor here, but it's not clear what should actually happen
+ // here. See <https://github.com/whatwg/html/issues/4157>. For
+ // now, keep our old behavior and don't do any magic.
+ if (!dom::DOMProxyHandler::getOwnPropertyDescriptor(cx, proxy, id, desc)) {
+ return false;
+ }
+ }
+ return JS_WrapPropertyDescriptor(cx, desc);
+ }
+
+ // Step 2.
+ if (!CrossOriginGetOwnPropertyHelper(cx, proxy, id, desc)) {
+ return false;
+ }
+
+ // Step 3.
+ if (desc.isSome()) {
+ return true;
+ }
+
+ // And step 4.
+ return CrossOriginPropertyFallback(cx, proxy, id, desc);
+ """
+ )
+
+
+class CGDOMJSProxyHandler_getSameOriginPrototype(ClassMethod):
+ """
+ Implementation of getSameOriginPrototype. We only use this for
+ cross-origin objects.
+ """
+
+ def __init__(self, descriptor):
+ assert descriptor.isMaybeCrossOriginObject()
+
+ args = [Argument("JSContext*", "cx")]
+ ClassMethod.__init__(
+ self,
+ "getSameOriginPrototype",
+ "JSObject*",
+ args,
+ virtual=True,
+ override=True,
+ const=True,
+ )
+ self.descriptor = descriptor
+
+ def getBody(self):
+ return dedent(
+ """
+ return GetProtoObjectHandle(cx);
+ """
+ )
+
+
+class CGDOMJSProxyHandler_definePropertySameOrigin(ClassMethod):
+ """
+ Implementation of definePropertySameOrigin. We only use this for
+ cross-origin objects.
+ """
+
+ def __init__(self, descriptor):
+ assert descriptor.isMaybeCrossOriginObject()
+
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "proxy"),
+ Argument("JS::Handle<jsid>", "id"),
+ Argument("JS::Handle<JS::PropertyDescriptor>", "desc"),
+ Argument("JS::ObjectOpResult&", "result"),
+ ]
+ ClassMethod.__init__(
+ self,
+ "definePropertySameOrigin",
+ "bool",
+ args,
+ virtual=True,
+ override=True,
+ const=True,
+ )
+ self.descriptor = descriptor
+
+ def getBody(self):
+ return dedent(
+ """
+ return dom::DOMProxyHandler::defineProperty(cx, proxy, id, desc, result);
+ """
+ )
+
+
+class CGDOMJSProxyHandler_set(ClassMethod):
+ """
+ Implementation of set(). We only use this for cross-origin objects.
+ """
+
+ def __init__(self, descriptor):
+ assert descriptor.isMaybeCrossOriginObject()
+
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "proxy"),
+ Argument("JS::Handle<jsid>", "id"),
+ Argument("JS::Handle<JS::Value>", "v"),
+ Argument("JS::Handle<JS::Value>", "receiver"),
+ Argument("JS::ObjectOpResult&", "result"),
+ ]
+ ClassMethod.__init__(
+ self, "set", "bool", args, virtual=True, override=True, const=True
+ )
+ self.descriptor = descriptor
+
+ def getBody(self):
+ return dedent(
+ """
+ if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+ return CrossOriginSet(cx, proxy, id, v, receiver, result);
+ }
+
+ // Safe to enter the Realm of proxy now, since it's same-origin with us.
+ JSAutoRealm ar(cx, proxy);
+ JS::Rooted<JS::Value> wrappedReceiver(cx, receiver);
+ if (!MaybeWrapValue(cx, &wrappedReceiver)) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> wrappedValue(cx, v);
+ if (!MaybeWrapValue(cx, &wrappedValue)) {
+ return false;
+ }
+
+ JS_MarkCrossZoneId(cx, id);
+
+ return dom::DOMProxyHandler::set(cx, proxy, id, wrappedValue, wrappedReceiver, result);
+ """
+ )
+
+
+class CGDOMJSProxyHandler_EnsureHolder(ClassMethod):
+ """
+ Implementation of EnsureHolder(). We only use this for cross-origin objects.
+ """
+
+ def __init__(self, descriptor):
+ args = [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "proxy"),
+ Argument("JS::MutableHandle<JSObject*>", "holder"),
+ ]
+ ClassMethod.__init__(
+ self, "EnsureHolder", "bool", args, virtual=True, override=True, const=True
+ )
+ self.descriptor = descriptor
+
+ def getBody(self):
+ return dedent(
+ """
+ return EnsureHolder(cx, proxy,
+ JSCLASS_RESERVED_SLOTS(JS::GetClass(proxy)) - 1,
+ sCrossOriginProperties, holder);
+ """
+ )
+
+
+class CGDOMJSProxyHandler(CGClass):
+ def __init__(self, descriptor):
+ assert (
+ descriptor.supportsIndexedProperties()
+ or descriptor.supportsNamedProperties()
+ or descriptor.isMaybeCrossOriginObject()
+ )
+ methods = [
+ CGDOMJSProxyHandler_getOwnPropDescriptor(descriptor),
+ CGDOMJSProxyHandler_defineProperty(descriptor),
+ ClassUsingDeclaration("mozilla::dom::DOMProxyHandler", "defineProperty"),
+ CGDOMJSProxyHandler_ownPropNames(descriptor),
+ CGDOMJSProxyHandler_hasOwn(descriptor),
+ CGDOMJSProxyHandler_get(descriptor),
+ CGDOMJSProxyHandler_className(descriptor),
+ CGDOMJSProxyHandler_finalizeInBackground(descriptor),
+ CGDOMJSProxyHandler_finalize(descriptor),
+ CGJSProxyHandler_getInstance("DOMProxyHandler"),
+ CGDOMJSProxyHandler_delete(descriptor),
+ ]
+ constructors = [
+ ClassConstructor([], constexpr=True, visibility="public", explicit=True)
+ ]
+
+ if descriptor.supportsIndexedProperties():
+ methods.append(CGDOMJSProxyHandler_getElements(descriptor))
+ if descriptor.operations["IndexedSetter"] is not None or (
+ descriptor.operations["NamedSetter"] is not None
+ and descriptor.interface.getExtendedAttribute("LegacyOverrideBuiltIns")
+ ):
+ methods.append(CGDOMJSProxyHandler_setCustom(descriptor))
+ if descriptor.operations["LegacyCaller"]:
+ methods.append(CGDOMJSProxyHandler_call())
+ methods.append(CGDOMJSProxyHandler_isCallable())
+ if descriptor.interface.hasProbablyShortLivingWrapper():
+ if not descriptor.wrapperCache:
+ raise TypeError(
+ "Need a wrapper cache to support nursery "
+ "allocation of DOM objects"
+ )
+ methods.append(CGDOMJSProxyHandler_canNurseryAllocate())
+ if descriptor.wrapperCache:
+ methods.append(CGDOMJSProxyHandler_objectMoved(descriptor))
+
+ if descriptor.isMaybeCrossOriginObject():
+ methods.extend(
+ [
+ CGDOMJSProxyHandler_getOwnPropertyDescriptor(descriptor),
+ CGDOMJSProxyHandler_getSameOriginPrototype(descriptor),
+ CGDOMJSProxyHandler_definePropertySameOrigin(descriptor),
+ CGDOMJSProxyHandler_set(descriptor),
+ CGDOMJSProxyHandler_EnsureHolder(descriptor),
+ ClassUsingDeclaration(
+ "MaybeCrossOriginObjectMixins", "EnsureHolder"
+ ),
+ ]
+ )
+
+ if descriptor.interface.getExtendedAttribute("LegacyOverrideBuiltIns"):
+ assert not descriptor.isMaybeCrossOriginObject()
+ parentClass = "ShadowingDOMProxyHandler"
+ elif descriptor.isMaybeCrossOriginObject():
+ parentClass = "MaybeCrossOriginObject<mozilla::dom::DOMProxyHandler>"
+ else:
+ parentClass = "mozilla::dom::DOMProxyHandler"
+
+ CGClass.__init__(
+ self,
+ "DOMProxyHandler",
+ bases=[ClassBase(parentClass)],
+ constructors=constructors,
+ methods=methods,
+ )
+
+
+class CGDOMJSProxyHandlerDeclarer(CGThing):
+ """
+ A class for declaring a DOMProxyHandler.
+ """
+
+ def __init__(self, handlerThing):
+ self.handlerThing = handlerThing
+
+ def declare(self):
+ # Our class declaration should happen when we're defining
+ return ""
+
+ def define(self):
+ return self.handlerThing.declare()
+
+
+class CGDOMJSProxyHandlerDefiner(CGThing):
+ """
+ A class for defining a DOMProxyHandler.
+ """
+
+ def __init__(self, handlerThing):
+ self.handlerThing = handlerThing
+
+ def declare(self):
+ return ""
+
+ def define(self):
+ return self.handlerThing.define()
+
+
+def stripTrailingWhitespace(text):
+ tail = "\n" if text.endswith("\n") else ""
+ lines = text.splitlines()
+ return "\n".join(line.rstrip() for line in lines) + tail
+
+
+class MemberProperties:
+ def __init__(self):
+ self.isCrossOriginMethod = False
+ self.isCrossOriginGetter = False
+ self.isCrossOriginSetter = False
+
+
+def memberProperties(m, descriptor):
+ props = MemberProperties()
+ if m.isMethod():
+ if not m.isIdentifierLess() or m == descriptor.operations["Stringifier"]:
+ if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject():
+ if m.getExtendedAttribute("CrossOriginCallable"):
+ props.isCrossOriginMethod = True
+ elif m.isAttr():
+ if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject():
+ if m.getExtendedAttribute("CrossOriginReadable"):
+ props.isCrossOriginGetter = True
+ if not m.readonly:
+ if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject():
+ if m.getExtendedAttribute("CrossOriginWritable"):
+ props.isCrossOriginSetter = True
+ elif m.getExtendedAttribute("PutForwards"):
+ if m.getExtendedAttribute("CrossOriginWritable"):
+ props.isCrossOriginSetter = True
+ elif m.getExtendedAttribute("Replaceable") or m.getExtendedAttribute(
+ "LegacyLenientSetter"
+ ):
+ if m.getExtendedAttribute("CrossOriginWritable"):
+ props.isCrossOriginSetter = True
+
+ return props
+
+
+class CGDescriptor(CGThing):
+ def __init__(self, descriptor):
+ CGThing.__init__(self)
+
+ assert (
+ not descriptor.concrete
+ or descriptor.interface.hasInterfacePrototypeObject()
+ )
+
+ self._deps = descriptor.interface.getDeps()
+
+ iteratorCGThings = None
+ if (
+ descriptor.interface.isIterable()
+ and descriptor.interface.maplikeOrSetlikeOrIterable.isPairIterator()
+ ) or descriptor.interface.isAsyncIterable():
+ # We need the Wrap function when using the [Async]IterableIterator type, so we want to declare it before we need it. We don't really want to expose it in the header file, so we make it static too.
+ iteratorCGThings = []
+ itr_iface = (
+ descriptor.interface.maplikeOrSetlikeOrIterable.iteratorType.inner
+ )
+ iteratorDescriptor = descriptor.getDescriptor(itr_iface.identifier.name)
+ iteratorCGThings.append(
+ CGWrapNonWrapperCacheMethod(
+ iteratorDescriptor, static=True, signatureOnly=True
+ )
+ )
+ iteratorCGThings = CGList(
+ (CGIndenter(t, declareOnly=True) for t in iteratorCGThings), "\n"
+ )
+ iteratorCGThings = CGWrapper(iteratorCGThings, pre="\n", post="\n")
+ iteratorCGThings = CGWrapper(
+ CGNamespace(
+ toBindingNamespace(iteratorDescriptor.name), iteratorCGThings
+ ),
+ post="\n",
+ )
+
+ cgThings = []
+
+ isIteratorInterface = (
+ descriptor.interface.isIteratorInterface()
+ or descriptor.interface.isAsyncIteratorInterface()
+ )
+ if not isIteratorInterface:
+ cgThings.append(
+ CGGeneric(declare="typedef %s NativeType;\n" % descriptor.nativeType)
+ )
+ parent = descriptor.interface.parent
+ if parent:
+ cgThings.append(
+ CGGeneric(
+ "static_assert(IsRefcounted<NativeType>::value == IsRefcounted<%s::NativeType>::value,\n"
+ ' "Can\'t inherit from an interface with a different ownership model.");\n'
+ % toBindingNamespace(descriptor.parentPrototypeName)
+ )
+ )
+
+ defaultToJSONMethod = None
+ needCrossOriginPropertyArrays = False
+ unscopableNames = list()
+ for n in descriptor.interface.legacyFactoryFunctions:
+ cgThings.append(
+ CGClassConstructor(descriptor, n, LegacyFactoryFunctionName(n))
+ )
+ for m in descriptor.interface.members:
+ if m.isMethod() and m.identifier.name == "QueryInterface":
+ continue
+
+ props = memberProperties(m, descriptor)
+
+ if m.isMethod():
+ if m.getExtendedAttribute("Unscopable"):
+ assert not m.isStatic()
+ unscopableNames.append(m.identifier.name)
+ if m.isDefaultToJSON():
+ defaultToJSONMethod = m
+ elif (
+ not m.isIdentifierLess()
+ or m == descriptor.operations["Stringifier"]
+ ):
+ if m.isStatic():
+ assert descriptor.interface.hasInterfaceObject()
+ cgThings.append(CGStaticMethod(descriptor, m))
+ if m.returnsPromise():
+ cgThings.append(CGStaticMethodJitinfo(m))
+ elif descriptor.interface.hasInterfacePrototypeObject():
+ specializedMethod = CGSpecializedMethod(descriptor, m)
+ cgThings.append(specializedMethod)
+ if m.returnsPromise():
+ cgThings.append(
+ CGMethodPromiseWrapper(descriptor, specializedMethod)
+ )
+ cgThings.append(CGMemberJITInfo(descriptor, m))
+ if props.isCrossOriginMethod:
+ needCrossOriginPropertyArrays = True
+ # 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.type.isObservableArray():
+ cgThings.append(
+ CGObservableArrayProxyHandlerGenerator(descriptor, m)
+ )
+ cgThings.append(CGObservableArrayHelperGenerator(descriptor, m))
+ if m.getExtendedAttribute("Unscopable"):
+ assert not m.isStatic()
+ unscopableNames.append(m.identifier.name)
+ if m.isStatic():
+ assert descriptor.interface.hasInterfaceObject()
+ cgThings.append(CGStaticGetter(descriptor, m))
+ elif descriptor.interface.hasInterfacePrototypeObject():
+ specializedGetter = CGSpecializedGetter(descriptor, m)
+ cgThings.append(specializedGetter)
+ if m.type.isPromise():
+ cgThings.append(
+ CGGetterPromiseWrapper(descriptor, specializedGetter)
+ )
+ if props.isCrossOriginGetter:
+ needCrossOriginPropertyArrays = True
+ if not m.readonly:
+ if m.isStatic():
+ assert descriptor.interface.hasInterfaceObject()
+ cgThings.append(CGStaticSetter(descriptor, m))
+ elif descriptor.interface.hasInterfacePrototypeObject():
+ cgThings.append(CGSpecializedSetter(descriptor, m))
+ if props.isCrossOriginSetter:
+ needCrossOriginPropertyArrays = True
+ elif m.getExtendedAttribute("PutForwards"):
+ cgThings.append(CGSpecializedForwardingSetter(descriptor, m))
+ if props.isCrossOriginSetter:
+ needCrossOriginPropertyArrays = True
+ elif m.getExtendedAttribute("Replaceable"):
+ cgThings.append(CGSpecializedReplaceableSetter(descriptor, m))
+ elif m.getExtendedAttribute("LegacyLenientSetter"):
+ # XXX In this case, we need to add an include for mozilla/dom/Document.h to the generated cpp file.
+ cgThings.append(CGSpecializedLenientSetter(descriptor, m))
+ if (
+ not m.isStatic()
+ and descriptor.interface.hasInterfacePrototypeObject()
+ ):
+ cgThings.append(CGMemberJITInfo(descriptor, m))
+ if m.isConst() and m.type.isPrimitive():
+ cgThings.append(CGConstDefinition(m))
+
+ if defaultToJSONMethod:
+ cgThings.append(CGDefaultToJSONMethod(descriptor, defaultToJSONMethod))
+ cgThings.append(CGMemberJITInfo(descriptor, defaultToJSONMethod))
+
+ if descriptor.concrete and not descriptor.proxy:
+ if wantsAddProperty(descriptor):
+ cgThings.append(CGAddPropertyHook(descriptor))
+
+ # Always have a finalize hook, regardless of whether the class
+ # wants a custom hook.
+ cgThings.append(CGClassFinalizeHook(descriptor))
+
+ if wantsGetWrapperCache(descriptor):
+ cgThings.append(CGGetWrapperCacheHook(descriptor))
+
+ if descriptor.concrete and descriptor.wrapperCache and not descriptor.proxy:
+ cgThings.append(CGClassObjectMovedHook(descriptor))
+
+ properties = PropertyArrays(descriptor)
+ cgThings.append(CGGeneric(define=str(properties)))
+ cgThings.append(CGNativeProperties(descriptor, properties))
+
+ if defaultToJSONMethod:
+ # Now that we know about our property arrays, we can
+ # output our "collect attribute values" method, which uses those.
+ cgThings.append(
+ CGCollectJSONAttributesMethod(descriptor, defaultToJSONMethod)
+ )
+
+ # Declare our DOMProxyHandler.
+ if descriptor.concrete and descriptor.proxy:
+ cgThings.append(
+ CGGeneric(
+ fill(
+ """
+ static_assert(std::is_base_of_v<nsISupports, ${nativeType}>,
+ "We don't support non-nsISupports native classes for "
+ "proxy-based bindings yet");
+
+ """,
+ nativeType=descriptor.nativeType,
+ )
+ )
+ )
+ if not descriptor.wrapperCache:
+ raise TypeError(
+ "We need a wrappercache to support expandos for proxy-based "
+ "bindings (" + descriptor.name + ")"
+ )
+ handlerThing = CGDOMJSProxyHandler(descriptor)
+ cgThings.append(CGDOMJSProxyHandlerDeclarer(handlerThing))
+ cgThings.append(CGProxyIsProxy(descriptor))
+ cgThings.append(CGProxyUnwrap(descriptor))
+
+ # Set up our Xray callbacks as needed. This needs to come
+ # after we have our DOMProxyHandler defined.
+ if descriptor.wantsXrays:
+ if descriptor.concrete and descriptor.proxy:
+ if descriptor.needsXrayNamedDeleterHook():
+ cgThings.append(CGDeleteNamedProperty(descriptor))
+ elif descriptor.needsXrayResolveHooks():
+ cgThings.append(CGResolveOwnPropertyViaResolve(descriptor))
+ cgThings.append(
+ CGEnumerateOwnPropertiesViaGetOwnPropertyNames(descriptor)
+ )
+ if descriptor.wantsXrayExpandoClass:
+ cgThings.append(CGXrayExpandoJSClass(descriptor))
+
+ # Now that we have our ResolveOwnProperty/EnumerateOwnProperties stuff
+ # done, set up our NativePropertyHooks.
+ cgThings.append(CGNativePropertyHooks(descriptor, properties))
+
+ if descriptor.interface.hasInterfaceObject():
+ cgThings.append(CGClassConstructor(descriptor, descriptor.interface.ctor()))
+ cgThings.append(CGInterfaceObjectJSClass(descriptor, properties))
+ cgThings.append(CGLegacyFactoryFunctions(descriptor))
+
+ cgThings.append(CGLegacyCallHook(descriptor))
+ if descriptor.interface.getExtendedAttribute("NeedResolve"):
+ cgThings.append(CGResolveHook(descriptor))
+ cgThings.append(CGMayResolveHook(descriptor))
+ cgThings.append(CGEnumerateHook(descriptor))
+
+ if descriptor.hasNamedPropertiesObject:
+ cgThings.append(CGGetNamedPropertiesObjectMethod(descriptor))
+
+ if descriptor.interface.hasInterfacePrototypeObject():
+ cgThings.append(CGPrototypeJSClass(descriptor, properties))
+
+ if (
+ descriptor.interface.hasInterfaceObject()
+ and not descriptor.interface.isExternal()
+ and descriptor.isExposedConditionally()
+ ):
+ cgThings.append(CGConstructorEnabled(descriptor))
+
+ if (
+ descriptor.interface.hasMembersInSlots()
+ and descriptor.interface.hasChildInterfaces()
+ ):
+ raise TypeError(
+ "We don't support members in slots on "
+ "non-leaf interfaces like %s" % descriptor.interface.identifier.name
+ )
+
+ if descriptor.needsMissingPropUseCounters:
+ cgThings.append(CGCountMaybeMissingProperty(descriptor))
+
+ if descriptor.concrete:
+ if descriptor.interface.isSerializable():
+ cgThings.append(CGSerializer(descriptor))
+ cgThings.append(CGDeserializer(descriptor))
+
+ # CGDOMProxyJSClass/CGDOMJSClass need GetProtoObjectHandle, but we don't want to export it for the iterator interfaces, so declare it here.
+ if isIteratorInterface:
+ cgThings.append(
+ CGGetProtoObjectHandleMethod(
+ descriptor, static=True, signatureOnly=True
+ )
+ )
+
+ if descriptor.proxy:
+ cgThings.append(CGDOMJSProxyHandlerDefiner(handlerThing))
+ cgThings.append(CGDOMProxyJSClass(descriptor))
+ else:
+ cgThings.append(CGDOMJSClass(descriptor))
+
+ if descriptor.interface.hasMembersInSlots():
+ cgThings.append(CGUpdateMemberSlotsMethod(descriptor))
+
+ if descriptor.isGlobal():
+ assert descriptor.wrapperCache
+ cgThings.append(CGWrapGlobalMethod(descriptor, properties))
+ elif descriptor.wrapperCache:
+ cgThings.append(CGWrapWithCacheMethod(descriptor))
+ cgThings.append(CGWrapMethod(descriptor))
+ else:
+ cgThings.append(
+ CGWrapNonWrapperCacheMethod(descriptor, static=isIteratorInterface)
+ )
+
+ # If we're not wrappercached, we don't know how to clear our
+ # cached values, since we can't get at the JSObject.
+ if descriptor.wrapperCache:
+ cgThings.extend(
+ CGClearCachedValueMethod(descriptor, m)
+ for m in clearableCachedAttrs(descriptor)
+ )
+
+ haveUnscopables = (
+ len(unscopableNames) != 0
+ and descriptor.interface.hasInterfacePrototypeObject()
+ )
+ if haveUnscopables:
+ cgThings.append(
+ CGList(
+ [
+ CGGeneric("static const char* const unscopableNames[] = {"),
+ CGIndenter(
+ CGList(
+ [CGGeneric('"%s"' % name) for name in unscopableNames]
+ + [CGGeneric("nullptr")],
+ ",\n",
+ )
+ ),
+ CGGeneric("};\n"),
+ ],
+ "\n",
+ )
+ )
+
+ legacyWindowAliases = descriptor.interface.legacyWindowAliases
+ haveLegacyWindowAliases = len(legacyWindowAliases) != 0
+ if haveLegacyWindowAliases:
+ cgThings.append(
+ CGList(
+ [
+ CGGeneric("static const char* const legacyWindowAliases[] = {"),
+ CGIndenter(
+ CGList(
+ [
+ CGGeneric('"%s"' % name)
+ for name in legacyWindowAliases
+ ]
+ + [CGGeneric("nullptr")],
+ ",\n",
+ )
+ ),
+ CGGeneric("};\n"),
+ ],
+ "\n",
+ )
+ )
+
+ # CGCreateInterfaceObjectsMethod needs to come after our
+ # CGDOMJSClass and unscopables, if any.
+ cgThings.append(
+ CGCreateInterfaceObjectsMethod(
+ descriptor,
+ properties,
+ haveUnscopables,
+ haveLegacyWindowAliases,
+ static=isIteratorInterface,
+ )
+ )
+
+ # CGGetProtoObjectMethod and CGGetConstructorObjectMethod need
+ # to come after CGCreateInterfaceObjectsMethod.
+ if (
+ descriptor.interface.hasInterfacePrototypeObject()
+ and not descriptor.hasOrdinaryObjectPrototype
+ ):
+ cgThings.append(
+ CGGetProtoObjectHandleMethod(descriptor, static=isIteratorInterface)
+ )
+ if descriptor.interface.hasChildInterfaces():
+ assert not isIteratorInterface
+ cgThings.append(CGGetProtoObjectMethod(descriptor))
+ if descriptor.interface.hasInterfaceObject():
+ cgThings.append(CGGetConstructorObjectHandleMethod(descriptor))
+ cgThings.append(CGGetConstructorObjectMethod(descriptor))
+
+ # See whether we need to generate cross-origin property arrays.
+ if needCrossOriginPropertyArrays:
+ cgThings.append(CGCrossOriginProperties(descriptor))
+
+ cgThings = CGList((CGIndenter(t, declareOnly=True) for t in cgThings), "\n")
+ cgThings = CGWrapper(cgThings, pre="\n", post="\n")
+ cgThings = CGWrapper(
+ CGNamespace(toBindingNamespace(descriptor.name), cgThings), post="\n"
+ )
+ self.cgRoot = CGList([iteratorCGThings, cgThings], "\n")
+
+ def declare(self):
+ return self.cgRoot.declare()
+
+ def define(self):
+ return self.cgRoot.define()
+
+ def deps(self):
+ return self._deps
+
+
+class CGNamespacedEnum(CGThing):
+ def __init__(self, namespace, enumName, names, values, comment=""):
+
+ if not values:
+ values = []
+
+ # Account for explicit enum values.
+ entries = []
+ for i in range(0, len(names)):
+ if len(values) > i and values[i] is not None:
+ entry = "%s = %s" % (names[i], values[i])
+ else:
+ entry = names[i]
+ entries.append(entry)
+
+ # Append a Count.
+ entries.append("_" + enumName + "_Count")
+
+ # Indent.
+ entries = [" " + e for e in entries]
+
+ # Build the enum body.
+ enumstr = comment + "enum %s : uint16_t\n{\n%s\n};\n" % (
+ enumName,
+ ",\n".join(entries),
+ )
+ curr = CGGeneric(declare=enumstr)
+
+ # Add some whitespace padding.
+ curr = CGWrapper(curr, pre="\n", post="\n")
+
+ # Add the namespace.
+ curr = CGNamespace(namespace, curr)
+
+ # Add the typedef
+ typedef = "\ntypedef %s::%s %s;\n\n" % (namespace, enumName, enumName)
+ curr = CGList([curr, CGGeneric(declare=typedef)])
+
+ # Save the result.
+ self.node = curr
+
+ def declare(self):
+ return self.node.declare()
+
+ def define(self):
+ return ""
+
+
+def initIdsClassMethod(identifiers, atomCacheName):
+ idinit = [
+ '!atomsCache->%s.init(cx, "%s")' % (CGDictionary.makeIdName(id), id)
+ for id in identifiers
+ ]
+ idinit.reverse()
+ body = fill(
+ """
+ MOZ_ASSERT(reinterpret_cast<jsid*>(atomsCache)->isVoid());
+
+ // Initialize these in reverse order so that any failure leaves the first one
+ // uninitialized.
+ if (${idinit}) {
+ return false;
+ }
+ return true;
+ """,
+ idinit=" ||\n ".join(idinit),
+ )
+ return ClassMethod(
+ "InitIds",
+ "bool",
+ [Argument("JSContext*", "cx"), Argument("%s*" % atomCacheName, "atomsCache")],
+ static=True,
+ body=body,
+ visibility="private",
+ )
+
+
+class CGDictionary(CGThing):
+ def __init__(self, dictionary, descriptorProvider):
+ self.dictionary = dictionary
+ self.descriptorProvider = descriptorProvider
+ self.needToInitIds = len(dictionary.members) > 0
+ self.memberInfo = [
+ (
+ member,
+ getJSToNativeConversionInfo(
+ member.type,
+ descriptorProvider,
+ isMember="Dictionary",
+ isOptional=member.canHaveMissingValue(),
+ isKnownMissing=not dictionary.needsConversionFromJS,
+ defaultValue=member.defaultValue,
+ sourceDescription=self.getMemberSourceDescription(member),
+ ),
+ )
+ for member in dictionary.members
+ ]
+
+ # If we have a union member which is going to be declared in a different
+ # header but contains something that will be declared in the same header
+ # as us, bail: the C++ includes won't work out.
+ for member in dictionary.members:
+ type = member.type.unroll()
+ if type.isUnion() and CGHeaders.getUnionDeclarationFilename(
+ descriptorProvider.getConfig(), type
+ ) != CGHeaders.getDeclarationFilename(dictionary):
+ for t in type.flatMemberTypes:
+ if t.isDictionary() and CGHeaders.getDeclarationFilename(
+ t.inner
+ ) == CGHeaders.getDeclarationFilename(dictionary):
+ raise TypeError(
+ "Dictionary contains a union that will live in a different "
+ "header that contains a dictionary from the same header as "
+ "the original dictionary. This won't compile. Move the "
+ "inner dictionary to a different Web IDL file to move it "
+ "to a different header.\n%s\n%s"
+ % (t.location, t.inner.location)
+ )
+ self.structs = self.getStructs()
+
+ def declare(self):
+ return self.structs.declare()
+
+ def define(self):
+ return self.structs.define()
+
+ def base(self):
+ if self.dictionary.parent:
+ return self.makeClassName(self.dictionary.parent)
+ return "DictionaryBase"
+
+ def initMethod(self):
+ """
+ This function outputs the body of the Init() method for the dictionary.
+
+ For the most part, this is some bookkeeping for our atoms so
+ we can avoid atomizing strings all the time, then we just spit
+ out the getMemberConversion() output for each member,
+ separated by newlines.
+
+ """
+ body = dedent(
+ """
+ // Passing a null JSContext is OK only if we're initing from null,
+ // Since in that case we will not have to do any property gets
+ // Also evaluate isNullOrUndefined in order to avoid false-positive
+ // checkers by static analysis tools
+ MOZ_ASSERT_IF(!cx, val.isNull() && val.isNullOrUndefined());
+ """
+ )
+
+ if self.needToInitIds:
+ body += fill(
+ """
+ ${dictName}Atoms* atomsCache = nullptr;
+ if (cx) {
+ atomsCache = GetAtomCache<${dictName}Atoms>(cx);
+ if (reinterpret_cast<jsid*>(atomsCache)->isVoid() &&
+ !InitIds(cx, atomsCache)) {
+ return false;
+ }
+ }
+
+ """,
+ dictName=self.makeClassName(self.dictionary),
+ )
+
+ if self.dictionary.parent:
+ body += fill(
+ """
+ // Per spec, we init the parent's members first
+ if (!${dictName}::Init(cx, val)) {
+ return false;
+ }
+
+ """,
+ dictName=self.makeClassName(self.dictionary.parent),
+ )
+ else:
+ body += dedent(
+ """
+ if (!IsConvertibleToDictionary(val)) {
+ return cx.ThrowErrorMessage<MSG_CONVERSION_ERROR>(sourceDescription, "dictionary");
+ }
+
+ """
+ )
+
+ memberInits = [self.getMemberConversion(m).define() for m in self.memberInfo]
+ if memberInits:
+ body += fill(
+ """
+ bool isNull = val.isNullOrUndefined();
+ // We only need these if !isNull, in which case we have |cx|.
+ Maybe<JS::Rooted<JSObject *> > object;
+ Maybe<JS::Rooted<JS::Value> > temp;
+ if (!isNull) {
+ MOZ_ASSERT(cx);
+ object.emplace(cx, &val.toObject());
+ temp.emplace(cx);
+ }
+ $*{memberInits}
+ """,
+ memberInits="\n".join(memberInits),
+ )
+
+ body += "return true;\n"
+
+ return ClassMethod(
+ "Init",
+ "bool",
+ [
+ Argument("BindingCallContext&", "cx"),
+ Argument("JS::Handle<JS::Value>", "val"),
+ Argument("const char*", "sourceDescription", default='"Value"'),
+ Argument("bool", "passedToJSImpl", default="false"),
+ ],
+ body=body,
+ )
+
+ def initWithoutCallContextMethod(self):
+ """
+ This function outputs the body of an Init() method for the dictionary
+ that takes just a JSContext*. This is needed for non-binding consumers.
+ """
+ body = dedent(
+ """
+ // We don't want to use sourceDescription for our context here;
+ // that's not really what it's formatted for.
+ BindingCallContext cx(cx_, nullptr);
+ return Init(cx, val, sourceDescription, passedToJSImpl);
+ """
+ )
+ return ClassMethod(
+ "Init",
+ "bool",
+ [
+ Argument("JSContext*", "cx_"),
+ Argument("JS::Handle<JS::Value>", "val"),
+ Argument("const char*", "sourceDescription", default='"Value"'),
+ Argument("bool", "passedToJSImpl", default="false"),
+ ],
+ body=body,
+ )
+
+ def simpleInitMethod(self):
+ """
+ This function outputs the body of the Init() method for the dictionary,
+ for cases when we are just default-initializing it.
+
+ """
+ relevantMembers = [
+ m
+ for m in self.memberInfo
+ # We only need to init the things that can have
+ # default values.
+ if m[0].optional and m[0].defaultValue
+ ]
+
+ # We mostly avoid outputting code that uses cx in our native-to-JS
+ # conversions, but there is one exception: we may have a
+ # dictionary-typed member that _does_ generally support conversion from
+ # JS. If we have such a thing, we can pass it a null JSContext and
+ # JS::NullHandleValue to default-initialize it, but since the
+ # native-to-JS templates hardcode `cx` as the JSContext value, we're
+ # going to need to provide that.
+ haveMemberThatNeedsCx = any(
+ m[0].type.isDictionary() and m[0].type.unroll().inner.needsConversionFromJS
+ for m in relevantMembers
+ )
+ if haveMemberThatNeedsCx:
+ body = dedent(
+ """
+ JSContext* cx = nullptr;
+ """
+ )
+ else:
+ body = ""
+
+ if self.dictionary.parent:
+ if self.dictionary.parent.needsConversionFromJS:
+ args = "nullptr, JS::NullHandleValue"
+ else:
+ args = ""
+ body += fill(
+ """
+ // We init the parent's members first
+ if (!${dictName}::Init(${args})) {
+ return false;
+ }
+
+ """,
+ dictName=self.makeClassName(self.dictionary.parent),
+ args=args,
+ )
+
+ memberInits = [
+ self.getMemberConversion(m, isKnownMissing=True).define()
+ for m in relevantMembers
+ ]
+ if memberInits:
+ body += fill(
+ """
+ $*{memberInits}
+ """,
+ memberInits="\n".join(memberInits),
+ )
+
+ body += "return true;\n"
+
+ return ClassMethod(
+ "Init",
+ "bool",
+ [
+ Argument("const char*", "sourceDescription", default='"Value"'),
+ Argument("bool", "passedToJSImpl", default="false"),
+ ],
+ body=body,
+ )
+
+ def initFromJSONMethod(self):
+ return ClassMethod(
+ "Init",
+ "bool",
+ [Argument("const nsAString&", "aJSON")],
+ body=dedent(
+ """
+ AutoJSAPI jsapi;
+ JSObject* cleanGlobal = SimpleGlobalObject::Create(SimpleGlobalObject::GlobalType::BindingDetail);
+ if (!cleanGlobal) {
+ return false;
+ }
+ if (!jsapi.Init(cleanGlobal)) {
+ return false;
+ }
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JS::Value> json(cx);
+ bool ok = ParseJSON(cx, aJSON, &json);
+ NS_ENSURE_TRUE(ok, false);
+ return Init(cx, json);
+ """
+ ),
+ )
+
+ def toJSONMethod(self):
+ return ClassMethod(
+ "ToJSON",
+ "bool",
+ [Argument("nsAString&", "aJSON")],
+ body=dedent(
+ """
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext *cx = jsapi.cx();
+ // It's safe to use UnprivilegedJunkScopeOrWorkerGlobal here
+ // because we'll only be creating objects, in ways that have no
+ // side-effects, followed by a call to JS::ToJSONMaybeSafely,
+ // which likewise guarantees no side-effects for the sorts of
+ // things we will pass it.
+ JSObject* scope = UnprivilegedJunkScopeOrWorkerGlobal(fallible);
+ if (!scope) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+ JSAutoRealm ar(cx, scope);
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToObjectInternal(cx, &val)) {
+ return false;
+ }
+ JS::Rooted<JSObject*> obj(cx, &val.toObject());
+ return StringifyToJSON(cx, obj, aJSON);
+ """
+ ),
+ const=True,
+ )
+
+ def toObjectInternalMethod(self):
+ body = ""
+ if self.needToInitIds:
+ body += fill(
+ """
+ ${dictName}Atoms* atomsCache = GetAtomCache<${dictName}Atoms>(cx);
+ if (reinterpret_cast<jsid*>(atomsCache)->isVoid() &&
+ !InitIds(cx, atomsCache)) {
+ return false;
+ }
+
+ """,
+ dictName=self.makeClassName(self.dictionary),
+ )
+
+ if self.dictionary.parent:
+ body += fill(
+ """
+ // Per spec, we define the parent's members first
+ if (!${dictName}::ToObjectInternal(cx, rval)) {
+ return false;
+ }
+ JS::Rooted<JSObject*> obj(cx, &rval.toObject());
+
+ """,
+ dictName=self.makeClassName(self.dictionary.parent),
+ )
+ else:
+ body += dedent(
+ """
+ JS::Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx));
+ if (!obj) {
+ return false;
+ }
+ rval.set(JS::ObjectValue(*obj));
+
+ """
+ )
+
+ if self.memberInfo:
+ body += "\n".join(
+ self.getMemberDefinition(m).define() for m in self.memberInfo
+ )
+ body += "\nreturn true;\n"
+
+ return ClassMethod(
+ "ToObjectInternal",
+ "bool",
+ [
+ Argument("JSContext*", "cx"),
+ Argument("JS::MutableHandle<JS::Value>", "rval"),
+ ],
+ const=True,
+ body=body,
+ )
+
+ def initIdsMethod(self):
+ assert self.needToInitIds
+ return initIdsClassMethod(
+ [m.identifier.name for m in self.dictionary.members],
+ "%sAtoms" % self.makeClassName(self.dictionary),
+ )
+
+ def traceDictionaryMethod(self):
+ body = ""
+ if self.dictionary.parent:
+ cls = self.makeClassName(self.dictionary.parent)
+ body += "%s::TraceDictionary(trc);\n" % cls
+
+ memberTraces = [
+ self.getMemberTrace(m)
+ for m in self.dictionary.members
+ if typeNeedsRooting(m.type)
+ ]
+
+ if memberTraces:
+ body += "\n".join(memberTraces)
+
+ return ClassMethod(
+ "TraceDictionary",
+ "void",
+ [
+ Argument("JSTracer*", "trc"),
+ ],
+ body=body,
+ )
+
+ @staticmethod
+ def dictionaryNeedsCycleCollection(dictionary):
+ return any(idlTypeNeedsCycleCollection(m.type) for m in dictionary.members) or (
+ dictionary.parent
+ and CGDictionary.dictionaryNeedsCycleCollection(dictionary.parent)
+ )
+
+ def traverseForCCMethod(self):
+ body = ""
+ if self.dictionary.parent and self.dictionaryNeedsCycleCollection(
+ self.dictionary.parent
+ ):
+ cls = self.makeClassName(self.dictionary.parent)
+ body += "%s::TraverseForCC(aCallback, aFlags);\n" % cls
+
+ for m, _ in self.memberInfo:
+ if idlTypeNeedsCycleCollection(m.type):
+ memberName = self.makeMemberName(m.identifier.name)
+ body += (
+ 'ImplCycleCollectionTraverse(aCallback, %s, "%s", aFlags);\n'
+ % (memberName, memberName)
+ )
+
+ return ClassMethod(
+ "TraverseForCC",
+ "void",
+ [
+ Argument("nsCycleCollectionTraversalCallback&", "aCallback"),
+ Argument("uint32_t", "aFlags"),
+ ],
+ body=body,
+ # Inline so we don't pay a codesize hit unless someone actually uses
+ # this traverse method.
+ inline=True,
+ bodyInHeader=True,
+ )
+
+ def unlinkForCCMethod(self):
+ body = ""
+ if self.dictionary.parent and self.dictionaryNeedsCycleCollection(
+ self.dictionary.parent
+ ):
+ cls = self.makeClassName(self.dictionary.parent)
+ body += "%s::UnlinkForCC();\n" % cls
+
+ for m, _ in self.memberInfo:
+ if idlTypeNeedsCycleCollection(m.type):
+ memberName = self.makeMemberName(m.identifier.name)
+ body += "ImplCycleCollectionUnlink(%s);\n" % memberName
+
+ return ClassMethod(
+ "UnlinkForCC",
+ "void",
+ [],
+ body=body,
+ # Inline so we don't pay a codesize hit unless someone actually uses
+ # this unlink method.
+ inline=True,
+ bodyInHeader=True,
+ )
+
+ def assignmentOperator(self):
+ body = CGList([])
+ body.append(CGGeneric("%s::operator=(aOther);\n" % self.base()))
+
+ for m, _ in self.memberInfo:
+ memberName = self.makeMemberName(m.identifier.name)
+ if m.canHaveMissingValue():
+ memberAssign = CGGeneric(
+ fill(
+ """
+ ${name}.Reset();
+ if (aOther.${name}.WasPassed()) {
+ ${name}.Construct(aOther.${name}.Value());
+ }
+ """,
+ name=memberName,
+ )
+ )
+ else:
+ memberAssign = CGGeneric("%s = aOther.%s;\n" % (memberName, memberName))
+ body.append(memberAssign)
+ body.append(CGGeneric("return *this;\n"))
+ return ClassMethod(
+ "operator=",
+ "%s&" % self.makeClassName(self.dictionary),
+ [Argument("const %s&" % self.makeClassName(self.dictionary), "aOther")],
+ body=body.define(),
+ )
+
+ def canHaveEqualsOperator(self):
+ return all(
+ m.type.isString() or m.type.isPrimitive() for (m, _) in self.memberInfo
+ )
+
+ def equalsOperator(self):
+ body = CGList([])
+
+ for m, _ in self.memberInfo:
+ memberName = self.makeMemberName(m.identifier.name)
+ memberTest = CGGeneric(
+ fill(
+ """
+ if (${memberName} != aOther.${memberName}) {
+ return false;
+ }
+ """,
+ memberName=memberName,
+ )
+ )
+ body.append(memberTest)
+ body.append(CGGeneric("return true;\n"))
+ return ClassMethod(
+ "operator==",
+ "bool",
+ [Argument("const %s&" % self.makeClassName(self.dictionary), "aOther")],
+ const=True,
+ body=body.define(),
+ )
+
+ def getStructs(self):
+ d = self.dictionary
+ selfName = self.makeClassName(d)
+ members = [
+ ClassMember(
+ self.makeMemberName(m[0].identifier.name),
+ self.getMemberType(m),
+ visibility="public",
+ body=self.getMemberInitializer(m),
+ hasIgnoreInitCheckFlag=True,
+ )
+ for m in self.memberInfo
+ ]
+ if d.parent:
+ # We always want to init our parent with our non-initializing
+ # constructor arg, because either we're about to init ourselves (and
+ # hence our parent) or we don't want any init happening.
+ baseConstructors = [
+ "%s(%s)"
+ % (self.makeClassName(d.parent), self.getNonInitializingCtorArg())
+ ]
+ else:
+ baseConstructors = None
+
+ if d.needsConversionFromJS:
+ initArgs = "nullptr, JS::NullHandleValue"
+ else:
+ initArgs = ""
+ ctors = [
+ ClassConstructor(
+ [],
+ visibility="public",
+ baseConstructors=baseConstructors,
+ body=(
+ "// Safe to pass a null context if we pass a null value\n"
+ "Init(%s);\n" % initArgs
+ ),
+ ),
+ ClassConstructor(
+ [Argument("const FastDictionaryInitializer&", "")],
+ visibility="public",
+ baseConstructors=baseConstructors,
+ explicit=True,
+ bodyInHeader=True,
+ body='// Do nothing here; this is used by our "Fast" subclass\n',
+ ),
+ ]
+ methods = []
+
+ if self.needToInitIds:
+ methods.append(self.initIdsMethod())
+
+ if d.needsConversionFromJS:
+ methods.append(self.initMethod())
+ methods.append(self.initWithoutCallContextMethod())
+ else:
+ methods.append(self.simpleInitMethod())
+
+ canBeRepresentedAsJSON = self.dictionarySafeToJSONify(d)
+ if canBeRepresentedAsJSON and d.getExtendedAttribute("GenerateInitFromJSON"):
+ methods.append(self.initFromJSONMethod())
+
+ if d.needsConversionToJS:
+ methods.append(self.toObjectInternalMethod())
+
+ if canBeRepresentedAsJSON and d.getExtendedAttribute("GenerateToJSON"):
+ methods.append(self.toJSONMethod())
+
+ methods.append(self.traceDictionaryMethod())
+
+ try:
+ if self.dictionaryNeedsCycleCollection(d):
+ methods.append(self.traverseForCCMethod())
+ methods.append(self.unlinkForCCMethod())
+ except CycleCollectionUnsupported:
+ # We have some member that we don't know how to CC. Don't output
+ # our cycle collection overloads, so attempts to CC us will fail to
+ # compile instead of misbehaving.
+ pass
+
+ ctors.append(
+ ClassConstructor(
+ [Argument("%s&&" % selfName, "aOther")],
+ default=True,
+ visibility="public",
+ baseConstructors=baseConstructors,
+ )
+ )
+
+ if CGDictionary.isDictionaryCopyConstructible(d):
+ disallowCopyConstruction = False
+ # Note: gcc's -Wextra has a warning against not initializng our
+ # base explicitly. If we have one. Use our non-initializing base
+ # constructor to get around that.
+ ctors.append(
+ ClassConstructor(
+ [Argument("const %s&" % selfName, "aOther")],
+ bodyInHeader=True,
+ visibility="public",
+ baseConstructors=baseConstructors,
+ explicit=True,
+ body="*this = aOther;\n",
+ )
+ )
+ methods.append(self.assignmentOperator())
+ else:
+ disallowCopyConstruction = True
+
+ if self.canHaveEqualsOperator():
+ methods.append(self.equalsOperator())
+
+ struct = CGClass(
+ selfName,
+ bases=[ClassBase(self.base())],
+ members=members,
+ constructors=ctors,
+ methods=methods,
+ isStruct=True,
+ disallowCopyConstruction=disallowCopyConstruction,
+ )
+
+ fastDictionaryCtor = ClassConstructor(
+ [],
+ visibility="public",
+ bodyInHeader=True,
+ baseConstructors=["%s(%s)" % (selfName, self.getNonInitializingCtorArg())],
+ body="// Doesn't matter what int we pass to the parent constructor\n",
+ )
+
+ fastStruct = CGClass(
+ "Fast" + selfName,
+ bases=[ClassBase(selfName)],
+ constructors=[fastDictionaryCtor],
+ isStruct=True,
+ )
+
+ return CGList([struct, CGNamespace("binding_detail", fastStruct)], "\n")
+
+ def deps(self):
+ return self.dictionary.getDeps()
+
+ @staticmethod
+ def makeDictionaryName(dictionary):
+ return dictionary.identifier.name
+
+ def makeClassName(self, dictionary):
+ return self.makeDictionaryName(dictionary)
+
+ @staticmethod
+ def makeMemberName(name):
+ return "m" + name[0].upper() + IDLToCIdentifier(name[1:])
+
+ def getMemberType(self, memberInfo):
+ _, conversionInfo = memberInfo
+ # We can't handle having a holderType here
+ assert conversionInfo.holderType is None
+ declType = conversionInfo.declType
+ if conversionInfo.dealWithOptional:
+ declType = CGTemplatedType("Optional", declType)
+ return declType.define()
+
+ def getMemberConversion(self, memberInfo, isKnownMissing=False):
+ """
+ A function that outputs the initialization of a single dictionary
+ member from the given dictionary value.
+
+ We start with our conversionInfo, which tells us how to
+ convert a JS::Value to whatever type this member is. We
+ substiture the template from the conversionInfo with values
+ that point to our "temp" JS::Value and our member (which is
+ the C++ value we want to produce). The output is a string of
+ code to do the conversion. We store this string in
+ conversionReplacements["convert"].
+
+ Now we have three different ways we might use (or skip) this
+ string of code, depending on whether the value is required,
+ optional with default value, or optional without default
+ value. We set up a template in the 'conversion' variable for
+ exactly how to do this, then substitute into it from the
+ conversionReplacements dictionary.
+ """
+ member, conversionInfo = memberInfo
+
+ # We should only be initializing things with default values if
+ # we're always-missing.
+ assert not isKnownMissing or (member.optional and member.defaultValue)
+
+ replacements = {
+ "declName": self.makeMemberName(member.identifier.name),
+ # We need a holder name for external interfaces, but
+ # it's scoped down to the conversion so we can just use
+ # anything we want.
+ "holderName": "holder",
+ "passedToJSImpl": "passedToJSImpl",
+ }
+
+ if isKnownMissing:
+ replacements["val"] = "(JS::NullHandleValue)"
+ else:
+ replacements["val"] = "temp.ref()"
+ replacements["maybeMutableVal"] = "temp.ptr()"
+
+ # We can't handle having a holderType here
+ assert conversionInfo.holderType is None
+ if conversionInfo.dealWithOptional:
+ replacements["declName"] = "(" + replacements["declName"] + ".Value())"
+ if member.defaultValue:
+ if isKnownMissing:
+ replacements["haveValue"] = "false"
+ else:
+ replacements["haveValue"] = "!isNull && !temp->isUndefined()"
+
+ propId = self.makeIdName(member.identifier.name)
+ propGet = "JS_GetPropertyById(cx, *object, atomsCache->%s, temp.ptr())" % propId
+
+ conversionReplacements = {
+ "prop": self.makeMemberName(member.identifier.name),
+ "convert": string.Template(conversionInfo.template).substitute(
+ replacements
+ ),
+ "propGet": propGet,
+ }
+ # The conversion code will only run where a default value or a value passed
+ # by the author needs to get converted, so we can remember if we have any
+ # members present here.
+ conversionReplacements["convert"] += "mIsAnyMemberPresent = true;\n"
+ if isKnownMissing:
+ conversion = ""
+ else:
+ setTempValue = CGGeneric(
+ dedent(
+ """
+ if (!${propGet}) {
+ return false;
+ }
+ """
+ )
+ )
+ conditions = getConditionList(member, "cx", "*object")
+ if len(conditions) != 0:
+ setTempValue = CGIfElseWrapper(
+ conditions.define(),
+ setTempValue,
+ CGGeneric("temp->setUndefined();\n"),
+ )
+ setTempValue = CGIfWrapper(setTempValue, "!isNull")
+ conversion = setTempValue.define()
+
+ if member.defaultValue:
+ if member.type.isUnion() and (
+ not member.type.nullable()
+ or not isinstance(member.defaultValue, IDLNullValue)
+ ):
+ # Since this has a default value, it might have been initialized
+ # already. Go ahead and uninit it before we try to init it
+ # again.
+ memberName = self.makeMemberName(member.identifier.name)
+ if member.type.nullable():
+ conversion += fill(
+ """
+ if (!${memberName}.IsNull()) {
+ ${memberName}.Value().Uninit();
+ }
+ """,
+ memberName=memberName,
+ )
+ else:
+ conversion += "%s.Uninit();\n" % memberName
+ conversion += "${convert}"
+ elif not conversionInfo.dealWithOptional:
+ # We're required, but have no default value. Make sure
+ # that we throw if we have no value provided.
+ conversion += dedent(
+ """
+ if (!isNull && !temp->isUndefined()) {
+ ${convert}
+ } else if (cx) {
+ // Don't error out if we have no cx. In that
+ // situation the caller is default-constructing us and we'll
+ // just assume they know what they're doing.
+ return cx.ThrowErrorMessage<MSG_MISSING_REQUIRED_DICTIONARY_MEMBER>("%s");
+ }
+ """
+ % self.getMemberSourceDescription(member)
+ )
+ conversionReplacements["convert"] = indent(
+ conversionReplacements["convert"]
+ ).rstrip()
+ else:
+ conversion += (
+ "if (!isNull && !temp->isUndefined()) {\n"
+ " ${prop}.Construct();\n"
+ "${convert}"
+ "}\n"
+ )
+ conversionReplacements["convert"] = indent(
+ conversionReplacements["convert"]
+ )
+
+ return CGGeneric(string.Template(conversion).substitute(conversionReplacements))
+
+ def getMemberDefinition(self, memberInfo):
+ member = memberInfo[0]
+ declType = memberInfo[1].declType
+ memberLoc = self.makeMemberName(member.identifier.name)
+ if not member.canHaveMissingValue():
+ memberData = memberLoc
+ else:
+ # The data is inside the Optional<>
+ memberData = "%s.InternalValue()" % memberLoc
+
+ # If you have to change this list (which you shouldn't!), make sure it
+ # continues to match the list in test_Object.prototype_props.html
+ if member.identifier.name in [
+ "constructor",
+ "toString",
+ "toLocaleString",
+ "valueOf",
+ "hasOwnProperty",
+ "isPrototypeOf",
+ "propertyIsEnumerable",
+ "__defineGetter__",
+ "__defineSetter__",
+ "__lookupGetter__",
+ "__lookupSetter__",
+ "__proto__",
+ ]:
+ raise TypeError(
+ "'%s' member of %s dictionary shadows "
+ "a property of Object.prototype, and Xrays to "
+ "Object can't handle that.\n"
+ "%s"
+ % (
+ member.identifier.name,
+ self.dictionary.identifier.name,
+ member.location,
+ )
+ )
+
+ propDef = (
+ "JS_DefinePropertyById(cx, obj, atomsCache->%s, temp, JSPROP_ENUMERATE)"
+ % self.makeIdName(member.identifier.name)
+ )
+
+ innerTemplate = wrapForType(
+ member.type,
+ self.descriptorProvider,
+ {
+ "result": "currentValue",
+ "successCode": (
+ "if (!%s) {\n" " return false;\n" "}\n" "break;\n" % propDef
+ ),
+ "jsvalRef": "temp",
+ "jsvalHandle": "&temp",
+ "returnsNewObject": False,
+ # 'obj' can just be allowed to be the string "obj", since that
+ # will be our dictionary object, which is presumably itself in
+ # the right scope.
+ "spiderMonkeyInterfacesAreStructs": True,
+ },
+ )
+ conversion = CGGeneric(innerTemplate)
+ conversion = CGWrapper(
+ conversion,
+ pre=(
+ "JS::Rooted<JS::Value> temp(cx);\n"
+ "%s const & currentValue = %s;\n" % (declType.define(), memberData)
+ ),
+ )
+
+ # Now make sure that our successCode can actually break out of the
+ # conversion. This incidentally gives us a scope for 'temp' and
+ # 'currentValue'.
+ conversion = CGWrapper(
+ CGIndenter(conversion),
+ pre=(
+ "do {\n"
+ " // block for our 'break' successCode and scope for 'temp' and 'currentValue'\n"
+ ),
+ post="} while(false);\n",
+ )
+ if member.canHaveMissingValue():
+ # Only do the conversion if we have a value
+ conversion = CGIfWrapper(conversion, "%s.WasPassed()" % memberLoc)
+ conditions = getConditionList(member, "cx", "obj")
+ if len(conditions) != 0:
+ conversion = CGIfWrapper(conversion, conditions.define())
+ return conversion
+
+ def getMemberTrace(self, member):
+ type = member.type
+ assert typeNeedsRooting(type)
+ memberLoc = self.makeMemberName(member.identifier.name)
+ if not member.canHaveMissingValue():
+ memberData = memberLoc
+ else:
+ # The data is inside the Optional<>
+ memberData = "%s.Value()" % memberLoc
+
+ memberName = "%s.%s" % (self.makeClassName(self.dictionary), memberLoc)
+
+ if type.isObject():
+ trace = CGGeneric(
+ 'JS::TraceRoot(trc, %s, "%s");\n' % ("&" + memberData, memberName)
+ )
+ if type.nullable():
+ trace = CGIfWrapper(trace, memberData)
+ elif type.isAny():
+ trace = CGGeneric(
+ 'JS::TraceRoot(trc, %s, "%s");\n' % ("&" + memberData, memberName)
+ )
+ elif (
+ type.isSequence()
+ or type.isDictionary()
+ or type.isSpiderMonkeyInterface()
+ or type.isUnion()
+ or type.isRecord()
+ ):
+ if type.nullable():
+ memberNullable = memberData
+ memberData = "%s.Value()" % memberData
+ if type.isSequence():
+ trace = CGGeneric("DoTraceSequence(trc, %s);\n" % memberData)
+ elif type.isDictionary():
+ trace = CGGeneric("%s.TraceDictionary(trc);\n" % memberData)
+ elif type.isUnion():
+ trace = CGGeneric("%s.TraceUnion(trc);\n" % memberData)
+ elif type.isRecord():
+ trace = CGGeneric("TraceRecord(trc, %s);\n" % memberData)
+ else:
+ assert type.isSpiderMonkeyInterface()
+ trace = CGGeneric("%s.TraceSelf(trc);\n" % memberData)
+ if type.nullable():
+ trace = CGIfWrapper(trace, "!%s.IsNull()" % memberNullable)
+ else:
+ assert False # unknown type
+
+ if member.canHaveMissingValue():
+ trace = CGIfWrapper(trace, "%s.WasPassed()" % memberLoc)
+
+ return trace.define()
+
+ def getMemberInitializer(self, memberInfo):
+ """
+ Get the right initializer for the member. Most members don't need one,
+ but we need to pre-initialize 'object' that have a default value or are
+ required (and hence are not inside Optional), so they're safe to trace
+ at all times. And we can optimize a bit for dictionary-typed members.
+ """
+ member, _ = memberInfo
+ if member.canHaveMissingValue():
+ # Allowed missing value means no need to set it up front, since it's
+ # inside an Optional and won't get traced until it's actually set
+ # up.
+ return None
+ type = member.type
+ if type.isDictionary():
+ # When we construct ourselves, we don't want to init our member
+ # dictionaries. Either we're being constructed-but-not-initialized
+ # ourselves (and then we don't want to init them) or we're about to
+ # init ourselves and then we'll init them anyway.
+ return CGDictionary.getNonInitializingCtorArg()
+ return initializerForType(type)
+
+ def getMemberSourceDescription(self, member):
+ return "'%s' member of %s" % (
+ member.identifier.name,
+ self.dictionary.identifier.name,
+ )
+
+ @staticmethod
+ def makeIdName(name):
+ return IDLToCIdentifier(name) + "_id"
+
+ @staticmethod
+ def getNonInitializingCtorArg():
+ return "FastDictionaryInitializer()"
+
+ @staticmethod
+ def isDictionaryCopyConstructible(dictionary):
+ if dictionary.parent and not CGDictionary.isDictionaryCopyConstructible(
+ dictionary.parent
+ ):
+ return False
+ return all(isTypeCopyConstructible(m.type) for m in dictionary.members)
+
+ @staticmethod
+ def typeSafeToJSONify(type):
+ """
+ Determine whether the given type is safe to convert to JSON. The
+ restriction is that this needs to be safe while in a global controlled
+ by an adversary, and "safe" means no side-effects when the JS
+ representation of this type is converted to JSON. That means that we
+ have to be pretty restrictive about what things we can allow. For
+ example, "object" is out, because it may have accessor properties on it.
+ """
+ if type.nullable():
+ # Converting null to JSON is always OK.
+ return CGDictionary.typeSafeToJSONify(type.inner)
+
+ if type.isSequence():
+ # Sequences are arrays we create ourselves, with no holes. They
+ # should be safe if their contents are safe, as long as we suppress
+ # invocation of .toJSON on objects.
+ return CGDictionary.typeSafeToJSONify(type.inner)
+
+ if type.isUnion():
+ # OK if everything in it is ok.
+ return all(CGDictionary.typeSafeToJSONify(t) for t in type.flatMemberTypes)
+
+ if type.isDictionary():
+ # OK if the dictionary is OK
+ return CGDictionary.dictionarySafeToJSONify(type.inner)
+
+ if type.isUndefined() or type.isString() or type.isEnum():
+ # Strings are always OK.
+ return True
+
+ if type.isPrimitive():
+ # Primitives (numbers and booleans) are ok, as long as
+ # they're not unrestricted float/double.
+ return not type.isFloat() or not type.isUnrestricted()
+
+ if type.isRecord():
+ # Records are okay, as long as the value type is.
+ # Per spec, only strings are allowed as keys.
+ return CGDictionary.typeSafeToJSONify(type.inner)
+
+ return False
+
+ @staticmethod
+ def dictionarySafeToJSONify(dictionary):
+ # The dictionary itself is OK, so we're good if all our types are.
+ return all(CGDictionary.typeSafeToJSONify(m.type) for m in dictionary.members)
+
+
+class CGRegisterWorkerBindings(CGAbstractMethod):
+ def __init__(self, config):
+ CGAbstractMethod.__init__(
+ self,
+ None,
+ "RegisterWorkerBindings",
+ "bool",
+ [Argument("JSContext*", "aCx"), Argument("JS::Handle<JSObject*>", "aObj")],
+ )
+ self.config = config
+
+ def definition_body(self):
+ descriptors = self.config.getDescriptors(
+ hasInterfaceObject=True, isExposedInAnyWorker=True, register=True
+ )
+ conditions = []
+ for desc in descriptors:
+ bindingNS = toBindingNamespace(desc.name)
+ condition = "!%s::GetConstructorObject(aCx)" % bindingNS
+ if desc.isExposedConditionally():
+ condition = (
+ "%s::ConstructorEnabled(aCx, aObj) && " % bindingNS + condition
+ )
+ conditions.append(condition)
+ lines = [
+ CGIfWrapper(CGGeneric("return false;\n"), condition)
+ for condition in conditions
+ ]
+ lines.append(CGGeneric("return true;\n"))
+ return CGList(lines, "\n").define()
+
+
+class CGRegisterWorkerDebuggerBindings(CGAbstractMethod):
+ def __init__(self, config):
+ CGAbstractMethod.__init__(
+ self,
+ None,
+ "RegisterWorkerDebuggerBindings",
+ "bool",
+ [Argument("JSContext*", "aCx"), Argument("JS::Handle<JSObject*>", "aObj")],
+ )
+ self.config = config
+
+ def definition_body(self):
+ descriptors = self.config.getDescriptors(
+ hasInterfaceObject=True, isExposedInWorkerDebugger=True, register=True
+ )
+ conditions = []
+ for desc in descriptors:
+ bindingNS = toBindingNamespace(desc.name)
+ condition = "!%s::GetConstructorObject(aCx)" % bindingNS
+ if desc.isExposedConditionally():
+ condition = (
+ "%s::ConstructorEnabled(aCx, aObj) && " % bindingNS + condition
+ )
+ conditions.append(condition)
+ lines = [
+ CGIfWrapper(CGGeneric("return false;\n"), condition)
+ for condition in conditions
+ ]
+ lines.append(CGGeneric("return true;\n"))
+ return CGList(lines, "\n").define()
+
+
+class CGRegisterWorkletBindings(CGAbstractMethod):
+ def __init__(self, config):
+ CGAbstractMethod.__init__(
+ self,
+ None,
+ "RegisterWorkletBindings",
+ "bool",
+ [Argument("JSContext*", "aCx"), Argument("JS::Handle<JSObject*>", "aObj")],
+ )
+ self.config = config
+
+ def definition_body(self):
+ descriptors = self.config.getDescriptors(
+ hasInterfaceObject=True, isExposedInAnyWorklet=True, register=True
+ )
+ conditions = []
+ for desc in descriptors:
+ bindingNS = toBindingNamespace(desc.name)
+ condition = "!%s::GetConstructorObject(aCx)" % bindingNS
+ if desc.isExposedConditionally():
+ condition = (
+ "%s::ConstructorEnabled(aCx, aObj) && " % bindingNS + condition
+ )
+ conditions.append(condition)
+ lines = [
+ CGIfWrapper(CGGeneric("return false;\n"), condition)
+ for condition in conditions
+ ]
+ lines.append(CGGeneric("return true;\n"))
+ return CGList(lines, "\n").define()
+
+
+class CGRegisterShadowRealmBindings(CGAbstractMethod):
+ def __init__(self, config):
+ CGAbstractMethod.__init__(
+ self,
+ None,
+ "RegisterShadowRealmBindings",
+ "bool",
+ [Argument("JSContext*", "aCx"), Argument("JS::Handle<JSObject*>", "aObj")],
+ )
+ self.config = config
+
+ def definition_body(self):
+ descriptors = self.config.getDescriptors(
+ hasInterfaceObject=True, isExposedInShadowRealms=True, register=True
+ )
+ conditions = []
+ for desc in descriptors:
+ bindingNS = toBindingNamespace(desc.name)
+ condition = "!%s::GetConstructorObject(aCx)" % bindingNS
+ if desc.isExposedConditionally():
+ condition = (
+ "%s::ConstructorEnabled(aCx, aObj) && " % bindingNS + condition
+ )
+ conditions.append(condition)
+ lines = [
+ CGIfWrapper(CGGeneric("return false;\n"), condition)
+ for condition in conditions
+ ]
+ lines.append(CGGeneric("return true;\n"))
+ return CGList(lines, "\n").define()
+
+
+def BindingNamesOffsetEnum(name):
+ return CppKeywords.checkMethodName(name.replace(" ", "_"))
+
+
+class CGGlobalNames(CGGeneric):
+ def __init__(self, names):
+ """
+ names is expected to be a list of tuples of the name and the descriptor it refers to.
+ """
+
+ strings = []
+ entries = []
+ for name, desc in names:
+ # Generate the entry declaration
+ # XXX(nika): mCreate & mEnabled require relocations. If we want to
+ # reduce those, we could move them into separate tables.
+ nativeEntry = fill(
+ """
+ {
+ /* mNameOffset */ BindingNamesOffset::${nameOffset},
+ /* mNameLength */ ${nameLength},
+ /* mConstructorId */ constructors::id::${realname},
+ /* mCreate */ ${realname}_Binding::CreateInterfaceObjects,
+ /* mEnabled */ ${enabled}
+ }
+ """,
+ nameOffset=BindingNamesOffsetEnum(name),
+ nameLength=len(name),
+ name=name,
+ realname=desc.name,
+ enabled=(
+ "%s_Binding::ConstructorEnabled" % desc.name
+ if desc.isExposedConditionally()
+ else "nullptr"
+ ),
+ )
+
+ entries.append((name, nativeEntry))
+
+ # Unfortunately, when running tests, we may have no entries.
+ # PerfectHash will assert if we give it an empty set of entries, so we
+ # just generate a dummy value.
+ if len(entries) == 0:
+ CGGeneric.__init__(
+ self,
+ define=dedent(
+ """
+ static_assert(false, "No WebIDL global name entries!");
+ """
+ ),
+ )
+ return
+
+ # Build the perfect hash function.
+ phf = PerfectHash(entries, GLOBAL_NAMES_PHF_SIZE)
+
+ # Generate code for the PHF
+ phfCodegen = phf.codegen(
+ "WebIDLGlobalNameHash::sEntries", "WebIDLNameTableEntry"
+ )
+ entries = phfCodegen.gen_entries(lambda e: e[1])
+ getter = phfCodegen.gen_jslinearstr_getter(
+ name="WebIDLGlobalNameHash::GetEntry",
+ return_type="const WebIDLNameTableEntry*",
+ return_entry=dedent(
+ """
+ if (JS_LinearStringEqualsAscii(aKey, BindingName(entry.mNameOffset), entry.mNameLength)) {
+ return &entry;
+ }
+ return nullptr;
+ """
+ ),
+ )
+
+ define = fill(
+ """
+ const uint32_t WebIDLGlobalNameHash::sCount = ${count};
+
+ $*{entries}
+
+ $*{getter}
+ """,
+ count=len(phf.entries),
+ strings="\n".join(strings) + ";\n",
+ entries=entries,
+ getter=getter,
+ )
+ CGGeneric.__init__(self, define=define)
+
+
+def dependencySortObjects(objects, dependencyGetter, nameGetter):
+ """
+ Sort IDL objects with dependencies on each other such that if A
+ depends on B then B will come before A. This is needed for
+ declaring C++ classes in the right order, for example. Objects
+ that have no dependencies are just sorted by name.
+
+ objects should be something that can produce a set of objects
+ (e.g. a set, iterator, list, etc).
+
+ dependencyGetter is something that, given an object, should return
+ the set of objects it depends on.
+ """
+ # XXXbz this will fail if we have two webidl files F1 and F2 such that F1
+ # declares an object which depends on an object in F2, and F2 declares an
+ # object (possibly a different one!) that depends on an object in F1. The
+ # good news is that I expect this to never happen.
+ sortedObjects = []
+ objects = set(objects)
+ while len(objects) != 0:
+ # Find the dictionaries that don't depend on anything else
+ # anymore and move them over.
+ toMove = [o for o in objects if len(dependencyGetter(o) & objects) == 0]
+ if len(toMove) == 0:
+ raise TypeError(
+ "Loop in dependency graph\n" + "\n".join(o.location for o in objects)
+ )
+ objects = objects - set(toMove)
+ sortedObjects.extend(sorted(toMove, key=nameGetter))
+ return sortedObjects
+
+
+class ForwardDeclarationBuilder:
+ """
+ Create a canonical representation of a set of namespaced forward
+ declarations.
+ """
+
+ def __init__(self):
+ """
+ The set of declarations is represented as a tree of nested namespaces.
+ Each tree node has a set of declarations |decls| and a dict |children|.
+ Each declaration is a pair consisting of the class name and a boolean
+ that is true iff the class is really a struct. |children| maps the
+ names of inner namespaces to the declarations in that namespace.
+ """
+ self.decls = set()
+ self.children = {}
+
+ def _ensureNonTemplateType(self, type):
+ if "<" in type:
+ # This is a templated type. We don't really know how to
+ # forward-declare those, and trying to do it naively is not going to
+ # go well (e.g. we may have :: characters inside the type we're
+ # templated on!). Just bail out.
+ raise TypeError(
+ "Attempt to use ForwardDeclarationBuilder on "
+ "templated type %s. We don't know how to do that "
+ "yet." % type
+ )
+
+ def _listAdd(self, namespaces, name, isStruct=False):
+ """
+ Add a forward declaration, where |namespaces| is a list of namespaces.
+ |name| should not contain any other namespaces.
+ """
+ if namespaces:
+ child = self.children.setdefault(namespaces[0], ForwardDeclarationBuilder())
+ child._listAdd(namespaces[1:], name, isStruct)
+ else:
+ assert "::" not in name
+ self.decls.add((name, isStruct))
+
+ def addInMozillaDom(self, name, isStruct=False):
+ """
+ Add a forward declaration to the mozilla::dom:: namespace. |name| should not
+ contain any other namespaces.
+ """
+ self._ensureNonTemplateType(name)
+ self._listAdd(["mozilla", "dom"], name, isStruct)
+
+ def add(self, nativeType, isStruct=False):
+ """
+ Add a forward declaration, where |nativeType| is a string containing
+ the type and its namespaces, in the usual C++ way.
+ """
+ self._ensureNonTemplateType(nativeType)
+ components = nativeType.split("::")
+ self._listAdd(components[:-1], components[-1], isStruct)
+
+ def _build(self, atTopLevel):
+ """
+ Return a codegenerator for the forward declarations.
+ """
+ decls = []
+ if self.decls:
+ decls.append(
+ CGList(
+ [
+ CGClassForwardDeclare(cname, isStruct)
+ for cname, isStruct in sorted(self.decls)
+ ]
+ )
+ )
+ for namespace, child in sorted(six.iteritems(self.children)):
+ decls.append(CGNamespace(namespace, child._build(atTopLevel=False)))
+
+ cg = CGList(decls, "\n")
+ if not atTopLevel and len(decls) + len(self.decls) > 1:
+ cg = CGWrapper(cg, pre="\n", post="\n")
+ return cg
+
+ def build(self):
+ return self._build(atTopLevel=True)
+
+ def forwardDeclareForType(self, t, config):
+ t = t.unroll()
+ if t.isGeckoInterface():
+ name = t.inner.identifier.name
+ try:
+ desc = config.getDescriptor(name)
+ self.add(desc.nativeType)
+ except NoSuchDescriptorError:
+ pass
+
+ # Note: SpiderMonkey interfaces are typedefs, so can't be
+ # forward-declared
+ elif t.isPromise():
+ self.addInMozillaDom("Promise")
+ elif t.isCallback():
+ self.addInMozillaDom(t.callback.identifier.name)
+ elif t.isDictionary():
+ self.addInMozillaDom(t.inner.identifier.name, isStruct=True)
+ elif t.isCallbackInterface():
+ self.addInMozillaDom(t.inner.identifier.name)
+ elif t.isUnion():
+ # Forward declare both the owning and non-owning version,
+ # since we don't know which one we might want
+ self.addInMozillaDom(CGUnionStruct.unionTypeName(t, False))
+ self.addInMozillaDom(CGUnionStruct.unionTypeName(t, True))
+ elif t.isRecord():
+ self.forwardDeclareForType(t.inner, config)
+ # Don't need to do anything for void, primitive, string, any or object.
+ # There may be some other cases we are missing.
+
+
+class CGForwardDeclarations(CGWrapper):
+ """
+ Code generate the forward declarations for a header file.
+ additionalDeclarations is a list of tuples containing a classname and a
+ boolean. If the boolean is true we will declare a struct, otherwise we'll
+ declare a class.
+ """
+
+ def __init__(
+ self,
+ config,
+ descriptors,
+ callbacks,
+ dictionaries,
+ callbackInterfaces,
+ additionalDeclarations=[],
+ ):
+ builder = ForwardDeclarationBuilder()
+
+ # Needed for at least Wrap.
+ for d in descriptors:
+ # If this is a generated iterator interface, we only create these
+ # in the generated bindings, and don't need to forward declare.
+ if (
+ d.interface.isIteratorInterface()
+ or d.interface.isAsyncIteratorInterface()
+ ):
+ continue
+ builder.add(d.nativeType)
+ if d.interface.isSerializable():
+ builder.add("nsIGlobalObject")
+ # 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.maplikeOrSetlikeOrIterable:
+ if d.interface.maplikeOrSetlikeOrIterable.hasKeyType():
+ builder.forwardDeclareForType(
+ d.interface.maplikeOrSetlikeOrIterable.keyType, config
+ )
+ if d.interface.maplikeOrSetlikeOrIterable.hasValueType():
+ builder.forwardDeclareForType(
+ d.interface.maplikeOrSetlikeOrIterable.valueType, config
+ )
+
+ for m in d.interface.members:
+ if m.isAttr() and m.type.isObservableArray():
+ builder.forwardDeclareForType(m.type, config)
+
+ # We just about always need NativePropertyHooks
+ builder.addInMozillaDom("NativePropertyHooks", isStruct=True)
+ builder.addInMozillaDom("ProtoAndIfaceCache")
+
+ for callback in callbacks:
+ builder.addInMozillaDom(callback.identifier.name)
+ for t in getTypesFromCallback(callback):
+ builder.forwardDeclareForType(t, config)
+
+ for d in callbackInterfaces:
+ builder.add(d.nativeType)
+ builder.add(d.nativeType + "Atoms", isStruct=True)
+ for t in getTypesFromDescriptor(d):
+ builder.forwardDeclareForType(t, config)
+ if d.hasCEReactions():
+ builder.addInMozillaDom("DocGroup")
+
+ for d in dictionaries:
+ if len(d.members) > 0:
+ builder.addInMozillaDom(d.identifier.name + "Atoms", isStruct=True)
+ for t in getTypesFromDictionary(d):
+ builder.forwardDeclareForType(t, config)
+
+ for className, isStruct in additionalDeclarations:
+ builder.add(className, isStruct=isStruct)
+
+ CGWrapper.__init__(self, builder.build())
+
+
+def dependencySortDictionariesAndUnionsAndCallbacks(types):
+ def getDependenciesFromType(type):
+ if type.isDictionary():
+ return set([type.unroll().inner])
+ if type.isSequence():
+ return getDependenciesFromType(type.unroll())
+ if type.isUnion():
+ return set([type.unroll()])
+ if type.isRecord():
+ return set([type.unroll().inner])
+ if type.isCallback():
+ return set([type.unroll()])
+ return set()
+
+ def getDependencies(unionTypeOrDictionaryOrCallback):
+ if isinstance(unionTypeOrDictionaryOrCallback, IDLDictionary):
+ deps = set()
+ if unionTypeOrDictionaryOrCallback.parent:
+ deps.add(unionTypeOrDictionaryOrCallback.parent)
+ for member in unionTypeOrDictionaryOrCallback.members:
+ deps |= getDependenciesFromType(member.type)
+ return deps
+
+ if (
+ unionTypeOrDictionaryOrCallback.isType()
+ and unionTypeOrDictionaryOrCallback.isUnion()
+ ):
+ deps = set()
+ for member in unionTypeOrDictionaryOrCallback.flatMemberTypes:
+ deps |= getDependenciesFromType(member)
+ return deps
+
+ assert unionTypeOrDictionaryOrCallback.isCallback()
+ return set()
+
+ def getName(unionTypeOrDictionaryOrCallback):
+ if isinstance(unionTypeOrDictionaryOrCallback, IDLDictionary):
+ return unionTypeOrDictionaryOrCallback.identifier.name
+
+ if (
+ unionTypeOrDictionaryOrCallback.isType()
+ and unionTypeOrDictionaryOrCallback.isUnion()
+ ):
+ return unionTypeOrDictionaryOrCallback.name
+
+ assert unionTypeOrDictionaryOrCallback.isCallback()
+ return unionTypeOrDictionaryOrCallback.identifier.name
+
+ return dependencySortObjects(types, getDependencies, getName)
+
+
+class CGBindingRoot(CGThing):
+ """
+ Root codegen class for binding generation. Instantiate the class, and call
+ declare or define to generate header or cpp code (respectively).
+ """
+
+ def __init__(self, config, prefix, webIDLFile):
+ bindingHeaders = dict.fromkeys(
+ ("mozilla/dom/NonRefcountedDOMObject.h", "MainThreadUtils.h"), True
+ )
+ bindingDeclareHeaders = dict.fromkeys(
+ (
+ "mozilla/dom/BindingDeclarations.h",
+ "mozilla/dom/Nullable.h",
+ ),
+ True,
+ )
+
+ descriptors = config.getDescriptors(
+ webIDLFile=webIDLFile, hasInterfaceOrInterfacePrototypeObject=True
+ )
+
+ unionTypes = UnionsForFile(config, webIDLFile)
+
+ (
+ unionHeaders,
+ unionImplheaders,
+ unionDeclarations,
+ traverseMethods,
+ unlinkMethods,
+ unionStructs,
+ ) = UnionTypes(unionTypes, config)
+
+ bindingDeclareHeaders.update(dict.fromkeys(unionHeaders, True))
+ bindingHeaders.update(dict.fromkeys(unionImplheaders, True))
+ bindingDeclareHeaders["mozilla/dom/UnionMember.h"] = len(unionStructs) > 0
+ bindingDeclareHeaders["mozilla/dom/FakeString.h"] = len(unionStructs) > 0
+ # BindingUtils.h is only needed for SetToObject.
+ # If it stops being inlined or stops calling CallerSubsumes
+ # both this bit and the bit in UnionTypes can be removed.
+ bindingDeclareHeaders["mozilla/dom/BindingUtils.h"] = any(
+ d.isObject() for t in unionTypes for d in t.flatMemberTypes
+ )
+ bindingHeaders["mozilla/dom/IterableIterator.h"] = any(
+ (
+ d.interface.isIteratorInterface()
+ and d.interface.maplikeOrSetlikeOrIterable.isPairIterator()
+ )
+ or d.interface.isAsyncIteratorInterface()
+ or d.interface.isIterable()
+ or d.interface.isAsyncIterable()
+ for d in descriptors
+ )
+
+ def memberNeedsSubjectPrincipal(d, m):
+ if m.isAttr():
+ return (
+ "needsSubjectPrincipal" in d.getExtendedAttributes(m, getter=True)
+ ) or (
+ not m.readonly
+ and "needsSubjectPrincipal"
+ in d.getExtendedAttributes(m, setter=True)
+ )
+ return m.isMethod() and "needsSubjectPrincipal" in d.getExtendedAttributes(
+ m
+ )
+
+ if any(
+ memberNeedsSubjectPrincipal(d, m)
+ for d in descriptors
+ for m in d.interface.members
+ ):
+ bindingHeaders["mozilla/BasePrincipal.h"] = True
+ bindingHeaders["nsJSPrincipals.h"] = True
+
+ # The conditions for which we generate profiler labels are fairly
+ # complicated. The check below is a little imprecise to make it simple.
+ # It includes the profiler header in all cases where it is necessary and
+ # generates only a few false positives.
+ bindingHeaders["mozilla/ProfilerLabels.h"] = any(
+ # constructor profiler label
+ d.interface.legacyFactoryFunctions
+ or (d.interface.hasInterfaceObject() and d.interface.ctor())
+ or any(
+ # getter/setter profiler labels
+ m.isAttr()
+ # method profiler label
+ or m.isMethod()
+ for m in d.interface.members
+ )
+ for d in descriptors
+ )
+
+ def descriptorHasCrossOriginProperties(desc):
+ def hasCrossOriginProperty(m):
+ props = memberProperties(m, desc)
+ return (
+ props.isCrossOriginMethod
+ or props.isCrossOriginGetter
+ or props.isCrossOriginSetter
+ )
+
+ 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)
+ or descriptorHasObservableArrayTypes(d)
+ for d in descriptors
+ )
+ bindingDeclareHeaders["js/TypeDecls.h"] = not bindingDeclareHeaders["jsapi.h"]
+ bindingDeclareHeaders["js/RootingAPI.h"] = not bindingDeclareHeaders["jsapi.h"]
+
+ # JS::IsCallable
+ bindingDeclareHeaders["js/CallAndConstruct.h"] = True
+
+ def descriptorHasIteratorAlias(desc):
+ def hasIteratorAlias(m):
+ return m.isMethod() and (
+ ("@@iterator" in m.aliases) or ("@@asyncIterator" in m.aliases)
+ )
+
+ return any(hasIteratorAlias(m) for m in desc.interface.members)
+
+ bindingHeaders["js/Symbol.h"] = any(
+ descriptorHasIteratorAlias(d) for d in descriptors
+ )
+
+ bindingHeaders["js/shadow/Object.h"] = any(
+ d.interface.hasMembersInSlots() for d in descriptors
+ )
+
+ # The symbols supplied by this header are used so ubiquitously it's not
+ # worth the effort delineating the exact dependency, if it can't be done
+ # *at* the places where their definitions are required.
+ bindingHeaders["js/experimental/JitInfo.h"] = True
+
+ # JS::GetClass, JS::GetCompartment, JS::GetReservedSlot, and
+ # JS::SetReservedSlot are also used too many places to restate
+ # dependency logic.
+ bindingHeaders["js/Object.h"] = True
+
+ # JS::IsCallable, JS::Call, JS::Construct
+ bindingHeaders["js/CallAndConstruct.h"] = True
+
+ # JS_IsExceptionPending
+ bindingHeaders["js/Exception.h"] = True
+
+ # JS::Map{Clear, Delete, Has, Get, Set}
+ bindingHeaders["js/MapAndSet.h"] = True
+
+ # JS_DefineElement, JS_DefineProperty, JS_DefinePropertyById,
+ # JS_DefineUCProperty, JS_ForwardGetPropertyTo, JS_GetProperty,
+ # JS_GetPropertyById, JS_HasPropertyById, JS_SetProperty,
+ # JS_SetPropertyById
+ bindingHeaders["js/PropertyAndElement.h"] = True
+
+ # JS_GetOwnPropertyDescriptorById
+ bindingHeaders["js/PropertyDescriptor.h"] = True
+
+ def descriptorDeprecated(desc):
+ iface = desc.interface
+ return any(
+ m.getExtendedAttribute("Deprecated") for m in iface.members + [iface]
+ )
+
+ bindingHeaders["mozilla/dom/Document.h"] = any(
+ descriptorDeprecated(d) for d in descriptors
+ )
+
+ bindingHeaders["mozilla/dom/DOMJSProxyHandler.h"] = any(
+ d.concrete and d.proxy for d in descriptors
+ )
+
+ bindingHeaders["mozilla/dom/ProxyHandlerUtils.h"] = any(
+ d.concrete and d.proxy for d in descriptors
+ )
+
+ bindingHeaders["js/String.h"] = any(
+ d.needsMissingPropUseCounters for d in descriptors
+ )
+
+ hasCrossOriginObjects = any(
+ d.concrete and d.isMaybeCrossOriginObject() for d in descriptors
+ )
+ bindingHeaders["mozilla/dom/MaybeCrossOriginObject.h"] = hasCrossOriginObjects
+ bindingHeaders["AccessCheck.h"] = hasCrossOriginObjects
+ hasCEReactions = any(d.hasCEReactions() for d in descriptors)
+ bindingHeaders["mozilla/dom/CustomElementRegistry.h"] = hasCEReactions
+ bindingHeaders["mozilla/dom/DocGroup.h"] = hasCEReactions
+
+ def descriptorHasChromeOnly(desc):
+ ctor = desc.interface.ctor()
+
+ return (
+ any(
+ isChromeOnly(a) or needsContainsHack(a) or needsCallerType(a)
+ for a in desc.interface.members
+ )
+ or desc.interface.getExtendedAttribute("ChromeOnly") is not None
+ or
+ # JS-implemented interfaces with an interface object get a
+ # chromeonly _create method. And interfaces with an
+ # interface object might have a ChromeOnly constructor.
+ (
+ desc.interface.hasInterfaceObject()
+ and (
+ desc.interface.isJSImplemented()
+ or (ctor and isChromeOnly(ctor))
+ )
+ )
+ )
+
+ # XXXkhuey ugly hack but this is going away soon.
+ bindingHeaders["xpcprivate.h"] = webIDLFile.endswith("EventTarget.webidl")
+
+ hasThreadChecks = any(d.hasThreadChecks() for d in descriptors)
+ bindingHeaders["nsThreadUtils.h"] = hasThreadChecks
+
+ dictionaries = config.getDictionaries(webIDLFile)
+
+ def dictionaryHasChromeOnly(dictionary):
+ while dictionary:
+ if any(isChromeOnly(m) for m in dictionary.members):
+ return True
+ dictionary = dictionary.parent
+ return False
+
+ def needsNonSystemPrincipal(member):
+ return (
+ member.getExtendedAttribute("NeedsSubjectPrincipal") == ["NonSystem"]
+ or member.getExtendedAttribute("SetterNeedsSubjectPrincipal")
+ == ["NonSystem"]
+ or member.getExtendedAttribute("GetterNeedsSubjectPrincipal")
+ == ["NonSystem"]
+ )
+
+ def descriptorNeedsNonSystemPrincipal(d):
+ return any(needsNonSystemPrincipal(m) for m in d.interface.members)
+
+ def descriptorHasPrefDisabler(desc):
+ iface = desc.interface
+ return any(
+ PropertyDefiner.getControllingCondition(m, desc).hasDisablers()
+ for m in iface.members
+ if (m.isMethod() or m.isAttr() or m.isConst())
+ )
+
+ def addPrefHeaderForObject(bindingHeaders, obj):
+ """
+ obj might be a dictionary member or an interface.
+ """
+ if obj is not None:
+ pref = PropertyDefiner.getStringAttr(obj, "Pref")
+ if pref:
+ bindingHeaders[prefHeader(pref)] = True
+
+ def addPrefHeadersForDictionary(bindingHeaders, dictionary):
+ while dictionary:
+ for m in dictionary.members:
+ addPrefHeaderForObject(bindingHeaders, m)
+ dictionary = dictionary.parent
+
+ for d in dictionaries:
+ addPrefHeadersForDictionary(bindingHeaders, d)
+ for d in descriptors:
+ interface = d.interface
+ addPrefHeaderForObject(bindingHeaders, interface)
+ addPrefHeaderForObject(bindingHeaders, interface.ctor())
+
+ bindingHeaders["mozilla/dom/WebIDLPrefs.h"] = any(
+ descriptorHasPrefDisabler(d) for d in descriptors
+ )
+ bindingHeaders["nsContentUtils.h"] = (
+ any(descriptorHasChromeOnly(d) for d in descriptors)
+ or any(descriptorNeedsNonSystemPrincipal(d) for d in descriptors)
+ or any(dictionaryHasChromeOnly(d) for d in dictionaries)
+ )
+ hasNonEmptyDictionaries = any(len(dict.members) > 0 for dict in dictionaries)
+ callbacks = config.getCallbacks(webIDLFile)
+ callbackDescriptors = config.getDescriptors(
+ webIDLFile=webIDLFile, isCallback=True
+ )
+ jsImplemented = config.getDescriptors(
+ webIDLFile=webIDLFile, isJSImplemented=True
+ )
+ bindingDeclareHeaders["nsWeakReference.h"] = jsImplemented
+ bindingDeclareHeaders["mozilla/dom/PrototypeList.h"] = descriptors
+ bindingHeaders["nsIGlobalObject.h"] = jsImplemented
+ bindingHeaders["AtomList.h"] = (
+ hasNonEmptyDictionaries or jsImplemented or callbackDescriptors
+ )
+
+ if callbackDescriptors:
+ bindingDeclareHeaders["mozilla/ErrorResult.h"] = True
+
+ def descriptorClearsPropsInSlots(descriptor):
+ if not descriptor.wrapperCache:
+ return False
+ return any(
+ m.isAttr() and m.getExtendedAttribute("StoreInSlot")
+ for m in descriptor.interface.members
+ )
+
+ bindingHeaders["nsJSUtils.h"] = any(
+ descriptorClearsPropsInSlots(d) for d in descriptors
+ )
+
+ # Make sure we can sanely use binding_detail in generated code.
+ cgthings = [
+ CGGeneric(
+ dedent(
+ """
+ namespace binding_detail {}; // Just to make sure it's known as a namespace
+ using namespace mozilla::dom::binding_detail;
+ """
+ )
+ )
+ ]
+
+ # Do codegen for all the enums
+ enums = config.getEnums(webIDLFile)
+ cgthings.extend(CGEnum(e) for e in enums)
+
+ bindingDeclareHeaders["mozilla/Span.h"] = enums
+ bindingDeclareHeaders["mozilla/ArrayUtils.h"] = enums
+
+ hasCode = descriptors or callbackDescriptors or dictionaries or callbacks
+ bindingHeaders["mozilla/dom/BindingUtils.h"] = hasCode
+ bindingHeaders["mozilla/OwningNonNull.h"] = hasCode
+ bindingHeaders["<type_traits>"] = hasCode
+ bindingHeaders["mozilla/dom/BindingDeclarations.h"] = not hasCode and enums
+
+ bindingHeaders["WrapperFactory.h"] = descriptors
+ bindingHeaders["mozilla/dom/DOMJSClass.h"] = descriptors
+ bindingHeaders["mozilla/dom/ScriptSettings.h"] = dictionaries # AutoJSAPI
+ # Ensure we see our enums in the generated .cpp file, for the ToJSValue
+ # method body. Also ensure that we see jsapi.h.
+ if enums:
+ bindingHeaders[CGHeaders.getDeclarationFilename(enums[0])] = True
+ bindingHeaders["jsapi.h"] = True
+
+ # For things that have [UseCounter] or [InstrumentedProps] or [Trial]
+ for d in descriptors:
+ if d.concrete:
+ if d.instrumentedProps:
+ bindingHeaders["mozilla/UseCounter.h"] = True
+ if d.needsMissingPropUseCounters:
+ bindingHeaders[prefHeader(MISSING_PROP_PREF)] = True
+ if d.interface.isSerializable():
+ bindingHeaders["mozilla/dom/StructuredCloneTags.h"] = True
+ if d.wantsXrays:
+ bindingHeaders["mozilla/Atomics.h"] = True
+ bindingHeaders["mozilla/dom/XrayExpandoClass.h"] = True
+ if d.wantsXrayExpandoClass:
+ bindingHeaders["XrayWrapper.h"] = True
+ for m in d.interface.members:
+ if m.getExtendedAttribute("UseCounter"):
+ bindingHeaders["mozilla/UseCounter.h"] = True
+ if m.getExtendedAttribute("Trial"):
+ bindingHeaders["mozilla/OriginTrials.h"] = True
+
+ bindingHeaders["mozilla/dom/SimpleGlobalObject.h"] = any(
+ CGDictionary.dictionarySafeToJSONify(d) for d in dictionaries
+ )
+
+ for ancestor in (findAncestorWithInstrumentedProps(d) for d in descriptors):
+ if not ancestor:
+ continue
+ bindingHeaders[CGHeaders.getDeclarationFilename(ancestor)] = True
+
+ cgthings.extend(traverseMethods)
+ cgthings.extend(unlinkMethods)
+
+ # Do codegen for all the dictionaries. We have to be a bit careful
+ # here, because we have to generate these in order from least derived
+ # to most derived so that class inheritance works out. We also have to
+ # generate members before the dictionary that contains them.
+
+ for t in dependencySortDictionariesAndUnionsAndCallbacks(
+ dictionaries + unionStructs + callbacks
+ ):
+ if t.isDictionary():
+ cgthings.append(CGDictionary(t, config))
+ elif t.isUnion():
+ cgthings.append(CGUnionStruct(t, config))
+ cgthings.append(CGUnionStruct(t, config, True))
+ else:
+ assert t.isCallback()
+ cgthings.append(CGCallbackFunction(t, config))
+ cgthings.append(CGNamespace("binding_detail", CGFastCallback(t)))
+
+ # Do codegen for all the descriptors
+ cgthings.extend([CGDescriptor(x) for x in descriptors])
+
+ # Do codegen for all the callback interfaces.
+ cgthings.extend([CGCallbackInterface(x) for x in callbackDescriptors])
+
+ cgthings.extend(
+ [
+ CGNamespace("binding_detail", CGFastCallback(x.interface))
+ for x in callbackDescriptors
+ ]
+ )
+
+ # Do codegen for JS implemented classes
+ def getParentDescriptor(desc):
+ if not desc.interface.parent:
+ return set()
+ return {desc.getDescriptor(desc.interface.parent.identifier.name)}
+
+ for x in dependencySortObjects(
+ jsImplemented, getParentDescriptor, lambda d: d.interface.identifier.name
+ ):
+ cgthings.append(
+ CGCallbackInterface(x, spiderMonkeyInterfacesAreStructs=True)
+ )
+ cgthings.append(CGJSImplClass(x))
+
+ # And make sure we have the right number of newlines at the end
+ curr = CGWrapper(CGList(cgthings, "\n\n"), post="\n\n")
+
+ # Wrap all of that in our namespaces.
+ curr = CGNamespace.build(["mozilla", "dom"], CGWrapper(curr, pre="\n"))
+
+ curr = CGList(
+ [
+ CGForwardDeclarations(
+ config,
+ descriptors,
+ callbacks,
+ dictionaries,
+ callbackDescriptors + jsImplemented,
+ additionalDeclarations=unionDeclarations,
+ ),
+ curr,
+ ],
+ "\n",
+ )
+
+ # Add header includes.
+ bindingHeaders = [
+ header for header, include in six.iteritems(bindingHeaders) if include
+ ]
+ bindingDeclareHeaders = [
+ header
+ for header, include in six.iteritems(bindingDeclareHeaders)
+ if include
+ ]
+
+ curr = CGHeaders(
+ descriptors,
+ dictionaries,
+ callbacks,
+ callbackDescriptors,
+ bindingDeclareHeaders,
+ bindingHeaders,
+ prefix,
+ curr,
+ config,
+ jsImplemented,
+ )
+
+ # Add include guards.
+ curr = CGIncludeGuard(prefix, curr)
+
+ # Add the auto-generated comment.
+ curr = CGWrapper(
+ curr,
+ pre=(
+ AUTOGENERATED_WITH_SOURCE_WARNING_COMMENT % os.path.basename(webIDLFile)
+ ),
+ )
+
+ # Store the final result.
+ self.root = curr
+
+ def declare(self):
+ return stripTrailingWhitespace(self.root.declare())
+
+ def define(self):
+ return stripTrailingWhitespace(self.root.define())
+
+ def deps(self):
+ return self.root.deps()
+
+
+class CGNativeMember(ClassMethod):
+ def __init__(
+ self,
+ descriptorProvider,
+ member,
+ name,
+ signature,
+ extendedAttrs,
+ breakAfter=True,
+ passJSBitsAsNeeded=True,
+ visibility="public",
+ spiderMonkeyInterfacesAreStructs=True,
+ variadicIsSequence=False,
+ resultNotAddRefed=False,
+ virtual=False,
+ override=False,
+ canRunScript=False,
+ ):
+ """
+ If spiderMonkeyInterfacesAreStructs is false, SpiderMonkey interfaces
+ will be passed as JS::Handle<JSObject*>. If it's true they will be
+ passed as one of the dom::SpiderMonkeyInterfaceObjectStorage subclasses.
+
+ If passJSBitsAsNeeded is false, we don't automatically pass in a
+ JSContext* or a JSObject* based on the return and argument types. We
+ can still pass it based on 'implicitJSContext' annotations.
+ """
+ self.descriptorProvider = descriptorProvider
+ self.member = member
+ self.extendedAttrs = extendedAttrs
+ self.resultAlreadyAddRefed = not resultNotAddRefed
+ self.passJSBitsAsNeeded = passJSBitsAsNeeded
+ self.spiderMonkeyInterfacesAreStructs = spiderMonkeyInterfacesAreStructs
+ self.variadicIsSequence = variadicIsSequence
+ breakAfterSelf = "\n" if breakAfter else ""
+ ClassMethod.__init__(
+ self,
+ name,
+ self.getReturnType(signature[0], False),
+ self.getArgs(signature[0], signature[1]),
+ static=member.isStatic(),
+ # Mark our getters, which are attrs that
+ # have a non-void return type, as const.
+ const=(
+ not member.isStatic()
+ and member.isAttr()
+ and not signature[0].isUndefined()
+ ),
+ breakAfterReturnDecl=" ",
+ breakAfterSelf=breakAfterSelf,
+ visibility=visibility,
+ virtual=virtual,
+ override=override,
+ canRunScript=canRunScript,
+ )
+
+ def getReturnType(self, type, isMember):
+ return self.getRetvalInfo(type, isMember)[0]
+
+ def getRetvalInfo(self, type, isMember):
+ """
+ Returns a tuple:
+
+ The first element is the type declaration for the retval
+
+ The second element is a default value that can be used on error returns.
+ For cases whose behavior depends on isMember, the second element will be
+ None if isMember is true.
+
+ The third element is a template for actually returning a value stored in
+ "${declName}" and "${holderName}". This means actually returning it if
+ we're not outparam, else assigning to the "retval" outparam. If
+ isMember is true, this can be None, since in that case the caller will
+ never examine this value.
+ """
+ if type.isUndefined():
+ return "void", "", ""
+ if type.isPrimitive() and type.tag() in builtinNames:
+ result = CGGeneric(builtinNames[type.tag()])
+ defaultReturnArg = "0"
+ if type.nullable():
+ result = CGTemplatedType("Nullable", result)
+ defaultReturnArg = ""
+ return (
+ result.define(),
+ "%s(%s)" % (result.define(), defaultReturnArg),
+ "return ${declName};\n",
+ )
+ if type.isJSString():
+ if isMember:
+ raise TypeError("JSString not supported as return type member")
+ # Outparam
+ return "void", "", "aRetVal.set(${declName});\n"
+ if type.isDOMString() or type.isUSVString():
+ if isMember:
+ # No need for a third element in the isMember case
+ return "nsString", None, None
+ # Outparam
+ return "void", "", "aRetVal = ${declName};\n"
+ if type.isByteString() or type.isUTF8String():
+ if isMember:
+ # No need for a third element in the isMember case
+ return "nsCString", None, None
+ # Outparam
+ return "void", "", "aRetVal = ${declName};\n"
+ if type.isEnum():
+ enumName = type.unroll().inner.identifier.name
+ if type.nullable():
+ enumName = CGTemplatedType("Nullable", CGGeneric(enumName)).define()
+ defaultValue = "%s()" % enumName
+ else:
+ defaultValue = "%s(0)" % enumName
+ return enumName, defaultValue, "return ${declName};\n"
+ if type.isGeckoInterface() or type.isPromise():
+ if type.isGeckoInterface():
+ iface = type.unroll().inner
+ result = CGGeneric(
+ self.descriptorProvider.getDescriptor(
+ iface.identifier.name
+ ).prettyNativeType
+ )
+ else:
+ result = CGGeneric("Promise")
+ if self.resultAlreadyAddRefed:
+ if isMember:
+ holder = "RefPtr"
+ else:
+ holder = "already_AddRefed"
+ if memberReturnsNewObject(self.member) or isMember:
+ warning = ""
+ else:
+ warning = "// Return a raw pointer here to avoid refcounting, but make sure it's safe (the object should be kept alive by the callee).\n"
+ result = CGWrapper(result, pre=("%s%s<" % (warning, holder)), post=">")
+ else:
+ result = CGWrapper(result, post="*")
+ # Since we always force an owning type for callback return values,
+ # our ${declName} is an OwningNonNull or RefPtr. So we can just
+ # .forget() to get our already_AddRefed.
+ return result.define(), "nullptr", "return ${declName}.forget();\n"
+ if type.isCallback():
+ return (
+ "already_AddRefed<%s>" % type.unroll().callback.identifier.name,
+ "nullptr",
+ "return ${declName}.forget();\n",
+ )
+ if type.isAny():
+ if isMember:
+ # No need for a third element in the isMember case
+ return "JS::Value", None, None
+ # Outparam
+ return "void", "", "aRetVal.set(${declName});\n"
+
+ if type.isObject():
+ if isMember:
+ # No need for a third element in the isMember case
+ return "JSObject*", None, None
+ return "void", "", "aRetVal.set(${declName});\n"
+ if type.isSpiderMonkeyInterface():
+ if isMember:
+ # No need for a third element in the isMember case
+ return "JSObject*", None, None
+ if type.nullable():
+ returnCode = (
+ "${declName}.IsNull() ? nullptr : ${declName}.Value().Obj()"
+ )
+ else:
+ returnCode = "${declName}.Obj()"
+ return "void", "", "aRetVal.set(%s);\n" % returnCode
+ if type.isSequence():
+ # If we want to handle sequence-of-sequences return values, we're
+ # going to need to fix example codegen to not produce nsTArray<void>
+ # for the relevant argument...
+ assert not isMember
+ # Outparam.
+ if type.nullable():
+ returnCode = dedent(
+ """
+ if (${declName}.IsNull()) {
+ aRetVal.SetNull();
+ } else {
+ aRetVal.SetValue() = std::move(${declName}.Value());
+ }
+ """
+ )
+ else:
+ returnCode = "aRetVal = std::move(${declName});\n"
+ return "void", "", returnCode
+ if type.isRecord():
+ # If we want to handle record-of-record return values, we're
+ # going to need to fix example codegen to not produce record<void>
+ # for the relevant argument...
+ assert not isMember
+ # In this case we convert directly into our outparam to start with
+ return "void", "", ""
+ if type.isDictionary():
+ if isMember:
+ # Only the first member of the tuple matters here, but return
+ # bogus values for the others in case someone decides to use
+ # them.
+ return CGDictionary.makeDictionaryName(type.inner), None, None
+ # In this case we convert directly into our outparam to start with
+ return "void", "", ""
+ if type.isUnion():
+ if isMember:
+ # Only the first member of the tuple matters here, but return
+ # bogus values for the others in case someone decides to use
+ # them.
+ return CGUnionStruct.unionTypeDecl(type, True), None, None
+ # In this case we convert directly into our outparam to start with
+ return "void", "", ""
+
+ raise TypeError("Don't know how to declare return value for %s" % type)
+
+ def getArgs(self, returnType, argList):
+ args = [self.getArg(arg) for arg in argList]
+ # Now the outparams
+ if returnType.isJSString():
+ args.append(Argument("JS::MutableHandle<JSString*>", "aRetVal"))
+ elif returnType.isDOMString() or returnType.isUSVString():
+ args.append(Argument("nsString&", "aRetVal"))
+ elif returnType.isByteString() or returnType.isUTF8String():
+ args.append(Argument("nsCString&", "aRetVal"))
+ elif returnType.isSequence():
+ nullable = returnType.nullable()
+ if nullable:
+ returnType = returnType.inner
+ # And now the actual underlying type
+ elementDecl = self.getReturnType(returnType.inner, True)
+ type = CGTemplatedType("nsTArray", CGGeneric(elementDecl))
+ if nullable:
+ type = CGTemplatedType("Nullable", type)
+ args.append(Argument("%s&" % type.define(), "aRetVal"))
+ elif returnType.isRecord():
+ nullable = returnType.nullable()
+ if nullable:
+ returnType = returnType.inner
+ # And now the actual underlying type
+ elementDecl = self.getReturnType(returnType.inner, True)
+ type = CGTemplatedType(
+ "Record", [recordKeyDeclType(returnType), CGGeneric(elementDecl)]
+ )
+ if nullable:
+ type = CGTemplatedType("Nullable", type)
+ args.append(Argument("%s&" % type.define(), "aRetVal"))
+ elif returnType.isDictionary():
+ nullable = returnType.nullable()
+ if nullable:
+ returnType = returnType.inner
+ dictType = CGGeneric(CGDictionary.makeDictionaryName(returnType.inner))
+ if nullable:
+ dictType = CGTemplatedType("Nullable", dictType)
+ args.append(Argument("%s&" % dictType.define(), "aRetVal"))
+ elif returnType.isUnion():
+ args.append(
+ Argument(
+ "%s&" % CGUnionStruct.unionTypeDecl(returnType, True), "aRetVal"
+ )
+ )
+ elif returnType.isAny():
+ args.append(Argument("JS::MutableHandle<JS::Value>", "aRetVal"))
+ elif returnType.isObject() or returnType.isSpiderMonkeyInterface():
+ args.append(Argument("JS::MutableHandle<JSObject*>", "aRetVal"))
+
+ # And the nsIPrincipal
+ if "needsSubjectPrincipal" in self.extendedAttrs:
+ # Cheat and assume self.descriptorProvider is a descriptor
+ if self.descriptorProvider.interface.isExposedInAnyWorker():
+ args.append(Argument("Maybe<nsIPrincipal*>", "aSubjectPrincipal"))
+ elif "needsNonSystemSubjectPrincipal" in self.extendedAttrs:
+ args.append(Argument("nsIPrincipal*", "aPrincipal"))
+ else:
+ args.append(Argument("nsIPrincipal&", "aPrincipal"))
+ # And the caller type, if desired.
+ if needsCallerType(self.member):
+ args.append(Argument("CallerType", "aCallerType"))
+ # And the ErrorResult or OOMReporter
+ if "needsErrorResult" in self.extendedAttrs:
+ # Use aRv so it won't conflict with local vars named "rv"
+ args.append(Argument("ErrorResult&", "aRv"))
+ elif "canOOM" in self.extendedAttrs:
+ args.append(Argument("OOMReporter&", "aRv"))
+
+ # The legacycaller thisval
+ if self.member.isMethod() and self.member.isLegacycaller():
+ # If it has an identifier, we can't deal with it yet
+ assert self.member.isIdentifierLess()
+ args.insert(0, Argument("const JS::Value&", "aThisVal"))
+ # And jscontext bits.
+ if needCx(
+ returnType,
+ argList,
+ self.extendedAttrs,
+ self.passJSBitsAsNeeded,
+ self.member.isStatic(),
+ ):
+ args.insert(0, Argument("JSContext*", "cx"))
+ if needScopeObject(
+ returnType,
+ argList,
+ self.extendedAttrs,
+ self.descriptorProvider.wrapperCache,
+ self.passJSBitsAsNeeded,
+ self.member.getExtendedAttribute("StoreInSlot"),
+ ):
+ args.insert(1, Argument("JS::Handle<JSObject*>", "obj"))
+ # And if we're static, a global
+ if self.member.isStatic():
+ args.insert(0, Argument("const GlobalObject&", "global"))
+ return args
+
+ def doGetArgType(self, type, optional, isMember):
+ """
+ The main work of getArgType. Returns a string type decl, whether this
+ is a const ref, as well as whether the type should be wrapped in
+ Nullable as needed.
+
+ isMember can be false or one of the strings "Sequence", "Variadic",
+ "Record"
+ """
+ if type.isSequence():
+ nullable = type.nullable()
+ if nullable:
+ type = type.inner
+ elementType = type.inner
+ argType = self.getArgType(elementType, False, "Sequence")[0]
+ decl = CGTemplatedType("Sequence", argType)
+ return decl.define(), True, True
+
+ if type.isRecord():
+ nullable = type.nullable()
+ if nullable:
+ type = type.inner
+ elementType = type.inner
+ argType = self.getArgType(elementType, False, "Record")[0]
+ decl = CGTemplatedType("Record", [recordKeyDeclType(type), argType])
+ return decl.define(), True, True
+
+ if type.isUnion():
+ # unionTypeDecl will handle nullable types, so return False for
+ # auto-wrapping in Nullable
+ return CGUnionStruct.unionTypeDecl(type, isMember), True, False
+
+ if type.isPromise():
+ assert not type.nullable()
+ if optional or isMember:
+ typeDecl = "OwningNonNull<Promise>"
+ else:
+ typeDecl = "Promise&"
+ return (typeDecl, False, False)
+
+ if type.isGeckoInterface() and not type.isCallbackInterface():
+ iface = type.unroll().inner
+ if iface.identifier.name == "WindowProxy":
+ return "WindowProxyHolder", True, False
+
+ argIsPointer = type.nullable() or iface.isExternal()
+ forceOwningType = iface.isCallback() or isMember
+ if argIsPointer:
+ if (optional or isMember) and forceOwningType:
+ typeDecl = "RefPtr<%s>"
+ else:
+ typeDecl = "%s*"
+ else:
+ if optional or isMember:
+ if forceOwningType:
+ typeDecl = "OwningNonNull<%s>"
+ else:
+ typeDecl = "NonNull<%s>"
+ else:
+ typeDecl = "%s&"
+ return (
+ (
+ typeDecl
+ % self.descriptorProvider.getDescriptor(
+ iface.identifier.name
+ ).prettyNativeType
+ ),
+ False,
+ False,
+ )
+
+ if type.isSpiderMonkeyInterface():
+ if not self.spiderMonkeyInterfacesAreStructs:
+ return "JS::Handle<JSObject*>", False, False
+
+ # Unroll for the name, in case we're nullable.
+ return type.unroll().name, True, True
+
+ if type.isJSString():
+ if isMember:
+ raise TypeError("JSString not supported as member")
+ return "JS::Handle<JSString*>", False, False
+
+ if type.isDOMString() or type.isUSVString():
+ if isMember:
+ declType = "nsString"
+ else:
+ declType = "nsAString"
+ return declType, True, False
+
+ if type.isByteString() or type.isUTF8String():
+ # TODO(emilio): Maybe bytestrings could benefit from nsAutoCString
+ # or such too.
+ if type.isUTF8String() and not isMember:
+ declType = "nsACString"
+ else:
+ declType = "nsCString"
+ return declType, True, False
+
+ if type.isEnum():
+ return type.unroll().inner.identifier.name, False, True
+
+ if type.isCallback() or type.isCallbackInterface():
+ forceOwningType = optional or isMember
+ if type.nullable():
+ if forceOwningType:
+ declType = "RefPtr<%s>"
+ else:
+ declType = "%s*"
+ else:
+ if forceOwningType:
+ declType = "OwningNonNull<%s>"
+ else:
+ declType = "%s&"
+ if type.isCallback():
+ name = type.unroll().callback.identifier.name
+ else:
+ name = type.unroll().inner.identifier.name
+ return declType % name, False, False
+
+ if type.isAny():
+ # Don't do the rooting stuff for variadics for now
+ if isMember:
+ declType = "JS::Value"
+ else:
+ declType = "JS::Handle<JS::Value>"
+ return declType, False, False
+
+ if type.isObject():
+ if isMember:
+ declType = "JSObject*"
+ else:
+ declType = "JS::Handle<JSObject*>"
+ return declType, False, False
+
+ if type.isDictionary():
+ typeName = CGDictionary.makeDictionaryName(type.inner)
+ return typeName, True, True
+
+ assert type.isPrimitive()
+
+ return builtinNames[type.tag()], False, True
+
+ def getArgType(self, type, optional, isMember):
+ """
+ Get the type of an argument declaration. Returns the type CGThing, and
+ whether this should be a const ref.
+
+ isMember can be False, "Sequence", or "Variadic"
+ """
+ decl, ref, handleNullable = self.doGetArgType(type, optional, isMember)
+ decl = CGGeneric(decl)
+ if handleNullable and type.nullable():
+ decl = CGTemplatedType("Nullable", decl)
+ ref = True
+ if isMember == "Variadic":
+ arrayType = "Sequence" if self.variadicIsSequence else "nsTArray"
+ decl = CGTemplatedType(arrayType, decl)
+ ref = True
+ elif optional:
+ # Note: All variadic args claim to be optional, but we can just use
+ # empty arrays to represent them not being present.
+ decl = CGTemplatedType("Optional", decl)
+ ref = True
+ return (decl, ref)
+
+ def getArg(self, arg):
+ """
+ Get the full argument declaration for an argument
+ """
+ decl, ref = self.getArgType(
+ arg.type, arg.canHaveMissingValue(), "Variadic" if arg.variadic else False
+ )
+ if ref:
+ decl = CGWrapper(decl, pre="const ", post="&")
+
+ return Argument(decl.define(), arg.identifier.name)
+
+ def arguments(self):
+ return self.member.signatures()[0][1]
+
+
+class CGExampleMethod(CGNativeMember):
+ def __init__(self, descriptor, method, signature, isConstructor, breakAfter=True):
+ CGNativeMember.__init__(
+ self,
+ descriptor,
+ method,
+ CGSpecializedMethod.makeNativeName(descriptor, method),
+ signature,
+ descriptor.getExtendedAttributes(method),
+ breakAfter=breakAfter,
+ variadicIsSequence=True,
+ )
+
+ def declare(self, cgClass):
+ assert self.member.isMethod()
+ # We skip declaring ourselves if this is a maplike/setlike/iterable
+ # method, because those get implemented automatically by the binding
+ # machinery, so the implementor of the interface doesn't have to worry
+ # about it.
+ if self.member.isMaplikeOrSetlikeOrIterableMethod():
+ return ""
+ return CGNativeMember.declare(self, cgClass)
+
+ def define(self, cgClass):
+ return ""
+
+
+class CGExampleGetter(CGNativeMember):
+ def __init__(self, descriptor, attr):
+ CGNativeMember.__init__(
+ self,
+ descriptor,
+ attr,
+ CGSpecializedGetter.makeNativeName(descriptor, attr),
+ (attr.type, []),
+ descriptor.getExtendedAttributes(attr, getter=True),
+ )
+
+ def declare(self, cgClass):
+ assert self.member.isAttr()
+ # We skip declaring ourselves if this is a maplike/setlike attr (in
+ # practice, "size"), because those get implemented automatically by the
+ # binding machinery, so the implementor of the interface doesn't have to
+ # worry about it.
+ if self.member.isMaplikeOrSetlikeAttr():
+ return ""
+ return CGNativeMember.declare(self, cgClass)
+
+ def define(self, cgClass):
+ return ""
+
+
+class CGExampleSetter(CGNativeMember):
+ def __init__(self, descriptor, attr):
+ CGNativeMember.__init__(
+ self,
+ descriptor,
+ attr,
+ CGSpecializedSetter.makeNativeName(descriptor, attr),
+ (
+ BuiltinTypes[IDLBuiltinType.Types.undefined],
+ [FakeArgument(attr.type)],
+ ),
+ descriptor.getExtendedAttributes(attr, setter=True),
+ )
+
+ def define(self, cgClass):
+ return ""
+
+
+class CGBindingImplClass(CGClass):
+ """
+ Common codegen for generating a C++ implementation of a WebIDL interface
+ """
+
+ def __init__(
+ self,
+ descriptor,
+ cgMethod,
+ cgGetter,
+ cgSetter,
+ wantGetParent=True,
+ wrapMethodName="WrapObject",
+ skipStaticMethods=False,
+ ):
+ """
+ cgMethod, cgGetter and cgSetter are classes used to codegen methods,
+ getters and setters.
+ """
+ self.descriptor = descriptor
+ self._deps = descriptor.interface.getDeps()
+
+ iface = descriptor.interface
+
+ self.methodDecls = []
+
+ def appendMethod(m, isConstructor=False):
+ sigs = m.signatures()
+ for s in sigs[:-1]:
+ # Don't put a blank line after overloads, until we
+ # get to the last one.
+ self.methodDecls.append(
+ cgMethod(descriptor, m, s, isConstructor, breakAfter=False)
+ )
+ self.methodDecls.append(cgMethod(descriptor, m, sigs[-1], isConstructor))
+
+ if iface.ctor():
+ appendMethod(iface.ctor(), isConstructor=True)
+ for n in iface.legacyFactoryFunctions:
+ appendMethod(n, isConstructor=True)
+ for m in iface.members:
+ if m.isMethod():
+ if m.isIdentifierLess():
+ continue
+ if m.isMaplikeOrSetlikeOrIterableMethod():
+ # Handled by generated code already
+ continue
+ if not m.isStatic() or not skipStaticMethods:
+ appendMethod(m)
+ elif m.isAttr():
+ if m.isMaplikeOrSetlikeAttr() or m.type.isObservableArray():
+ # Handled by generated code already
+ continue
+ self.methodDecls.append(cgGetter(descriptor, m))
+ if not m.readonly:
+ self.methodDecls.append(cgSetter(descriptor, m))
+
+ # Now do the special operations
+ def appendSpecialOperation(name, op):
+ if op is None:
+ return
+ assert len(op.signatures()) == 1
+ returnType, args = op.signatures()[0]
+ # Make a copy of the args, since we plan to modify them.
+ args = list(args)
+ if op.isGetter() or op.isDeleter():
+ # This is a total hack. The '&' belongs with the
+ # type, not the name! But it works, and is simpler
+ # than trying to somehow make this pretty.
+ args.append(
+ FakeArgument(
+ BuiltinTypes[IDLBuiltinType.Types.boolean], name="&found"
+ )
+ )
+ if name == "Stringifier":
+ if op.isIdentifierLess():
+ # XXXbz I wish we were consistent about our renaming here.
+ name = "Stringify"
+ else:
+ # We already added this method
+ return
+ if name == "LegacyCaller":
+ if op.isIdentifierLess():
+ # XXXbz I wish we were consistent about our renaming here.
+ name = "LegacyCall"
+ else:
+ # We already added this method
+ return
+ self.methodDecls.append(
+ CGNativeMember(
+ descriptor,
+ op,
+ name,
+ (returnType, args),
+ descriptor.getExtendedAttributes(op),
+ )
+ )
+
+ # Sort things by name so we get stable ordering in the output.
+ ops = sorted(descriptor.operations.items(), key=lambda x: x[0])
+ for name, op in ops:
+ appendSpecialOperation(name, op)
+ # If we support indexed properties, then we need a Length()
+ # method so we know which indices are supported.
+ if descriptor.supportsIndexedProperties():
+ # But we don't need it if we already have an infallible
+ # "length" attribute, which we often do.
+ haveLengthAttr = any(
+ m
+ for m in iface.members
+ if m.isAttr()
+ and CGSpecializedGetter.makeNativeName(descriptor, m) == "Length"
+ )
+ if not haveLengthAttr:
+ self.methodDecls.append(
+ CGNativeMember(
+ descriptor,
+ FakeMember(),
+ "Length",
+ (BuiltinTypes[IDLBuiltinType.Types.unsigned_long], []),
+ [],
+ ),
+ )
+ # And if we support named properties we need to be able to
+ # enumerate the supported names.
+ if descriptor.supportsNamedProperties():
+ self.methodDecls.append(
+ CGNativeMember(
+ descriptor,
+ FakeMember(),
+ "GetSupportedNames",
+ (
+ IDLSequenceType(
+ None, BuiltinTypes[IDLBuiltinType.Types.domstring]
+ ),
+ [],
+ ),
+ [],
+ )
+ )
+
+ if descriptor.concrete:
+ wrapArgs = [
+ Argument("JSContext*", "aCx"),
+ Argument("JS::Handle<JSObject*>", "aGivenProto"),
+ ]
+ if not descriptor.wrapperCache:
+ wrapReturnType = "bool"
+ wrapArgs.append(Argument("JS::MutableHandle<JSObject*>", "aReflector"))
+ else:
+ wrapReturnType = "JSObject*"
+ self.methodDecls.insert(
+ 0,
+ ClassMethod(
+ wrapMethodName,
+ wrapReturnType,
+ wrapArgs,
+ virtual=descriptor.wrapperCache,
+ breakAfterReturnDecl=" ",
+ override=descriptor.wrapperCache,
+ body=self.getWrapObjectBody(),
+ ),
+ )
+ if descriptor.hasCEReactions():
+ self.methodDecls.insert(
+ 0,
+ ClassMethod(
+ "GetDocGroup",
+ "DocGroup*",
+ [],
+ const=True,
+ breakAfterReturnDecl=" ",
+ body=self.getGetDocGroupBody(),
+ ),
+ )
+ if wantGetParent:
+ self.methodDecls.insert(
+ 0,
+ ClassMethod(
+ "GetParentObject",
+ self.getGetParentObjectReturnType(),
+ [],
+ const=True,
+ breakAfterReturnDecl=" ",
+ body=self.getGetParentObjectBody(),
+ ),
+ )
+
+ # Invoke CGClass.__init__ in any subclasses afterwards to do the actual codegen.
+
+ def getWrapObjectBody(self):
+ return None
+
+ def getGetParentObjectReturnType(self):
+ # The lack of newline before the end of the string is on purpose.
+ return dedent(
+ """
+ // This should return something that eventually allows finding a
+ // path to the global this object is associated with. Most simply,
+ // returning an actual global works.
+ nsIGlobalObject*"""
+ )
+
+ def getGetParentObjectBody(self):
+ return None
+
+ def getGetDocGroupBody(self):
+ return None
+
+ def deps(self):
+ return self._deps
+
+
+class CGExampleObservableArrayCallback(CGNativeMember):
+ def __init__(self, descriptor, attr, callbackName):
+ assert attr.isAttr()
+ assert attr.type.isObservableArray()
+ CGNativeMember.__init__(
+ self,
+ descriptor,
+ attr,
+ self.makeNativeName(attr, callbackName),
+ (
+ BuiltinTypes[IDLBuiltinType.Types.undefined],
+ [
+ FakeArgument(attr.type.inner, "aValue"),
+ FakeArgument(
+ BuiltinTypes[IDLBuiltinType.Types.unsigned_long], "aIndex"
+ ),
+ ],
+ ),
+ ["needsErrorResult"],
+ )
+
+ def declare(self, cgClass):
+ assert self.member.isAttr()
+ assert self.member.type.isObservableArray()
+ return CGNativeMember.declare(self, cgClass)
+
+ def define(self, cgClass):
+ return ""
+
+ @staticmethod
+ def makeNativeName(attr, callbackName):
+ assert attr.isAttr()
+ nativeName = MakeNativeName(attr.identifier.name)
+ return "On" + callbackName + nativeName
+
+
+class CGExampleClass(CGBindingImplClass):
+ """
+ Codegen for the actual example class implementation for this descriptor
+ """
+
+ def __init__(self, descriptor):
+ CGBindingImplClass.__init__(
+ self,
+ descriptor,
+ CGExampleMethod,
+ CGExampleGetter,
+ CGExampleSetter,
+ wantGetParent=descriptor.wrapperCache,
+ )
+
+ self.parentIface = descriptor.interface.parent
+ if self.parentIface:
+ self.parentDesc = descriptor.getDescriptor(self.parentIface.identifier.name)
+ bases = [ClassBase(self.nativeLeafName(self.parentDesc))]
+ else:
+ bases = [
+ ClassBase(
+ "nsISupports /* or NonRefcountedDOMObject if this is a non-refcounted object */"
+ )
+ ]
+ if descriptor.wrapperCache:
+ bases.append(
+ ClassBase(
+ "nsWrapperCache /* Change wrapperCache in the binding configuration if you don't want this */"
+ )
+ )
+
+ destructorVisibility = "protected"
+ if self.parentIface:
+ extradeclarations = (
+ "public:\n"
+ " NS_DECL_ISUPPORTS_INHERITED\n"
+ " NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(%s, %s)\n"
+ "\n"
+ % (
+ self.nativeLeafName(descriptor),
+ self.nativeLeafName(self.parentDesc),
+ )
+ )
+ else:
+ extradeclarations = (
+ "public:\n"
+ " NS_DECL_CYCLE_COLLECTING_ISUPPORTS\n"
+ " NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(%s)\n"
+ "\n" % self.nativeLeafName(descriptor)
+ )
+
+ if descriptor.interface.hasChildInterfaces():
+ decorators = ""
+ else:
+ decorators = "final"
+
+ for m in descriptor.interface.members:
+ if m.isAttr() and m.type.isObservableArray():
+ self.methodDecls.append(
+ CGExampleObservableArrayCallback(descriptor, m, "Set")
+ )
+ self.methodDecls.append(
+ CGExampleObservableArrayCallback(descriptor, m, "Delete")
+ )
+
+ CGClass.__init__(
+ self,
+ self.nativeLeafName(descriptor),
+ bases=bases,
+ constructors=[ClassConstructor([], visibility="public")],
+ destructor=ClassDestructor(visibility=destructorVisibility),
+ methods=self.methodDecls,
+ decorators=decorators,
+ extradeclarations=extradeclarations,
+ )
+
+ def define(self):
+ # Just override CGClass and do our own thing
+ nativeType = self.nativeLeafName(self.descriptor)
+
+ ctordtor = fill(
+ """
+ ${nativeType}::${nativeType}()
+ {
+ // Add |MOZ_COUNT_CTOR(${nativeType});| for a non-refcounted object.
+ }
+
+ ${nativeType}::~${nativeType}()
+ {
+ // Add |MOZ_COUNT_DTOR(${nativeType});| for a non-refcounted object.
+ }
+ """,
+ nativeType=nativeType,
+ )
+
+ if self.parentIface:
+ ccImpl = fill(
+ """
+
+ // Only needed for refcounted objects.
+ # error "If you don't have members that need cycle collection,
+ # then remove all the cycle collection bits from this
+ # implementation and the corresponding header. If you do, you
+ # want NS_IMPL_CYCLE_COLLECTION_INHERITED(${nativeType},
+ # ${parentType}, your, members, here)"
+ NS_IMPL_ADDREF_INHERITED(${nativeType}, ${parentType})
+ NS_IMPL_RELEASE_INHERITED(${nativeType}, ${parentType})
+ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(${nativeType})
+ NS_INTERFACE_MAP_END_INHERITING(${parentType})
+
+ """,
+ nativeType=nativeType,
+ parentType=self.nativeLeafName(self.parentDesc),
+ )
+ else:
+ ccImpl = fill(
+ """
+
+ // Only needed for refcounted objects.
+ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(${nativeType})
+ NS_IMPL_CYCLE_COLLECTING_ADDREF(${nativeType})
+ NS_IMPL_CYCLE_COLLECTING_RELEASE(${nativeType})
+ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(${nativeType})
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_END
+
+ """,
+ nativeType=nativeType,
+ )
+
+ classImpl = ccImpl + ctordtor + "\n"
+ if self.descriptor.concrete:
+ if self.descriptor.wrapperCache:
+ reflectorArg = ""
+ reflectorPassArg = ""
+ returnType = "JSObject*"
+ else:
+ reflectorArg = ", JS::MutableHandle<JSObject*> aReflector"
+ reflectorPassArg = ", aReflector"
+ returnType = "bool"
+ classImpl += fill(
+ """
+ ${returnType}
+ ${nativeType}::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto${reflectorArg})
+ {
+ return ${ifaceName}_Binding::Wrap(aCx, this, aGivenProto${reflectorPassArg});
+ }
+
+ """,
+ returnType=returnType,
+ nativeType=nativeType,
+ reflectorArg=reflectorArg,
+ ifaceName=self.descriptor.name,
+ reflectorPassArg=reflectorPassArg,
+ )
+
+ return classImpl
+
+ @staticmethod
+ def nativeLeafName(descriptor):
+ return descriptor.nativeType.split("::")[-1]
+
+
+class CGExampleRoot(CGThing):
+ """
+ Root codegen class for example implementation generation. Instantiate the
+ class and call declare or define to generate header or cpp code,
+ respectively.
+ """
+
+ def __init__(self, config, interfaceName):
+ descriptor = config.getDescriptor(interfaceName)
+
+ self.root = CGWrapper(CGExampleClass(descriptor), pre="\n", post="\n")
+
+ self.root = CGNamespace.build(["mozilla", "dom"], self.root)
+
+ builder = ForwardDeclarationBuilder()
+ if descriptor.hasCEReactions():
+ builder.addInMozillaDom("DocGroup")
+ for member in descriptor.interface.members:
+ if not member.isAttr() and not member.isMethod():
+ continue
+ if member.isStatic():
+ builder.addInMozillaDom("GlobalObject")
+ if member.isAttr():
+ if not member.isMaplikeOrSetlikeAttr():
+ builder.forwardDeclareForType(member.type, config)
+ else:
+ assert member.isMethod()
+ if not member.isMaplikeOrSetlikeOrIterableMethod():
+ for sig in member.signatures():
+ builder.forwardDeclareForType(sig[0], config)
+ for arg in sig[1]:
+ builder.forwardDeclareForType(arg.type, config)
+
+ self.root = CGList([builder.build(), self.root], "\n")
+
+ # Throw in our #includes
+ self.root = CGHeaders(
+ [],
+ [],
+ [],
+ [],
+ [
+ "nsWrapperCache.h",
+ "nsCycleCollectionParticipant.h",
+ "mozilla/Attributes.h",
+ "mozilla/ErrorResult.h",
+ "mozilla/dom/BindingDeclarations.h",
+ "js/TypeDecls.h",
+ ],
+ [
+ "mozilla/dom/%s.h" % interfaceName,
+ (
+ "mozilla/dom/%s"
+ % CGHeaders.getDeclarationFilename(descriptor.interface)
+ ),
+ ],
+ "",
+ self.root,
+ )
+
+ # And now some include guards
+ self.root = CGIncludeGuard(interfaceName, self.root)
+
+ # And our license block comes before everything else
+ self.root = CGWrapper(
+ self.root,
+ pre=dedent(
+ """
+ /* -*- 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/. */
+
+ """
+ ),
+ )
+
+ def declare(self):
+ return self.root.declare()
+
+ def define(self):
+ return self.root.define()
+
+
+def jsImplName(name):
+ return name + "JSImpl"
+
+
+class CGJSImplMember(CGNativeMember):
+ """
+ Base class for generating code for the members of the implementation class
+ for a JS-implemented WebIDL interface.
+ """
+
+ def __init__(
+ self,
+ descriptorProvider,
+ member,
+ name,
+ signature,
+ extendedAttrs,
+ breakAfter=True,
+ passJSBitsAsNeeded=True,
+ visibility="public",
+ variadicIsSequence=False,
+ virtual=False,
+ override=False,
+ ):
+ CGNativeMember.__init__(
+ self,
+ descriptorProvider,
+ member,
+ name,
+ signature,
+ extendedAttrs,
+ breakAfter=breakAfter,
+ passJSBitsAsNeeded=passJSBitsAsNeeded,
+ visibility=visibility,
+ variadicIsSequence=variadicIsSequence,
+ virtual=virtual,
+ override=override,
+ )
+ self.body = self.getImpl()
+
+ def getArgs(self, returnType, argList):
+ args = CGNativeMember.getArgs(self, returnType, argList)
+ args.append(Argument("JS::Realm*", "aRealm", "nullptr"))
+ return args
+
+
+class CGJSImplMethod(CGJSImplMember):
+ """
+ Class for generating code for the methods for a JS-implemented WebIDL
+ interface.
+ """
+
+ def __init__(self, descriptor, method, signature, isConstructor, breakAfter=True):
+ self.signature = signature
+ self.descriptor = descriptor
+ self.isConstructor = isConstructor
+ CGJSImplMember.__init__(
+ self,
+ descriptor,
+ method,
+ CGSpecializedMethod.makeNativeName(descriptor, method),
+ signature,
+ descriptor.getExtendedAttributes(method),
+ breakAfter=breakAfter,
+ variadicIsSequence=True,
+ passJSBitsAsNeeded=False,
+ )
+
+ def getArgs(self, returnType, argList):
+ if self.isConstructor:
+ # Skip the JS::Compartment bits for constructors; it's handled
+ # manually in getImpl. But we do need our aGivenProto argument. We
+ # allow it to be omitted if the default proto is desired.
+ return CGNativeMember.getArgs(self, returnType, argList) + [
+ Argument("JS::Handle<JSObject*>", "aGivenProto", "nullptr")
+ ]
+ return CGJSImplMember.getArgs(self, returnType, argList)
+
+ def getImpl(self):
+ args = self.getArgs(self.signature[0], self.signature[1])
+ if not self.isConstructor:
+ return "return mImpl->%s(%s);\n" % (
+ self.name,
+ ", ".join(arg.name for arg in args),
+ )
+
+ assert self.descriptor.interface.isJSImplemented()
+ if self.name != "Constructor":
+ raise TypeError(
+ "Named constructors are not supported for JS implemented WebIDL. See bug 851287."
+ )
+ if len(self.signature[1]) != 0:
+ # The first two arguments to the constructor implementation are not
+ # arguments to the WebIDL constructor, so don't pass them to
+ # __Init(). The last argument is the prototype we're supposed to
+ # use, and shouldn't get passed to __Init() either.
+ assert args[0].argType == "const GlobalObject&"
+ assert args[1].argType == "JSContext*"
+ assert args[-1].argType == "JS::Handle<JSObject*>"
+ assert args[-1].name == "aGivenProto"
+ constructorArgs = [arg.name for arg in args[2:-1]]
+ constructorArgs.append("js::GetNonCCWObjectRealm(scopeObj)")
+ initCall = fill(
+ """
+ // Wrap the object before calling __Init so that __DOM_IMPL__ is available.
+ JS::Rooted<JSObject*> scopeObj(cx, global.Get());
+ MOZ_ASSERT(js::IsObjectInContextCompartment(scopeObj, cx));
+ JS::Rooted<JS::Value> wrappedVal(cx);
+ if (!GetOrCreateDOMReflector(cx, impl, &wrappedVal, aGivenProto)) {
+ MOZ_ASSERT(JS_IsExceptionPending(cx));
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ // Initialize the object with the constructor arguments.
+ impl->mImpl->__Init(${args});
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ """,
+ args=", ".join(constructorArgs),
+ )
+ else:
+ initCall = ""
+ return fill(
+ """
+ RefPtr<${implClass}> impl =
+ ConstructJSImplementation<${implClass}>("${contractId}", global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ $*{initCall}
+ return impl.forget();
+ """,
+ contractId=self.descriptor.interface.getJSImplementation(),
+ implClass=self.descriptor.name,
+ initCall=initCall,
+ )
+
+
+# We're always fallible
+def callbackGetterName(attr, descriptor):
+ return "Get" + MakeNativeName(
+ descriptor.binaryNameFor(attr.identifier.name, attr.isStatic())
+ )
+
+
+def callbackSetterName(attr, descriptor):
+ return "Set" + MakeNativeName(
+ descriptor.binaryNameFor(attr.identifier.name, attr.isStatic())
+ )
+
+
+class CGJSImplGetter(CGJSImplMember):
+ """
+ Class for generating code for the getters of attributes for a JS-implemented
+ WebIDL interface.
+ """
+
+ def __init__(self, descriptor, attr):
+ CGJSImplMember.__init__(
+ self,
+ descriptor,
+ attr,
+ CGSpecializedGetter.makeNativeName(descriptor, attr),
+ (attr.type, []),
+ descriptor.getExtendedAttributes(attr, getter=True),
+ passJSBitsAsNeeded=False,
+ )
+
+ def getImpl(self):
+ callbackArgs = [arg.name for arg in self.getArgs(self.member.type, [])]
+ return "return mImpl->%s(%s);\n" % (
+ callbackGetterName(self.member, self.descriptorProvider),
+ ", ".join(callbackArgs),
+ )
+
+
+class CGJSImplSetter(CGJSImplMember):
+ """
+ Class for generating code for the setters of attributes for a JS-implemented
+ WebIDL interface.
+ """
+
+ def __init__(self, descriptor, attr):
+ CGJSImplMember.__init__(
+ self,
+ descriptor,
+ attr,
+ CGSpecializedSetter.makeNativeName(descriptor, attr),
+ (
+ BuiltinTypes[IDLBuiltinType.Types.undefined],
+ [FakeArgument(attr.type)],
+ ),
+ descriptor.getExtendedAttributes(attr, setter=True),
+ passJSBitsAsNeeded=False,
+ )
+
+ def getImpl(self):
+ callbackArgs = [
+ arg.name
+ for arg in self.getArgs(
+ BuiltinTypes[IDLBuiltinType.Types.undefined],
+ [FakeArgument(self.member.type)],
+ )
+ ]
+ return "mImpl->%s(%s);\n" % (
+ callbackSetterName(self.member, self.descriptorProvider),
+ ", ".join(callbackArgs),
+ )
+
+
+class CGJSImplClass(CGBindingImplClass):
+ def __init__(self, descriptor):
+ CGBindingImplClass.__init__(
+ self,
+ descriptor,
+ CGJSImplMethod,
+ CGJSImplGetter,
+ CGJSImplSetter,
+ skipStaticMethods=True,
+ )
+
+ if descriptor.interface.parent:
+ parentClass = descriptor.getDescriptor(
+ descriptor.interface.parent.identifier.name
+ ).jsImplParent
+ baseClasses = [ClassBase(parentClass)]
+ isupportsDecl = "NS_DECL_ISUPPORTS_INHERITED\n"
+ ccDecl = "NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(%s, %s)\n" % (
+ descriptor.name,
+ parentClass,
+ )
+ extradefinitions = fill(
+ """
+ NS_IMPL_CYCLE_COLLECTION_INHERITED(${ifaceName}, ${parentClass}, mImpl, mParent)
+ NS_IMPL_ADDREF_INHERITED(${ifaceName}, ${parentClass})
+ NS_IMPL_RELEASE_INHERITED(${ifaceName}, ${parentClass})
+ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(${ifaceName})
+ NS_INTERFACE_MAP_END_INHERITING(${parentClass})
+ """,
+ ifaceName=self.descriptor.name,
+ parentClass=parentClass,
+ )
+ else:
+ baseClasses = [
+ ClassBase("nsSupportsWeakReference"),
+ ClassBase("nsWrapperCache"),
+ ]
+ isupportsDecl = "NS_DECL_CYCLE_COLLECTING_ISUPPORTS\n"
+ ccDecl = (
+ "NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(%s)\n" % descriptor.name
+ )
+ extradefinitions = fill(
+ """
+ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(${ifaceName})
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(${ifaceName})
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mImpl)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ tmp->ClearWeakReferences();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(${ifaceName})
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImpl)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+ NS_IMPL_CYCLE_COLLECTING_ADDREF(${ifaceName})
+ NS_IMPL_CYCLE_COLLECTING_RELEASE(${ifaceName})
+ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(${ifaceName})
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_END
+ """,
+ ifaceName=self.descriptor.name,
+ )
+
+ extradeclarations = fill(
+ """
+ public:
+ $*{isupportsDecl}
+ $*{ccDecl}
+
+ private:
+ RefPtr<${jsImplName}> mImpl;
+ nsCOMPtr<nsIGlobalObject> mParent;
+
+ """,
+ isupportsDecl=isupportsDecl,
+ ccDecl=ccDecl,
+ jsImplName=jsImplName(descriptor.name),
+ )
+
+ if descriptor.interface.getExtendedAttribute("WantsEventListenerHooks"):
+ # No need to do too much sanity checking here; the
+ # generated code will fail to compile if the methods we
+ # try to overrid aren't on a superclass.
+ self.methodDecls.extend(
+ self.getEventHookMethod(parentClass, "EventListenerAdded")
+ )
+ self.methodDecls.extend(
+ self.getEventHookMethod(parentClass, "EventListenerRemoved")
+ )
+
+ if descriptor.interface.hasChildInterfaces():
+ decorators = ""
+ # We need a protected virtual destructor our subclasses can use
+ destructor = ClassDestructor(virtual=True, visibility="protected")
+ else:
+ decorators = "final"
+ destructor = ClassDestructor(virtual=False, visibility="private")
+
+ baseConstructors = [
+ (
+ "mImpl(new %s(nullptr, aJSImplObject, aJSImplGlobal, /* aIncumbentGlobal = */ nullptr))"
+ % jsImplName(descriptor.name)
+ ),
+ "mParent(aParent)",
+ ]
+ parentInterface = descriptor.interface.parent
+ while parentInterface:
+ if parentInterface.isJSImplemented():
+ baseConstructors.insert(
+ 0, "%s(aJSImplObject, aJSImplGlobal, aParent)" % parentClass
+ )
+ break
+ parentInterface = parentInterface.parent
+ if not parentInterface and descriptor.interface.parent:
+ # We only have C++ ancestors, so only pass along the window
+ baseConstructors.insert(0, "%s(aParent)" % parentClass)
+
+ constructor = ClassConstructor(
+ [
+ Argument("JS::Handle<JSObject*>", "aJSImplObject"),
+ Argument("JS::Handle<JSObject*>", "aJSImplGlobal"),
+ Argument("nsIGlobalObject*", "aParent"),
+ ],
+ visibility="public",
+ baseConstructors=baseConstructors,
+ )
+
+ self.methodDecls.append(
+ ClassMethod(
+ "_Create",
+ "bool",
+ JSNativeArguments(),
+ static=True,
+ body=self.getCreateFromExistingBody(),
+ )
+ )
+
+ CGClass.__init__(
+ self,
+ descriptor.name,
+ bases=baseClasses,
+ constructors=[constructor],
+ destructor=destructor,
+ methods=self.methodDecls,
+ decorators=decorators,
+ extradeclarations=extradeclarations,
+ extradefinitions=extradefinitions,
+ )
+
+ def getWrapObjectBody(self):
+ return fill(
+ """
+ JS::Rooted<JSObject*> obj(aCx, ${name}_Binding::Wrap(aCx, this, aGivenProto));
+ if (!obj) {
+ return nullptr;
+ }
+
+ // Now define it on our chrome object
+ JSAutoRealm ar(aCx, mImpl->CallbackGlobalOrNull());
+ if (!JS_WrapObject(aCx, &obj)) {
+ return nullptr;
+ }
+ JS::Rooted<JSObject*> callback(aCx, mImpl->CallbackOrNull());
+ if (!JS_DefineProperty(aCx, callback, "__DOM_IMPL__", obj, 0)) {
+ return nullptr;
+ }
+ return obj;
+ """,
+ name=self.descriptor.name,
+ )
+
+ def getGetParentObjectReturnType(self):
+ return "nsISupports*"
+
+ def getGetParentObjectBody(self):
+ return "return mParent;\n"
+
+ def getGetDocGroupBody(self):
+ return dedent(
+ """
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mParent);
+ if (!window) {
+ return nullptr;
+ }
+ return window->GetDocGroup();
+ """
+ )
+
+ def getCreateFromExistingBody(self):
+ # XXXbz we could try to get parts of this (e.g. the argument
+ # conversions) auto-generated by somehow creating an IDLMethod and
+ # adding it to our interface, but we'd still need to special-case the
+ # implementation slightly to have it not try to forward to the JS
+ # object...
+ return fill(
+ """
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "${ifaceName}._create", 2)) {
+ return false;
+ }
+ BindingCallContext callCx(cx, "${ifaceName}._create");
+ if (!args[0].isObject()) {
+ return callCx.ThrowErrorMessage<MSG_NOT_OBJECT>("Argument 1");
+ }
+ if (!args[1].isObject()) {
+ return callCx.ThrowErrorMessage<MSG_NOT_OBJECT>("Argument 2");
+ }
+
+ // GlobalObject will go through wrappers as needed for us, and
+ // is simpler than the right UnwrapArg incantation.
+ GlobalObject global(cx, &args[0].toObject());
+ if (global.Failed()) {
+ return false;
+ }
+ nsCOMPtr<nsIGlobalObject> globalHolder = do_QueryInterface(global.GetAsSupports());
+ MOZ_ASSERT(globalHolder);
+ JS::Rooted<JSObject*> arg(cx, &args[1].toObject());
+ JS::Rooted<JSObject*> argGlobal(cx, JS::CurrentGlobalOrNull(cx));
+ RefPtr<${implName}> impl = new ${implName}(arg, argGlobal, globalHolder);
+ MOZ_ASSERT(js::IsObjectInContextCompartment(arg, cx));
+ return GetOrCreateDOMReflector(cx, impl, args.rval());
+ """,
+ ifaceName=self.descriptor.interface.identifier.name,
+ implName=self.descriptor.name,
+ )
+
+ def getEventHookMethod(self, parentClass, methodName):
+ body = fill(
+ """
+ ${parentClass}::${methodName}(aType);
+ mImpl->${methodName}(Substring(nsDependentAtomString(aType), 2), IgnoreErrors());
+ """,
+ parentClass=parentClass,
+ methodName=methodName,
+ )
+ return [
+ ClassMethod(
+ methodName,
+ "void",
+ [Argument("nsAtom*", "aType")],
+ virtual=True,
+ override=True,
+ body=body,
+ ),
+ ClassUsingDeclaration(parentClass, methodName),
+ ]
+
+
+def isJSImplementedDescriptor(descriptorProvider):
+ return (
+ isinstance(descriptorProvider, Descriptor)
+ and descriptorProvider.interface.isJSImplemented()
+ )
+
+
+class CGCallback(CGClass):
+ def __init__(
+ self, idlObject, descriptorProvider, baseName, methods, getters=[], setters=[]
+ ):
+ self.baseName = baseName
+ self._deps = idlObject.getDeps()
+ self.idlObject = idlObject
+ self.name = idlObject.identifier.name
+ if isJSImplementedDescriptor(descriptorProvider):
+ self.name = jsImplName(self.name)
+ # For our public methods that needThisHandling we want most of the
+ # same args and the same return type as what CallbackMember
+ # generates. So we want to take advantage of all its
+ # CGNativeMember infrastructure, but that infrastructure can't deal
+ # with templates and most especially template arguments. So just
+ # cheat and have CallbackMember compute all those things for us.
+ realMethods = []
+ for method in methods:
+ if not isinstance(method, CallbackMember) or not method.needThisHandling:
+ realMethods.append(method)
+ else:
+ realMethods.extend(self.getMethodImpls(method))
+ realMethods.append(
+ ClassMethod(
+ "operator==",
+ "bool",
+ [Argument("const %s&" % self.name, "aOther")],
+ inline=True,
+ bodyInHeader=True,
+ const=True,
+ body=("return %s::operator==(aOther);\n" % baseName),
+ )
+ )
+ CGClass.__init__(
+ self,
+ self.name,
+ bases=[ClassBase(baseName)],
+ constructors=self.getConstructors(),
+ methods=realMethods + getters + setters,
+ )
+
+ def getConstructors(self):
+ if (
+ not self.idlObject.isInterface()
+ and not self.idlObject._treatNonObjectAsNull
+ ):
+ body = "MOZ_ASSERT(JS::IsCallable(mCallback));\n"
+ else:
+ # Not much we can assert about it, other than not being null, and
+ # CallbackObject does that already.
+ body = ""
+ return [
+ ClassConstructor(
+ [
+ Argument("JSContext*", "aCx"),
+ Argument("JS::Handle<JSObject*>", "aCallback"),
+ Argument("JS::Handle<JSObject*>", "aCallbackGlobal"),
+ Argument("nsIGlobalObject*", "aIncumbentGlobal"),
+ ],
+ bodyInHeader=True,
+ visibility="public",
+ explicit=True,
+ baseConstructors=[
+ "%s(aCx, aCallback, aCallbackGlobal, aIncumbentGlobal)"
+ % self.baseName,
+ ],
+ body=body,
+ ),
+ ClassConstructor(
+ [
+ Argument("JSObject*", "aCallback"),
+ Argument("JSObject*", "aCallbackGlobal"),
+ Argument("const FastCallbackConstructor&", ""),
+ ],
+ bodyInHeader=True,
+ visibility="public",
+ explicit=True,
+ baseConstructors=[
+ "%s(aCallback, aCallbackGlobal, FastCallbackConstructor())"
+ % self.baseName,
+ ],
+ body=body,
+ ),
+ ClassConstructor(
+ [
+ Argument("JSObject*", "aCallback"),
+ Argument("JSObject*", "aCallbackGlobal"),
+ Argument("JSObject*", "aAsyncStack"),
+ Argument("nsIGlobalObject*", "aIncumbentGlobal"),
+ ],
+ bodyInHeader=True,
+ visibility="public",
+ explicit=True,
+ baseConstructors=[
+ "%s(aCallback, aCallbackGlobal, aAsyncStack, aIncumbentGlobal)"
+ % self.baseName,
+ ],
+ body=body,
+ ),
+ ]
+
+ def getMethodImpls(self, method):
+ assert method.needThisHandling
+ args = list(method.args)
+ # Strip out the BindingCallContext&/JSObject* args
+ # that got added.
+ assert args[0].name == "cx" and args[0].argType == "BindingCallContext&"
+ assert args[1].name == "aThisVal" and args[1].argType == "JS::Handle<JS::Value>"
+ args = args[2:]
+
+ # Now remember which index the ErrorResult argument is at;
+ # we'll need this below.
+ assert args[-1].name == "aRv" and args[-1].argType == "ErrorResult&"
+ rvIndex = len(args) - 1
+ assert rvIndex >= 0
+
+ # Record the names of all the arguments, so we can use them when we call
+ # the private method.
+ argnames = [arg.name for arg in args]
+ argnamesWithThis = ["s.GetCallContext()", "thisValJS"] + argnames
+ argnamesWithoutThis = [
+ "s.GetCallContext()",
+ "JS::UndefinedHandleValue",
+ ] + argnames
+ # Now that we've recorded the argnames for our call to our private
+ # method, insert our optional argument for the execution reason.
+ args.append(Argument("const char*", "aExecutionReason", "nullptr"))
+
+ # Make copies of the arg list for the two "without rv" overloads. Note
+ # that those don't need aExceptionHandling or aRealm arguments because
+ # those would make not sense anyway: the only sane thing to do with
+ # exceptions in the "without rv" cases is to report them.
+ argsWithoutRv = list(args)
+ argsWithoutRv.pop(rvIndex)
+ argsWithoutThisAndRv = list(argsWithoutRv)
+
+ # Add the potional argument for deciding whether the CallSetup should
+ # re-throw exceptions on aRv.
+ args.append(
+ Argument("ExceptionHandling", "aExceptionHandling", "eReportExceptions")
+ )
+ # And the argument for communicating when exceptions should really be
+ # rethrown. In particular, even when aExceptionHandling is
+ # eRethrowExceptions they won't get rethrown if aRealm is provided
+ # and its principal doesn't subsume either the callback or the
+ # exception.
+ args.append(Argument("JS::Realm*", "aRealm", "nullptr"))
+ # And now insert our template argument.
+ argsWithoutThis = list(args)
+ args.insert(0, Argument("const T&", "thisVal"))
+ argsWithoutRv.insert(0, Argument("const T&", "thisVal"))
+
+ argnamesWithoutThisAndRv = [arg.name for arg in argsWithoutThisAndRv]
+ argnamesWithoutThisAndRv.insert(rvIndex, "IgnoreErrors()")
+ # If we just leave things like that, and have no actual arguments in the
+ # IDL, we will end up trying to call the templated "without rv" overload
+ # with "rv" as the thisVal. That's no good. So explicitly append the
+ # aExceptionHandling and aRealm values we need to end up matching the
+ # signature of our non-templated "with rv" overload.
+ argnamesWithoutThisAndRv.extend(["eReportExceptions", "nullptr"])
+
+ argnamesWithoutRv = [arg.name for arg in argsWithoutRv]
+ # Note that we need to insert at rvIndex + 1, since we inserted a
+ # thisVal arg at the start.
+ argnamesWithoutRv.insert(rvIndex + 1, "IgnoreErrors()")
+
+ errorReturn = method.getDefaultRetval()
+
+ setupCall = fill(
+ """
+ MOZ_ASSERT(!aRv.Failed(), "Don't pass an already-failed ErrorResult to a callback!");
+ if (!aExecutionReason) {
+ aExecutionReason = "${executionReason}";
+ }
+ CallSetup s(this, aRv, aExecutionReason, aExceptionHandling, aRealm);
+ if (!s.GetContext()) {
+ MOZ_ASSERT(aRv.Failed());
+ return${errorReturn};
+ }
+ """,
+ errorReturn=errorReturn,
+ executionReason=method.getPrettyName(),
+ )
+
+ bodyWithThis = fill(
+ """
+ $*{setupCall}
+ JS::Rooted<JS::Value> thisValJS(s.GetContext());
+ if (!ToJSValue(s.GetContext(), thisVal, &thisValJS)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return${errorReturn};
+ }
+ return ${methodName}(${callArgs});
+ """,
+ setupCall=setupCall,
+ errorReturn=errorReturn,
+ methodName=method.name,
+ callArgs=", ".join(argnamesWithThis),
+ )
+ bodyWithoutThis = fill(
+ """
+ $*{setupCall}
+ return ${methodName}(${callArgs});
+ """,
+ setupCall=setupCall,
+ errorReturn=errorReturn,
+ methodName=method.name,
+ callArgs=", ".join(argnamesWithoutThis),
+ )
+ bodyWithThisWithoutRv = fill(
+ """
+ return ${methodName}(${callArgs});
+ """,
+ methodName=method.name,
+ callArgs=", ".join(argnamesWithoutRv),
+ )
+ bodyWithoutThisAndRv = fill(
+ """
+ return ${methodName}(${callArgs});
+ """,
+ methodName=method.name,
+ callArgs=", ".join(argnamesWithoutThisAndRv),
+ )
+
+ return [
+ ClassMethod(
+ method.name,
+ method.returnType,
+ args,
+ bodyInHeader=True,
+ templateArgs=["typename T"],
+ body=bodyWithThis,
+ canRunScript=method.canRunScript,
+ ),
+ ClassMethod(
+ method.name,
+ method.returnType,
+ argsWithoutThis,
+ bodyInHeader=True,
+ body=bodyWithoutThis,
+ canRunScript=method.canRunScript,
+ ),
+ ClassMethod(
+ method.name,
+ method.returnType,
+ argsWithoutRv,
+ bodyInHeader=True,
+ templateArgs=["typename T"],
+ body=bodyWithThisWithoutRv,
+ canRunScript=method.canRunScript,
+ ),
+ ClassMethod(
+ method.name,
+ method.returnType,
+ argsWithoutThisAndRv,
+ bodyInHeader=True,
+ body=bodyWithoutThisAndRv,
+ canRunScript=method.canRunScript,
+ ),
+ method,
+ ]
+
+ def deps(self):
+ return self._deps
+
+
+class CGCallbackFunction(CGCallback):
+ def __init__(self, callback, descriptorProvider):
+ self.callback = callback
+ if callback.isConstructor():
+ methods = [ConstructCallback(callback, descriptorProvider)]
+ else:
+ methods = [CallCallback(callback, descriptorProvider)]
+ CGCallback.__init__(
+ self, callback, descriptorProvider, "CallbackFunction", methods
+ )
+
+ def getConstructors(self):
+ return CGCallback.getConstructors(self) + [
+ ClassConstructor(
+ [Argument("CallbackFunction*", "aOther")],
+ bodyInHeader=True,
+ visibility="public",
+ explicit=True,
+ baseConstructors=["CallbackFunction(aOther)"],
+ )
+ ]
+
+
+class CGFastCallback(CGClass):
+ def __init__(self, idlObject):
+ self._deps = idlObject.getDeps()
+ baseName = idlObject.identifier.name
+ constructor = ClassConstructor(
+ [
+ Argument("JSObject*", "aCallback"),
+ Argument("JSObject*", "aCallbackGlobal"),
+ ],
+ bodyInHeader=True,
+ visibility="public",
+ explicit=True,
+ baseConstructors=[
+ "%s(aCallback, aCallbackGlobal, FastCallbackConstructor())" % baseName,
+ ],
+ body="",
+ )
+
+ traceMethod = ClassMethod(
+ "Trace",
+ "void",
+ [Argument("JSTracer*", "aTracer")],
+ inline=True,
+ bodyInHeader=True,
+ visibility="public",
+ body="%s::Trace(aTracer);\n" % baseName,
+ )
+ holdMethod = ClassMethod(
+ "FinishSlowJSInitIfMoreThanOneOwner",
+ "void",
+ [Argument("JSContext*", "aCx")],
+ inline=True,
+ bodyInHeader=True,
+ visibility="public",
+ body=("%s::FinishSlowJSInitIfMoreThanOneOwner(aCx);\n" % baseName),
+ )
+
+ CGClass.__init__(
+ self,
+ "Fast%s" % baseName,
+ bases=[ClassBase(baseName)],
+ constructors=[constructor],
+ methods=[traceMethod, holdMethod],
+ )
+
+ def deps(self):
+ return self._deps
+
+
+class CGCallbackInterface(CGCallback):
+ def __init__(self, descriptor, spiderMonkeyInterfacesAreStructs=False):
+ iface = descriptor.interface
+ attrs = [
+ m
+ for m in iface.members
+ if (
+ m.isAttr()
+ and not m.isStatic()
+ and (not m.isMaplikeOrSetlikeAttr() or not iface.isJSImplemented())
+ )
+ ]
+ getters = [
+ CallbackGetter(a, descriptor, spiderMonkeyInterfacesAreStructs)
+ for a in attrs
+ ]
+ setters = [
+ CallbackSetter(a, descriptor, spiderMonkeyInterfacesAreStructs)
+ for a in attrs
+ if not a.readonly
+ ]
+ methods = [
+ m
+ for m in iface.members
+ if (
+ m.isMethod()
+ and not m.isStatic()
+ and not m.isIdentifierLess()
+ and (
+ not m.isMaplikeOrSetlikeOrIterableMethod()
+ or not iface.isJSImplemented()
+ )
+ )
+ ]
+ methods = [
+ CallbackOperation(m, sig, descriptor, spiderMonkeyInterfacesAreStructs)
+ for m in methods
+ for sig in m.signatures()
+ ]
+
+ needInitId = False
+ if iface.isJSImplemented() and iface.ctor():
+ sigs = descriptor.interface.ctor().signatures()
+ if len(sigs) != 1:
+ raise TypeError("We only handle one constructor. See bug 869268.")
+ methods.append(CGJSImplInitOperation(sigs[0], descriptor))
+ needInitId = True
+
+ idlist = [
+ descriptor.binaryNameFor(m.identifier.name, m.isStatic())
+ for m in iface.members
+ if m.isAttr() or m.isMethod()
+ ]
+ if needInitId:
+ idlist.append("__init")
+
+ if iface.isJSImplemented() and iface.getExtendedAttribute(
+ "WantsEventListenerHooks"
+ ):
+ methods.append(CGJSImplEventHookOperation(descriptor, "eventListenerAdded"))
+ methods.append(
+ CGJSImplEventHookOperation(descriptor, "eventListenerRemoved")
+ )
+ idlist.append("eventListenerAdded")
+ idlist.append("eventListenerRemoved")
+
+ if len(idlist) != 0:
+ methods.append(initIdsClassMethod(idlist, iface.identifier.name + "Atoms"))
+ CGCallback.__init__(
+ self,
+ iface,
+ descriptor,
+ "CallbackInterface",
+ methods,
+ getters=getters,
+ setters=setters,
+ )
+
+
+class FakeMember:
+ def __init__(self, name=None):
+ if name is not None:
+ self.identifier = FakeIdentifier(name)
+
+ def isStatic(self):
+ return False
+
+ def isAttr(self):
+ return False
+
+ def isMethod(self):
+ return False
+
+ def getExtendedAttribute(self, name):
+ # Claim to be a [NewObject] so we can avoid the "return a raw pointer"
+ # comments CGNativeMember codegen would otherwise stick in.
+ if name == "NewObject":
+ return True
+ return None
+
+
+class CallbackMember(CGNativeMember):
+ # XXXbz It's OK to use CallbackKnownNotGray 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,
+ spiderMonkeyInterfacesAreStructs=False,
+ wrapScope=None,
+ canRunScript=False,
+ passJSBitsAsNeeded=False,
+ ):
+ """
+ needThisHandling is True if we need to be able to accept a specified
+ thisObj, False otherwise.
+ """
+ assert not rethrowContentException or not needThisHandling
+
+ self.retvalType = sig[0]
+ self.originalSig = sig
+ args = sig[1]
+ self.argCount = len(args)
+ if self.argCount > 0:
+ # Check for variadic arguments
+ lastArg = args[self.argCount - 1]
+ if lastArg.variadic:
+ self.argCountStr = "(%d - 1) + %s.Length()" % (
+ self.argCount,
+ lastArg.identifier.name,
+ )
+ else:
+ self.argCountStr = "%d" % self.argCount
+ self.needThisHandling = needThisHandling
+ # If needThisHandling, we generate ourselves as private and the caller
+ # 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.
+ CGNativeMember.__init__(
+ self,
+ descriptorProvider,
+ FakeMember(),
+ name,
+ (self.retvalType, args),
+ extendedAttrs=["needsErrorResult"],
+ passJSBitsAsNeeded=passJSBitsAsNeeded,
+ visibility=visibility,
+ spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs,
+ canRunScript=canRunScript,
+ )
+ # We have to do all the generation of our body now, because
+ # the caller relies on us throwing if we can't manage it.
+ self.body = self.getImpl()
+
+ def getImpl(self):
+ setupCall = self.getCallSetup()
+ declRval = self.getRvalDecl()
+ if self.argCount > 0:
+ argvDecl = fill(
+ """
+ JS::RootedVector<JS::Value> argv(cx);
+ if (!argv.resize(${argCount})) {
+ $*{failureCode}
+ return${errorReturn};
+ }
+ """,
+ argCount=self.argCountStr,
+ failureCode=self.getArgvDeclFailureCode(),
+ errorReturn=self.getDefaultRetval(),
+ )
+ else:
+ # Avoid weird 0-sized arrays
+ argvDecl = ""
+ convertArgs = self.getArgConversions()
+ doCall = self.getCall()
+ returnResult = self.getResultConversion()
+
+ body = declRval + argvDecl + convertArgs + doCall
+ if self.needsScopeBody():
+ body = "{\n" + indent(body) + "}\n"
+ return setupCall + body + returnResult
+
+ def needsScopeBody(self):
+ return False
+
+ def getArgvDeclFailureCode(self):
+ return dedent(
+ """
+ // That threw an exception on the JSContext, and our CallSetup will do
+ // the right thing with that.
+ """
+ )
+
+ def getExceptionCode(self, forResult):
+ return fill(
+ """
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return${defaultRetval};
+ """,
+ defaultRetval=self.getDefaultRetval(),
+ )
+
+ def getResultConversion(
+ self, val="rval", failureCode=None, isDefinitelyObject=False, exceptionCode=None
+ ):
+ replacements = {
+ "val": val,
+ "holderName": "rvalHolder",
+ "declName": "rvalDecl",
+ # We actually want to pass in a null scope object here, because
+ # wrapping things into our current compartment (that of mCallback)
+ # is what we want.
+ "obj": "nullptr",
+ "passedToJSImpl": "false",
+ }
+
+ if isJSImplementedDescriptor(self.descriptorProvider):
+ isCallbackReturnValue = "JSImpl"
+ else:
+ isCallbackReturnValue = "Callback"
+ sourceDescription = "return value of %s" % self.getPrettyName()
+ convertType = instantiateJSToNativeConversion(
+ getJSToNativeConversionInfo(
+ self.retvalType,
+ self.descriptorProvider,
+ failureCode=failureCode,
+ isDefinitelyObject=isDefinitelyObject,
+ exceptionCode=exceptionCode or self.getExceptionCode(forResult=True),
+ isCallbackReturnValue=isCallbackReturnValue,
+ # Allow returning a callback type that
+ # allows non-callable objects.
+ allowTreatNonCallableAsNull=True,
+ sourceDescription=sourceDescription,
+ ),
+ replacements,
+ )
+ assignRetval = string.Template(
+ self.getRetvalInfo(self.retvalType, False)[2]
+ ).substitute(replacements)
+ type = convertType.define()
+ return type + assignRetval
+
+ def getArgConversions(self):
+ # Just reget the arglist from self.originalSig, because our superclasses
+ # just have way to many members they like to clobber, so I can't find a
+ # safe member name to store it in.
+ argConversions = [
+ self.getArgConversion(i, arg) for i, arg in enumerate(self.originalSig[1])
+ ]
+ if not argConversions:
+ return "\n"
+
+ # Do them back to front, so our argc modifications will work
+ # correctly, because we examine trailing arguments first.
+ argConversions.reverse()
+ # Wrap each one in a scope so that any locals it has don't leak out, and
+ # also so that we can just "break;" for our successCode.
+ argConversions = [
+ CGWrapper(CGIndenter(CGGeneric(c)), pre="do {\n", post="} while (false);\n")
+ for c in argConversions
+ ]
+ if self.argCount > 0:
+ argConversions.insert(0, self.getArgcDecl())
+ # And slap them together.
+ return CGList(argConversions, "\n").define() + "\n"
+
+ def getArgConversion(self, i, arg):
+ argval = arg.identifier.name
+
+ if arg.variadic:
+ argval = argval + "[idx]"
+ jsvalIndex = "%d + idx" % i
+ else:
+ jsvalIndex = "%d" % i
+ if arg.canHaveMissingValue():
+ argval += ".Value()"
+
+ if arg.type.isDOMString():
+ # XPConnect string-to-JS conversion wants to mutate the string. So
+ # let's give it a string it can mutate
+ # XXXbz if we try to do a sequence of strings, this will kinda fail.
+ result = "mutableStr"
+ prepend = "nsString mutableStr(%s);\n" % argval
+ else:
+ result = argval
+ prepend = ""
+
+ wrapScope = self.wrapScope
+ if arg.type.isUnion() and wrapScope is None:
+ prepend += (
+ "JS::Rooted<JSObject*> callbackObj(cx, CallbackKnownNotGray());\n"
+ )
+ wrapScope = "callbackObj"
+
+ conversion = prepend + wrapForType(
+ arg.type,
+ self.descriptorProvider,
+ {
+ "result": result,
+ "successCode": "continue;\n" if arg.variadic else "break;\n",
+ "jsvalRef": "argv[%s]" % jsvalIndex,
+ "jsvalHandle": "argv[%s]" % jsvalIndex,
+ "obj": wrapScope,
+ "returnsNewObject": False,
+ "exceptionCode": self.getExceptionCode(forResult=False),
+ "spiderMonkeyInterfacesAreStructs": self.spiderMonkeyInterfacesAreStructs,
+ },
+ )
+
+ if arg.variadic:
+ conversion = fill(
+ """
+ for (uint32_t idx = 0; idx < ${arg}.Length(); ++idx) {
+ $*{conversion}
+ }
+ break;
+ """,
+ arg=arg.identifier.name,
+ conversion=conversion,
+ )
+ elif arg.canHaveMissingValue():
+ conversion = fill(
+ """
+ if (${argName}.WasPassed()) {
+ $*{conversion}
+ } else if (argc == ${iPlus1}) {
+ // This is our current trailing argument; reduce argc
+ --argc;
+ } else {
+ argv[${i}].setUndefined();
+ }
+ """,
+ argName=arg.identifier.name,
+ conversion=conversion,
+ iPlus1=i + 1,
+ i=i,
+ )
+ return conversion
+
+ def getDefaultRetval(self):
+ default = self.getRetvalInfo(self.retvalType, False)[1]
+ if len(default) != 0:
+ default = " " + default
+ return default
+
+ def getArgs(self, returnType, argList):
+ args = CGNativeMember.getArgs(self, returnType, argList)
+ if not self.needThisHandling:
+ # Since we don't need this handling, we're the actual method that
+ # will be called, so we need an aRethrowExceptions argument.
+ if not self.rethrowContentException:
+ args.append(Argument("const char*", "aExecutionReason", "nullptr"))
+ args.append(
+ Argument(
+ "ExceptionHandling", "aExceptionHandling", "eReportExceptions"
+ )
+ )
+ args.append(Argument("JS::Realm*", "aRealm", "nullptr"))
+ return args
+ # We want to allow the caller to pass in a "this" value, as
+ # well as a BindingCallContext.
+ return [
+ Argument("BindingCallContext&", "cx"),
+ Argument("JS::Handle<JS::Value>", "aThisVal"),
+ ] + args
+
+ def getCallSetup(self):
+ if self.needThisHandling:
+ # It's been done for us already
+ return ""
+ callSetup = "CallSetup s(this, aRv"
+ if self.rethrowContentException:
+ # getArgs doesn't add the aExceptionHandling argument but does add
+ # aRealm for us.
+ callSetup += (
+ ', "%s", eRethrowContentExceptions, aRealm, /* aIsJSImplementedWebIDL = */ '
+ % self.getPrettyName()
+ )
+ callSetup += toStringBool(
+ isJSImplementedDescriptor(self.descriptorProvider)
+ )
+ else:
+ callSetup += ', "%s", aExceptionHandling, aRealm' % self.getPrettyName()
+ callSetup += ");\n"
+ return fill(
+ """
+ $*{callSetup}
+ if (aRv.Failed()) {
+ return${errorReturn};
+ }
+ MOZ_ASSERT(s.GetContext());
+ BindingCallContext& cx = s.GetCallContext();
+
+ """,
+ callSetup=callSetup,
+ errorReturn=self.getDefaultRetval(),
+ )
+
+ def getArgcDecl(self):
+ return CGGeneric("unsigned argc = %s;\n" % self.argCountStr)
+
+ @staticmethod
+ def ensureASCIIName(idlObject):
+ type = "attribute" if idlObject.isAttr() else "operation"
+ if re.match("[^\x20-\x7E]", idlObject.identifier.name):
+ raise SyntaxError(
+ 'Callback %s name "%s" contains non-ASCII '
+ "characters. We can't handle that. %s"
+ % (type, idlObject.identifier.name, idlObject.location)
+ )
+ if re.match('"', idlObject.identifier.name):
+ raise SyntaxError(
+ "Callback %s name '%s' contains "
+ "double-quote character. We can't handle "
+ "that. %s" % (type, idlObject.identifier.name, idlObject.location)
+ )
+
+
+class ConstructCallback(CallbackMember):
+ def __init__(self, callback, descriptorProvider):
+ self.callback = callback
+ CallbackMember.__init__(
+ self,
+ callback.signatures()[0],
+ "Construct",
+ descriptorProvider,
+ needThisHandling=False,
+ canRunScript=True,
+ )
+
+ def getRvalDecl(self):
+ # Box constructedObj for getJSToNativeConversionInfo().
+ return "JS::Rooted<JS::Value> rval(cx);\n"
+
+ def getCall(self):
+ if self.argCount > 0:
+ args = "JS::HandleValueArray::subarray(argv, 0, argc)"
+ else:
+ args = "JS::HandleValueArray::empty()"
+
+ return fill(
+ """
+ JS::Rooted<JS::Value> constructor(cx, JS::ObjectValue(*mCallback));
+ JS::Rooted<JSObject*> constructedObj(cx);
+ if (!JS::Construct(cx, constructor,
+ ${args}, &constructedObj)) {
+ aRv.NoteJSContextException(cx);
+ return${errorReturn};
+ }
+ rval.setObject(*constructedObj);
+ """,
+ args=args,
+ errorReturn=self.getDefaultRetval(),
+ )
+
+ def getResultConversion(self):
+ return CallbackMember.getResultConversion(self, isDefinitelyObject=True)
+
+ def getPrettyName(self):
+ return self.callback.identifier.name
+
+
+class CallbackMethod(CallbackMember):
+ def __init__(
+ self,
+ sig,
+ name,
+ descriptorProvider,
+ needThisHandling,
+ rethrowContentException=False,
+ spiderMonkeyInterfacesAreStructs=False,
+ canRunScript=False,
+ ):
+ CallbackMember.__init__(
+ self,
+ sig,
+ name,
+ descriptorProvider,
+ needThisHandling,
+ rethrowContentException,
+ spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs,
+ canRunScript=canRunScript,
+ )
+
+ def getRvalDecl(self):
+ return "JS::Rooted<JS::Value> rval(cx);\n"
+
+ def getNoteCallFailed(self):
+ return fill(
+ """
+ aRv.NoteJSContextException(cx);
+ return${errorReturn};
+ """,
+ errorReturn=self.getDefaultRetval(),
+ )
+
+ def getCall(self):
+ if self.argCount > 0:
+ args = "JS::HandleValueArray::subarray(argv, 0, argc)"
+ else:
+ args = "JS::HandleValueArray::empty()"
+
+ return fill(
+ """
+ $*{declCallable}
+ $*{declThis}
+ if (${callGuard}!JS::Call(cx, ${thisVal}, callable,
+ ${args}, &rval)) {
+ $*{noteError}
+ }
+ """,
+ declCallable=self.getCallableDecl(),
+ declThis=self.getThisDecl(),
+ callGuard=self.getCallGuard(),
+ thisVal=self.getThisVal(),
+ args=args,
+ noteError=self.getNoteCallFailed(),
+ )
+
+
+class CallCallback(CallbackMethod):
+ def __init__(self, callback, descriptorProvider):
+ self.callback = callback
+ CallbackMethod.__init__(
+ self,
+ callback.signatures()[0],
+ "Call",
+ descriptorProvider,
+ needThisHandling=True,
+ canRunScript=not callback.isRunScriptBoundary(),
+ )
+
+ def getNoteCallFailed(self):
+ if self.retvalType.isPromise():
+ return dedent(
+ """
+ // Convert exception to a rejected promise.
+ // See https://heycam.github.io/webidl/#call-a-user-objects-operation
+ // step 12 and step 15.5.
+ return CreateRejectedPromiseFromThrownException(cx, aRv);
+ """
+ )
+ return CallbackMethod.getNoteCallFailed(self)
+
+ def getExceptionCode(self, forResult):
+ # If the result value is a promise, and conversion
+ # to the promise throws an exception we shouldn't
+ # try to convert that exception to a promise again.
+ if self.retvalType.isPromise() and not forResult:
+ return dedent(
+ """
+ // Convert exception to a rejected promise.
+ // See https://heycam.github.io/webidl/#call-a-user-objects-operation
+ // step 10 and step 15.5.
+ return CreateRejectedPromiseFromThrownException(cx, aRv);
+ """
+ )
+ return CallbackMethod.getExceptionCode(self, forResult)
+
+ def getThisDecl(self):
+ return ""
+
+ def getThisVal(self):
+ return "aThisVal"
+
+ def getCallableDecl(self):
+ return "JS::Rooted<JS::Value> callable(cx, JS::ObjectValue(*mCallback));\n"
+
+ def getPrettyName(self):
+ return self.callback.identifier.name
+
+ def getCallGuard(self):
+ if self.callback._treatNonObjectAsNull:
+ return "JS::IsCallable(mCallback) && "
+ return ""
+
+
+class CallbackOperationBase(CallbackMethod):
+ """
+ Common class for implementing various callback operations.
+ """
+
+ def __init__(
+ self,
+ signature,
+ jsName,
+ nativeName,
+ descriptor,
+ singleOperation,
+ rethrowContentException=False,
+ spiderMonkeyInterfacesAreStructs=False,
+ ):
+ self.singleOperation = singleOperation
+ self.methodName = descriptor.binaryNameFor(jsName, False)
+ CallbackMethod.__init__(
+ self,
+ signature,
+ nativeName,
+ descriptor,
+ singleOperation,
+ rethrowContentException,
+ spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs,
+ )
+
+ def getThisDecl(self):
+ if not self.singleOperation:
+ return "JS::Rooted<JS::Value> thisValue(cx, JS::ObjectValue(*mCallback));\n"
+ # This relies on getCallableDecl declaring a boolean
+ # isCallable in the case when we're a single-operation
+ # interface.
+ return dedent(
+ """
+ JS::Rooted<JS::Value> thisValue(cx, isCallable ? aThisVal.get()
+ : JS::ObjectValue(*mCallback));
+ """
+ )
+
+ def getThisVal(self):
+ return "thisValue"
+
+ def getCallableDecl(self):
+ getCallableFromProp = fill(
+ """
+ ${atomCacheName}* atomsCache = GetAtomCache<${atomCacheName}>(cx);
+ if ((reinterpret_cast<jsid*>(atomsCache)->isVoid() &&
+ !InitIds(cx, atomsCache)) ||
+ !GetCallableProperty(cx, atomsCache->${methodAtomName}, &callable)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return${errorReturn};
+ }
+ """,
+ methodAtomName=CGDictionary.makeIdName(self.methodName),
+ atomCacheName=self.descriptorProvider.interface.identifier.name + "Atoms",
+ errorReturn=self.getDefaultRetval(),
+ )
+ if not self.singleOperation:
+ return "JS::Rooted<JS::Value> callable(cx);\n" + getCallableFromProp
+ return fill(
+ """
+ bool isCallable = JS::IsCallable(mCallback);
+ JS::Rooted<JS::Value> callable(cx);
+ if (isCallable) {
+ callable = JS::ObjectValue(*mCallback);
+ } else {
+ $*{getCallableFromProp}
+ }
+ """,
+ getCallableFromProp=getCallableFromProp,
+ )
+
+ def getCallGuard(self):
+ return ""
+
+
+class CallbackOperation(CallbackOperationBase):
+ """
+ Codegen actual WebIDL operations on callback interfaces.
+ """
+
+ def __init__(self, method, signature, descriptor, spiderMonkeyInterfacesAreStructs):
+ self.ensureASCIIName(method)
+ self.method = method
+ jsName = method.identifier.name
+ CallbackOperationBase.__init__(
+ self,
+ signature,
+ jsName,
+ MakeNativeName(descriptor.binaryNameFor(jsName, False)),
+ descriptor,
+ descriptor.interface.isSingleOperationInterface(),
+ rethrowContentException=descriptor.interface.isJSImplemented(),
+ spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs,
+ )
+
+ def getPrettyName(self):
+ return "%s.%s" % (
+ self.descriptorProvider.interface.identifier.name,
+ self.method.identifier.name,
+ )
+
+
+class CallbackAccessor(CallbackMember):
+ """
+ Shared superclass for CallbackGetter and CallbackSetter.
+ """
+
+ def __init__(self, attr, sig, name, descriptor, spiderMonkeyInterfacesAreStructs):
+ self.ensureASCIIName(attr)
+ self.attrName = attr.identifier.name
+ CallbackMember.__init__(
+ self,
+ sig,
+ name,
+ descriptor,
+ needThisHandling=False,
+ rethrowContentException=descriptor.interface.isJSImplemented(),
+ spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs,
+ )
+
+ def getPrettyName(self):
+ return "%s.%s" % (
+ self.descriptorProvider.interface.identifier.name,
+ self.attrName,
+ )
+
+
+class CallbackGetter(CallbackAccessor):
+ def __init__(self, attr, descriptor, spiderMonkeyInterfacesAreStructs):
+ CallbackAccessor.__init__(
+ self,
+ attr,
+ (attr.type, []),
+ callbackGetterName(attr, descriptor),
+ descriptor,
+ spiderMonkeyInterfacesAreStructs,
+ )
+
+ def getRvalDecl(self):
+ return "JS::Rooted<JS::Value> rval(cx);\n"
+
+ def getCall(self):
+ return fill(
+ """
+ JS::Rooted<JSObject *> callback(cx, mCallback);
+ ${atomCacheName}* atomsCache = GetAtomCache<${atomCacheName}>(cx);
+ if ((reinterpret_cast<jsid*>(atomsCache)->isVoid()
+ && !InitIds(cx, atomsCache)) ||
+ !JS_GetPropertyById(cx, callback, atomsCache->${attrAtomName}, &rval)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return${errorReturn};
+ }
+ """,
+ atomCacheName=self.descriptorProvider.interface.identifier.name + "Atoms",
+ attrAtomName=CGDictionary.makeIdName(
+ self.descriptorProvider.binaryNameFor(self.attrName, False)
+ ),
+ errorReturn=self.getDefaultRetval(),
+ )
+
+
+class CallbackSetter(CallbackAccessor):
+ def __init__(self, attr, descriptor, spiderMonkeyInterfacesAreStructs):
+ CallbackAccessor.__init__(
+ self,
+ attr,
+ (
+ BuiltinTypes[IDLBuiltinType.Types.undefined],
+ [FakeArgument(attr.type)],
+ ),
+ callbackSetterName(attr, descriptor),
+ descriptor,
+ spiderMonkeyInterfacesAreStructs,
+ )
+
+ def getRvalDecl(self):
+ # We don't need an rval
+ return ""
+
+ def getCall(self):
+ return fill(
+ """
+ MOZ_ASSERT(argv.length() == 1);
+ JS::Rooted<JSObject*> callback(cx, CallbackKnownNotGray());
+ ${atomCacheName}* atomsCache = GetAtomCache<${atomCacheName}>(cx);
+ if ((reinterpret_cast<jsid*>(atomsCache)->isVoid() &&
+ !InitIds(cx, atomsCache)) ||
+ !JS_SetPropertyById(cx, callback, atomsCache->${attrAtomName}, argv[0])) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return${errorReturn};
+ }
+ """,
+ atomCacheName=self.descriptorProvider.interface.identifier.name + "Atoms",
+ attrAtomName=CGDictionary.makeIdName(
+ self.descriptorProvider.binaryNameFor(self.attrName, False)
+ ),
+ errorReturn=self.getDefaultRetval(),
+ )
+
+ def getArgcDecl(self):
+ return None
+
+
+class CGJSImplInitOperation(CallbackOperationBase):
+ """
+ Codegen the __Init() method used to pass along constructor arguments for JS-implemented WebIDL.
+ """
+
+ def __init__(self, sig, descriptor):
+ assert sig in descriptor.interface.ctor().signatures()
+ CallbackOperationBase.__init__(
+ self,
+ (BuiltinTypes[IDLBuiltinType.Types.undefined], sig[1]),
+ "__init",
+ "__Init",
+ descriptor,
+ singleOperation=False,
+ rethrowContentException=True,
+ spiderMonkeyInterfacesAreStructs=True,
+ )
+
+ def getPrettyName(self):
+ return "__init"
+
+
+class CGJSImplEventHookOperation(CallbackOperationBase):
+ """
+ Codegen the hooks on a JS impl for adding/removing event listeners.
+ """
+
+ def __init__(self, descriptor, name):
+ self.name = name
+
+ CallbackOperationBase.__init__(
+ self,
+ (
+ BuiltinTypes[IDLBuiltinType.Types.undefined],
+ [FakeArgument(BuiltinTypes[IDLBuiltinType.Types.domstring], "aType")],
+ ),
+ name,
+ MakeNativeName(name),
+ descriptor,
+ singleOperation=False,
+ rethrowContentException=False,
+ spiderMonkeyInterfacesAreStructs=True,
+ )
+
+ def getPrettyName(self):
+ return self.name
+
+
+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.maplikeOrSetlikeOrIterableType.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, descriptor),
+ 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,
+ needsValueTypeReturn=False,
+ 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
+ code = dedent(
+ """
+ if (!JS::${funcName}(${args})) {
+ $*{errorReturn}
+ }
+ """
+ )
+
+ if needsValueTypeReturn:
+ assert self.helperImpl and impl_method_name == "get"
+ code += fill(
+ """
+ if (result.isUndefined()) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return${retval};
+ }
+ """,
+ retval=self.helperImpl.getDefaultRetval(),
+ )
+
+ self.cgRoot.append(
+ CGWrapper(
+ CGGeneric(
+ fill(
+ code,
+ 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
+ a RootedVector<JS::Value>, while interface methods have separate 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 a RootedVector<JS::Value>, while interface methods have
+ separate 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_ReportErrorASCII(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("bool retVal;\n")], ["&retVal"], [])
+ 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 = []
+ # We don't need to create the result variable because it'll be created elsewhere
+ # for JSObject Get method
+ if not self.helperImpl or not self.helperImpl.needsScopeBody():
+ 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 CGHelperFunctionGenerator(CallbackMember):
+ """
+ Generates code to allow C++ to perform operations. Gets a context from the
+ binding wrapper, turns arguments into JS::Values (via
+ CallbackMember/CGNativeMember argument conversion), then uses
+ getCall to generate the body for getting result, and maybe convert the
+ result into return type (via CallbackMember/CGNativeMember result
+ conversion)
+ """
+
+ class HelperFunction(CGAbstractMethod):
+ """
+ Generates context retrieval code and rooted JSObject for interface for
+ method generator to use
+ """
+
+ def __init__(self, descriptor, name, args, code, returnType):
+ self.code = code
+ CGAbstractMethod.__init__(self, descriptor, name, returnType, args)
+
+ def definition_body(self):
+ return self.code
+
+ def __init__(
+ self,
+ descriptor,
+ name,
+ args,
+ returnType=BuiltinTypes[IDLBuiltinType.Types.undefined],
+ needsResultConversion=True,
+ ):
+ assert returnType.isType()
+ self.needsResultConversion = needsResultConversion
+
+ # 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,
+ [returnType, args],
+ name,
+ descriptor,
+ False,
+ wrapScope="obj",
+ passJSBitsAsNeeded=typeNeedsCx(returnType),
+ )
+
+ # Wrap CallbackMember body code into a CGAbstractMethod to make
+ # generation easier.
+ self.implMethod = CGHelperFunctionGenerator.HelperFunction(
+ descriptor, name, self.args, self.body, self.returnType
+ )
+
+ def getCallSetup(self):
+ # If passJSBitsAsNeeded is true, it means the caller will provide a
+ # JSContext, so we don't need to create JSContext and enter
+ # UnprivilegedJunkScopeOrWorkerGlobal here.
+ code = "MOZ_ASSERT(self);\n"
+ if not self.passJSBitsAsNeeded:
+ code += dedent(
+ """
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ // It's safe to use UnprivilegedJunkScopeOrWorkerGlobal here because
+ // all we want is to wrap into _some_ scope and then unwrap to find
+ // the reflector, and wrapping has no side-effects.
+ JSObject* scope = UnprivilegedJunkScopeOrWorkerGlobal(fallible);
+ if (!scope) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return%s;
+ }
+ JSAutoRealm tempRealm(cx, scope);
+ """
+ % self.getDefaultRetval()
+ )
+
+ code += dedent(
+ """
+ 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(), /* stopAtWindowProxy = */ false);
+ """
+ % self.getDefaultRetval()
+ )
+
+ # We'd like wrap the inner code in a scope such that the code can use the
+ # same realm. So here we are creating the result variable outside of the
+ # scope.
+ if self.needsScopeBody():
+ code += "JS::Rooted<JS::Value> result(cx);\n"
+
+ return code
+
+ 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 needsScopeBody(self):
+ return self.passJSBitsAsNeeded
+
+ def getArgvDeclFailureCode(self):
+ return "aRv.Throw(NS_ERROR_UNEXPECTED);\n"
+
+ def getResultConversion(self):
+ if self.needsResultConversion:
+ code = ""
+ if self.needsScopeBody():
+ code = dedent(
+ """
+ if (!JS_WrapValue(cx, &result)) {
+ aRv.NoteJSContextException(cx);
+ return;
+ }
+ """
+ )
+
+ failureCode = dedent("aRv.Throw(NS_ERROR_UNEXPECTED);\nreturn nullptr;\n")
+
+ exceptionCode = None
+ if self.retvalType.isPrimitive():
+ exceptionCode = dedent(
+ "aRv.NoteJSContextException(cx);\nreturn%s;\n"
+ % self.getDefaultRetval()
+ )
+
+ return code + CallbackMember.getResultConversion(
+ self,
+ "result",
+ failureCode=failureCode,
+ isDefinitelyObject=True,
+ exceptionCode=exceptionCode,
+ )
+
+ assignRetval = string.Template(
+ self.getRetvalInfo(self.retvalType, False)[2]
+ ).substitute(
+ {
+ "declName": "retVal",
+ }
+ )
+ return assignRetval
+
+ def getRvalDecl(self):
+ # hack to make sure we put JSAutoRealm inside the body scope
+ return "JSAutoRealm reflectorRealm(cx, obj);\n"
+
+ def getArgcDecl(self):
+ # Don't need argc for anything.
+ return None
+
+ def getCall(self):
+ assert False # Override me!
+
+ def getPrettyName(self):
+ return self.name
+
+ def declare(self):
+ return self.implMethod.declare()
+
+ def define(self):
+ return self.implMethod.define()
+
+
+class CGMaplikeOrSetlikeHelperFunctionGenerator(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
+ CGMaplikeOrSetlikeMethodGenerator to generate the body.
+ """
+
+ def __init__(
+ self,
+ descriptor,
+ maplikeOrSetlike,
+ name,
+ needsKeyArg=False,
+ needsValueArg=False,
+ needsValueTypeReturn=False,
+ needsBoolReturn=False,
+ needsResultConversion=True,
+ ):
+ self.maplikeOrSetlike = maplikeOrSetlike
+ self.needsValueTypeReturn = needsValueTypeReturn
+
+ args = []
+ if needsKeyArg:
+ args.append(FakeArgument(maplikeOrSetlike.keyType, "aKey"))
+ if needsValueArg:
+ assert needsKeyArg
+ assert not needsValueTypeReturn
+ args.append(FakeArgument(maplikeOrSetlike.valueType, "aValue"))
+
+ returnType = BuiltinTypes[IDLBuiltinType.Types.undefined]
+ if needsBoolReturn:
+ returnType = BuiltinTypes[IDLBuiltinType.Types.boolean]
+ elif needsValueTypeReturn:
+ returnType = maplikeOrSetlike.valueType
+
+ CGHelperFunctionGenerator.__init__(
+ self,
+ descriptor,
+ name,
+ args,
+ returnType,
+ needsResultConversion,
+ )
+
+ def getCall(self):
+ return CGMaplikeOrSetlikeMethodGenerator(
+ self.descriptorProvider,
+ self.maplikeOrSetlike,
+ self.name.lower(),
+ self.needsValueTypeReturn,
+ helperImpl=self,
+ ).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
+ # Since iterables are folded in with maplike/setlike, make sure we've
+ # got the right type here.
+ assert maplikeOrSetlike.isMaplike() or maplikeOrSetlike.isSetlike()
+ self.maplikeOrSetlike = maplikeOrSetlike
+ self.namespace = "%sHelpers" % (
+ self.maplikeOrSetlike.maplikeOrSetlikeOrIterableType.title()
+ )
+ self.helpers = [
+ CGMaplikeOrSetlikeHelperFunctionGenerator(
+ descriptor, maplikeOrSetlike, "Clear"
+ ),
+ CGMaplikeOrSetlikeHelperFunctionGenerator(
+ descriptor,
+ maplikeOrSetlike,
+ "Delete",
+ needsKeyArg=True,
+ needsBoolReturn=True,
+ needsResultConversion=False,
+ ),
+ CGMaplikeOrSetlikeHelperFunctionGenerator(
+ descriptor,
+ maplikeOrSetlike,
+ "Has",
+ needsKeyArg=True,
+ needsBoolReturn=True,
+ needsResultConversion=False,
+ ),
+ ]
+ if self.maplikeOrSetlike.isMaplike():
+ self.helpers.append(
+ CGMaplikeOrSetlikeHelperFunctionGenerator(
+ descriptor,
+ maplikeOrSetlike,
+ "Set",
+ needsKeyArg=True,
+ needsValueArg=True,
+ )
+ )
+ self.helpers.append(
+ CGMaplikeOrSetlikeHelperFunctionGenerator(
+ descriptor,
+ maplikeOrSetlike,
+ "Get",
+ needsKeyArg=True,
+ needsValueTypeReturn=True,
+ )
+ )
+ else:
+ assert self.maplikeOrSetlike.isSetlike()
+ self.helpers.append(
+ CGMaplikeOrSetlikeHelperFunctionGenerator(
+ descriptor, maplikeOrSetlike, "Add", needsKeyArg=True
+ )
+ )
+ CGNamespace.__init__(self, self.namespace, CGList(self.helpers))
+
+
+class CGIterableMethodGenerator(CGGeneric):
+ """
+ Creates methods for iterable interfaces. Unwrapping/wrapping
+ will be taken care of by the usual method generation machinery in
+ CGMethodCall/CGPerSignatureCall. Functionality is filled in here instead of
+ using CGCallGenerator.
+ """
+
+ def __init__(self, descriptor, methodName, args):
+ if methodName == "forEach":
+ assert len(args) == 2
+
+ CGGeneric.__init__(
+ self,
+ fill(
+ """
+ if (!JS::IsCallable(arg0)) {
+ cx.ThrowErrorMessage<MSG_NOT_CALLABLE>("Argument 1");
+ return false;
+ }
+ JS::RootedValueArray<3> callArgs(cx);
+ callArgs[2].setObject(*obj);
+ JS::Rooted<JS::Value> ignoredReturnVal(cx);
+ auto GetKeyAtIndex = &${selfType}::GetKeyAtIndex;
+ auto GetValueAtIndex = &${selfType}::GetValueAtIndex;
+ for (size_t i = 0; i < self->GetIterableLength(); ++i) {
+ if (!CallIterableGetter(cx, GetValueAtIndex, self, i,
+ callArgs[0])) {
+ return false;
+ }
+ if (!CallIterableGetter(cx, GetKeyAtIndex, self, i,
+ callArgs[1])) {
+ return false;
+ }
+ if (!JS::Call(cx, arg1, arg0, JS::HandleValueArray(callArgs),
+ &ignoredReturnVal)) {
+ return false;
+ }
+ }
+ """,
+ ifaceName=descriptor.interface.identifier.name,
+ selfType=descriptor.nativeType,
+ ),
+ )
+ return
+
+ if descriptor.interface.isIterable():
+ assert descriptor.interface.maplikeOrSetlikeOrIterable.isPairIterator()
+ assert len(args) == 0
+
+ wrap = f"{descriptor.interface.identifier.name}Iterator_Binding::Wrap"
+ iterClass = f"mozilla::dom::binding_detail::WrappableIterableIterator<{descriptor.nativeType}, &{wrap}>"
+ else:
+ needReturnMethod = toStringBool(
+ descriptor.interface.maplikeOrSetlikeOrIterable.getExtendedAttribute(
+ "GenerateReturnMethod"
+ )
+ is not None
+ )
+ wrap = f"{descriptor.interface.identifier.name}AsyncIterator_Binding::Wrap"
+ iterClass = f"mozilla::dom::binding_detail::WrappableAsyncIterableIterator<{descriptor.nativeType}, {needReturnMethod}, &{wrap}>"
+
+ createIterator = fill(
+ """
+ typedef ${iterClass} itrType;
+ RefPtr<itrType> result(new itrType(self,
+ itrType::IteratorType::${itrMethod}));
+ """,
+ iterClass=iterClass,
+ itrMethod=methodName.title(),
+ )
+
+ if descriptor.interface.isAsyncIterable():
+ args.append("initError")
+ createIterator = fill(
+ """
+ $*{createIterator}
+ {
+ ErrorResult initError;
+ self->InitAsyncIteratorData(result->Data(), itrType::IteratorType::${itrMethod}, ${args});
+ if (initError.MaybeSetPendingException(cx, "Asynchronous iterator initialization steps for ${ifaceName} failed")) {
+ return false;
+ }
+ }
+ """,
+ createIterator=createIterator,
+ itrMethod=methodName.title(),
+ args=", ".join(args),
+ ifaceName=descriptor.interface.identifier.name,
+ )
+
+ CGGeneric.__init__(self, createIterator)
+
+
+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.
+ """
+ assert attr.isAttr()
+ assert attr.type.isObservableArray()
+
+ # GetObservableArrayBackingObject may return a wrapped object for Xrays, so
+ # when we create it we need to unwrap it to store the interface in the
+ # reserved slot.
+ return fill(
+ """
+ JS::Rooted<JSObject*> backingObj(cx);
+ bool created = false;
+ if (!GetObservableArrayBackingObject(cx, obj, ${slot},
+ &backingObj, &created, ${namespace}::ObservableArrayProxyHandler::getInstance(),
+ self)) {
+ $*{errorReturn}
+ }
+ if (created) {
+ PreserveWrapper(self);
+ }
+ """,
+ namespace=toBindingNamespace(MakeNativeName(attr.identifier.name)),
+ slot=memberReservedSlot(attr, descriptor),
+ errorReturn=errorReturn,
+ selfType=descriptor.nativeType,
+ )
+
+
+def getObservableArrayGetterBody(descriptor, attr):
+ """
+ Creates the body for the getter method of an observableArray attribute.
+ """
+ assert attr.type.isObservableArray()
+ return fill(
+ """
+ $*{getBackingObj}
+ MOZ_ASSERT(!JS_IsExceptionPending(cx));
+ args.rval().setObject(*backingObj);
+ return true;
+ """,
+ getBackingObj=getObservableArrayBackingObject(descriptor, attr),
+ )
+
+
+class CGObservableArrayProxyHandler_callback(ClassMethod):
+ """
+ Base class for declaring and defining the hook methods for ObservableArrayProxyHandler
+ subclasses to get the interface native object from backing object and calls
+ its On{Set|Delete}* callback.
+
+ * 'callbackType': "Set" or "Delete".
+ * 'invalidTypeFatal' (optional): If True, we don't expect the type
+ conversion would fail, so generate the
+ assertion code if type conversion fails.
+ """
+
+ def __init__(
+ self, descriptor, attr, name, args, callbackType, invalidTypeFatal=False
+ ):
+ assert attr.isAttr()
+ assert attr.type.isObservableArray()
+ assert callbackType in ["Set", "Delete"]
+ self.descriptor = descriptor
+ self.attr = attr
+ self.innertype = attr.type.inner
+ self.callbackType = callbackType
+ self.invalidTypeFatal = invalidTypeFatal
+ ClassMethod.__init__(
+ self,
+ name,
+ "bool",
+ args,
+ visibility="protected",
+ virtual=True,
+ override=True,
+ const=True,
+ )
+
+ def preConversion(self):
+ """
+ The code to run before the conversion steps.
+ """
+ return ""
+
+ def preCallback(self):
+ """
+ The code to run before calling the callback.
+ """
+ return ""
+
+ def postCallback(self):
+ """
+ The code to run after calling the callback, all subclasses should override
+ this to generate the return values.
+ """
+ assert False # Override me!
+
+ def getBody(self):
+ exceptionCode = (
+ fill(
+ """
+ MOZ_ASSERT_UNREACHABLE("The item in ObservableArray backing list is not ${innertype}?");
+ return false;
+ """,
+ innertype=self.innertype,
+ )
+ if self.invalidTypeFatal
+ else None
+ )
+ convertType = instantiateJSToNativeConversion(
+ getJSToNativeConversionInfo(
+ self.innertype,
+ self.descriptor,
+ sourceDescription="Element in ObservableArray backing list",
+ exceptionCode=exceptionCode,
+ ),
+ {
+ "declName": "decl",
+ "holderName": "holder",
+ "val": "aValue",
+ },
+ )
+ callbackArgs = ["decl", "aIndex", "rv"]
+ if typeNeedsCx(self.innertype):
+ callbackArgs.insert(0, "cx")
+ return fill(
+ """
+ MOZ_ASSERT(IsObservableArrayProxy(aProxy));
+ $*{preConversion}
+
+ BindingCallContext cx(aCx, "ObservableArray ${name}");
+ $*{convertType}
+
+ $*{preCallback}
+ JS::Value val = js::GetProxyReservedSlot(aProxy, OBSERVABLE_ARRAY_DOM_INTERFACE_SLOT);
+ auto* interface = static_cast<${ifaceType}*>(val.toPrivate());
+ MOZ_ASSERT(interface);
+
+ ErrorResult rv;
+ MOZ_KnownLive(interface)->${methodName}(${callbackArgs});
+ $*{postCallback}
+ """,
+ preConversion=self.preConversion(),
+ name=self.name,
+ convertType=convertType.define(),
+ preCallback=self.preCallback(),
+ ifaceType=self.descriptor.nativeType,
+ methodName="On%s%s"
+ % (self.callbackType, MakeNativeName(self.attr.identifier.name)),
+ callbackArgs=", ".join(callbackArgs),
+ postCallback=self.postCallback(),
+ )
+
+
+class CGObservableArrayProxyHandler_OnDeleteItem(
+ CGObservableArrayProxyHandler_callback
+):
+ """
+ Declares and defines the hook methods for ObservableArrayProxyHandler
+ subclasses to get the interface native object from backing object and calls
+ its OnDelete* callback.
+ """
+
+ def __init__(self, descriptor, attr):
+ args = [
+ Argument("JSContext*", "aCx"),
+ Argument("JS::Handle<JSObject*>", "aProxy"),
+ Argument("JS::Handle<JS::Value>", "aValue"),
+ Argument("uint32_t", "aIndex"),
+ ]
+ CGObservableArrayProxyHandler_callback.__init__(
+ self,
+ descriptor,
+ attr,
+ "OnDeleteItem",
+ args,
+ "Delete",
+ True,
+ )
+
+ def postCallback(self):
+ return dedent(
+ """
+ return !rv.MaybeSetPendingException(cx);
+ """
+ )
+
+
+class CGObservableArrayProxyHandler_SetIndexedValue(
+ CGObservableArrayProxyHandler_callback
+):
+ """
+ Declares and defines the hook methods for ObservableArrayProxyHandler
+ subclasses to run the setting the indexed value steps.
+ """
+
+ def __init__(self, descriptor, attr):
+ args = [
+ Argument("JSContext*", "aCx"),
+ Argument("JS::Handle<JSObject*>", "aProxy"),
+ Argument("JS::Handle<JSObject*>", "aBackingList"),
+ Argument("uint32_t", "aIndex"),
+ Argument("JS::Handle<JS::Value>", "aValue"),
+ Argument("JS::ObjectOpResult&", "aResult"),
+ ]
+ CGObservableArrayProxyHandler_callback.__init__(
+ self,
+ descriptor,
+ attr,
+ "SetIndexedValue",
+ args,
+ "Set",
+ )
+
+ def preConversion(self):
+ return dedent(
+ """
+ uint32_t oldLen;
+ if (!JS::GetArrayLength(aCx, aBackingList, &oldLen)) {
+ return false;
+ }
+
+ if (aIndex > oldLen) {
+ return aResult.failBadIndex();
+ }
+ """
+ )
+
+ def preCallback(self):
+ return dedent(
+ """
+ if (aIndex < oldLen) {
+ JS::Rooted<JS::Value> value(aCx);
+ if (!JS_GetElement(aCx, aBackingList, aIndex, &value)) {
+ return false;
+ }
+
+ if (!OnDeleteItem(aCx, aProxy, value, aIndex)) {
+ return false;
+ }
+ }
+
+ """
+ )
+
+ def postCallback(self):
+ return dedent(
+ """
+ if (rv.MaybeSetPendingException(cx)) {
+ return false;
+ }
+
+ if (!JS_SetElement(aCx, aBackingList, aIndex, aValue)) {
+ return false;
+ }
+
+ return aResult.succeed();
+ """
+ )
+
+
+class CGObservableArrayProxyHandler(CGThing):
+ """
+ A class for declaring a ObservableArrayProxyHandler.
+ """
+
+ def __init__(self, descriptor, attr):
+ assert attr.isAttr()
+ assert attr.type.isObservableArray()
+ methods = [
+ # The item stored in backing object should always be converted successfully.
+ CGObservableArrayProxyHandler_OnDeleteItem(descriptor, attr),
+ CGObservableArrayProxyHandler_SetIndexedValue(descriptor, attr),
+ CGJSProxyHandler_getInstance("ObservableArrayProxyHandler"),
+ ]
+ parentClass = "mozilla::dom::ObservableArrayProxyHandler"
+ self.proxyHandler = CGClass(
+ "ObservableArrayProxyHandler",
+ bases=[ClassBase(parentClass)],
+ constructors=[],
+ methods=methods,
+ )
+
+ def declare(self):
+ # Our class declaration should happen when we're defining
+ return ""
+
+ def define(self):
+ return self.proxyHandler.declare() + "\n" + self.proxyHandler.define()
+
+
+class CGObservableArrayProxyHandlerGenerator(CGNamespace):
+ """
+ Declares and defines convenience methods for accessing backing list objects
+ for observable array attribute. Generates function signatures, un/packs
+ backing list objects from slot, etc.
+ """
+
+ def __init__(self, descriptor, attr):
+ assert attr.isAttr()
+ assert attr.type.isObservableArray()
+ namespace = toBindingNamespace(MakeNativeName(attr.identifier.name))
+ proxyHandler = CGObservableArrayProxyHandler(descriptor, attr)
+ CGNamespace.__init__(self, namespace, proxyHandler)
+
+
+class CGObservableArraySetterGenerator(CGGeneric):
+ """
+ Creates setter for an observableArray attributes.
+ """
+
+ def __init__(self, descriptor, attr):
+ assert attr.isAttr()
+ assert attr.type.isObservableArray()
+ getBackingObject = getObservableArrayBackingObject(descriptor, attr)
+ setElement = dedent(
+ """
+ if (!JS_SetElement(cx, backingObj, i, val)) {
+ return false;
+ }
+ """
+ )
+ conversion = wrapForType(
+ attr.type.inner,
+ descriptor,
+ {
+ "result": "arg0.ElementAt(i)",
+ "successCode": setElement,
+ "jsvalRef": "val",
+ "jsvalHandle": "&val",
+ },
+ )
+ CGGeneric.__init__(
+ self,
+ fill(
+ """
+ if (xpc::WrapperFactory::IsXrayWrapper(obj)) {
+ JS_ReportErrorASCII(cx, "Accessing from Xray wrapper is not supported.");
+ return false;
+ }
+
+ ${getBackingObject}
+ const ObservableArrayProxyHandler* handler = GetObservableArrayProxyHandler(backingObj);
+ if (!handler->SetLength(cx, backingObj, 0)) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> val(cx);
+ for (size_t i = 0; i < arg0.Length(); i++) {
+ $*{conversion}
+ }
+ """,
+ conversion=conversion,
+ getBackingObject=getBackingObject,
+ ),
+ )
+
+
+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.undefined],
+ 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.
+
+ To generate code, call the method associated with the target, and then
+ call the appropriate define/declare method.
+ """
+
+ @staticmethod
+ def GeneratedAtomList(config):
+ # Atom enum
+ dictionaries = config.dictionaries
+
+ structs = []
+
+ def memberToAtomCacheMember(binaryNameFor, m):
+ binaryMemberName = binaryNameFor(m)
+ return ClassMember(
+ CGDictionary.makeIdName(binaryMemberName),
+ "PinnedStringId",
+ visibility="public",
+ )
+
+ def buildAtomCacheStructure(idlobj, binaryNameFor, members):
+ classMembers = [memberToAtomCacheMember(binaryNameFor, m) for m in members]
+ structName = idlobj.identifier.name + "Atoms"
+ return (
+ structName,
+ CGWrapper(
+ CGClass(
+ structName, bases=None, isStruct=True, members=classMembers
+ ),
+ post="\n",
+ ),
+ )
+
+ for dict in dictionaries:
+ if len(dict.members) == 0:
+ continue
+
+ structs.append(
+ buildAtomCacheStructure(dict, lambda m: m.identifier.name, dict.members)
+ )
+
+ for d in config.getDescriptors(isJSImplemented=True) + config.getDescriptors(
+ isCallback=True
+ ):
+ members = [m for m in d.interface.members if m.isAttr() or m.isMethod()]
+ if d.interface.isJSImplemented() and d.interface.ctor():
+ # We'll have an __init() method.
+ members.append(FakeMember("__init"))
+ if d.interface.isJSImplemented() and d.interface.getExtendedAttribute(
+ "WantsEventListenerHooks"
+ ):
+ members.append(FakeMember("eventListenerAdded"))
+ members.append(FakeMember("eventListenerRemoved"))
+ if len(members) == 0:
+ continue
+
+ structs.append(
+ buildAtomCacheStructure(
+ d.interface,
+ lambda m: d.binaryNameFor(m.identifier.name, m.isStatic()),
+ members,
+ )
+ )
+
+ structs.sort()
+ generatedStructs = [struct for structName, struct in structs]
+ structNames = [structName for structName, struct in structs]
+
+ mainStruct = CGWrapper(
+ CGClass(
+ "PerThreadAtomCache",
+ bases=[ClassBase(structName) for structName in structNames],
+ isStruct=True,
+ ),
+ post="\n",
+ )
+
+ structs = CGList(generatedStructs + [mainStruct])
+
+ # Wrap all of that in our namespaces.
+ curr = CGNamespace.build(["mozilla", "dom"], CGWrapper(structs, pre="\n"))
+ curr = CGWrapper(curr, post="\n")
+
+ # Add include statement for PinnedStringId.
+ declareIncludes = ["mozilla/dom/PinnedStringId.h"]
+ curr = CGHeaders([], [], [], [], declareIncludes, [], "GeneratedAtomList", curr)
+
+ # Add include guards.
+ curr = CGIncludeGuard("GeneratedAtomList", curr)
+
+ # Add the auto-generated comment.
+ curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
+
+ # Done.
+ return curr
+
+ @staticmethod
+ def GeneratedEventList(config):
+ eventList = CGList([])
+ for generatedEvent in config.generatedEvents:
+ eventList.append(
+ CGGeneric(declare=("GENERATED_EVENT(%s)\n" % generatedEvent))
+ )
+ return eventList
+
+ @staticmethod
+ def PrototypeList(config):
+
+ # Prototype ID enum.
+ descriptorsWithPrototype = config.getDescriptors(
+ hasInterfacePrototypeObject=True
+ )
+ protos = [d.name for d in descriptorsWithPrototype]
+ idEnum = CGNamespacedEnum("id", "ID", ["_ID_Start"] + protos, [0, "_ID_Start"])
+ idEnum = CGList([idEnum])
+
+ def fieldSizeAssert(amount, jitInfoField, message):
+ maxFieldValue = (
+ "(uint64_t(1) << (sizeof(std::declval<JSJitInfo>().%s) * 8))"
+ % jitInfoField
+ )
+ return CGGeneric(
+ define='static_assert(%s < %s, "%s");\n\n'
+ % (amount, maxFieldValue, message)
+ )
+
+ idEnum.append(
+ fieldSizeAssert("id::_ID_Count", "protoID", "Too many prototypes!")
+ )
+
+ # Wrap all of that in our namespaces.
+ idEnum = CGNamespace.build(
+ ["mozilla", "dom", "prototypes"], CGWrapper(idEnum, pre="\n")
+ )
+ idEnum = CGWrapper(idEnum, post="\n")
+
+ curr = CGList(
+ [
+ CGGeneric(define="#include <stdint.h>\n"),
+ CGGeneric(define="#include <type_traits>\n\n"),
+ CGGeneric(define='#include "js/experimental/JitInfo.h"\n\n'),
+ CGGeneric(define='#include "mozilla/dom/BindingNames.h"\n\n'),
+ CGGeneric(define='#include "mozilla/dom/PrototypeList.h"\n\n'),
+ idEnum,
+ ]
+ )
+
+ # Let things know the maximum length of the prototype chain.
+ maxMacroName = "MAX_PROTOTYPE_CHAIN_LENGTH"
+ maxMacro = CGGeneric(
+ declare="#define " + maxMacroName + " " + str(config.maxProtoChainLength)
+ )
+ curr.append(CGWrapper(maxMacro, post="\n\n"))
+ curr.append(
+ fieldSizeAssert(
+ maxMacroName, "depth", "Some inheritance chain is too long!"
+ )
+ )
+
+ # Constructor ID enum.
+ constructors = [d.name for d in config.getDescriptors(hasInterfaceObject=True)]
+ idEnum = CGNamespacedEnum(
+ "id",
+ "ID",
+ ["_ID_Start"] + constructors,
+ ["prototypes::id::_ID_Count", "_ID_Start"],
+ )
+
+ # Wrap all of that in our namespaces.
+ idEnum = CGNamespace.build(
+ ["mozilla", "dom", "constructors"], CGWrapper(idEnum, pre="\n")
+ )
+ idEnum = CGWrapper(idEnum, post="\n")
+
+ curr.append(idEnum)
+
+ # Named properties object enum.
+ namedPropertiesObjects = [
+ d.name for d in config.getDescriptors(hasNamedPropertiesObject=True)
+ ]
+ idEnum = CGNamespacedEnum(
+ "id",
+ "ID",
+ ["_ID_Start"] + namedPropertiesObjects,
+ ["constructors::id::_ID_Count", "_ID_Start"],
+ )
+
+ # Wrap all of that in our namespaces.
+ idEnum = CGNamespace.build(
+ ["mozilla", "dom", "namedpropertiesobjects"], CGWrapper(idEnum, pre="\n")
+ )
+ idEnum = CGWrapper(idEnum, post="\n")
+
+ curr.append(idEnum)
+
+ traitsDecls = [
+ CGGeneric(
+ declare=dedent(
+ """
+ template <prototypes::ID PrototypeID>
+ struct PrototypeTraits;
+ """
+ )
+ )
+ ]
+ traitsDecls.extend(CGPrototypeTraitsClass(d) for d in descriptorsWithPrototype)
+
+ ifaceNamesWithProto = [
+ d.interface.getClassName() for d in descriptorsWithPrototype
+ ]
+ traitsDecls.append(
+ CGStringTable("NamesOfInterfacesWithProtos", ifaceNamesWithProto)
+ )
+
+ traitsDecl = CGNamespace.build(["mozilla", "dom"], CGList(traitsDecls))
+
+ curr.append(traitsDecl)
+
+ # Add include guards.
+ curr = CGIncludeGuard("PrototypeList", curr)
+
+ # Add the auto-generated comment.
+ curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
+
+ # Done.
+ return curr
+
+ @staticmethod
+ def BindingNames(config):
+ declare = fill(
+ """
+ enum class BindingNamesOffset : uint16_t {
+ $*{enumValues}
+ };
+
+ namespace binding_detail {
+ extern const char sBindingNames[];
+ } // namespace binding_detail
+
+ MOZ_ALWAYS_INLINE const char* BindingName(BindingNamesOffset aOffset) {
+ return binding_detail::sBindingNames + static_cast<size_t>(aOffset);
+ }
+ """,
+ enumValues="".join(
+ "%s = %i,\n" % (BindingNamesOffsetEnum(n), o)
+ for (n, o) in config.namesStringOffsets
+ ),
+ )
+ define = fill(
+ """
+ namespace binding_detail {
+
+ const char sBindingNames[] = {
+ $*{namesString}
+ };
+
+ } // namespace binding_detail
+
+ // Making this enum bigger than a uint16_t has consequences on the size
+ // of some structs (eg. WebIDLNameTableEntry) and tables. We should try
+ // to avoid that.
+ static_assert(EnumTypeFitsWithin<BindingNamesOffset, uint16_t>::value,
+ "Size increase");
+ """,
+ namesString=' "\\0"\n'.join(
+ '/* %5i */ "%s"' % (o, n) for (n, o) in config.namesStringOffsets
+ )
+ + "\n",
+ )
+
+ curr = CGGeneric(declare=declare, define=define)
+ curr = CGWrapper(curr, pre="\n", post="\n")
+
+ curr = CGNamespace.build(["mozilla", "dom"], curr)
+ curr = CGWrapper(curr, post="\n")
+
+ curr = CGHeaders(
+ [],
+ [],
+ [],
+ [],
+ ["<stddef.h>", "<stdint.h>", "mozilla/Attributes.h"],
+ ["mozilla/dom/BindingNames.h", "mozilla/EnumTypeTraits.h"],
+ "BindingNames",
+ curr,
+ )
+
+ # Add include guards.
+ curr = CGIncludeGuard("BindingNames", curr)
+
+ # Done.
+ return curr
+
+ @staticmethod
+ def RegisterBindings(config):
+
+ curr = CGNamespace.build(
+ ["mozilla", "dom"], CGGlobalNames(config.windowGlobalNames)
+ )
+ curr = CGWrapper(curr, post="\n")
+
+ # Add the includes
+ defineIncludes = [
+ CGHeaders.getDeclarationFilename(desc.interface)
+ for desc in config.getDescriptors(
+ hasInterfaceObject=True, isExposedInWindow=True, register=True
+ )
+ ]
+ defineIncludes.append("mozilla/dom/BindingNames.h")
+ defineIncludes.append("mozilla/dom/WebIDLGlobalNameHash.h")
+ defineIncludes.append("mozilla/dom/PrototypeList.h")
+ defineIncludes.append("mozilla/PerfectHash.h")
+ defineIncludes.append("js/String.h")
+ curr = CGHeaders([], [], [], [], [], defineIncludes, "RegisterBindings", curr)
+
+ # Add include guards.
+ curr = CGIncludeGuard("RegisterBindings", curr)
+
+ # Done.
+ return curr
+
+ @staticmethod
+ def RegisterWorkerBindings(config):
+
+ curr = CGRegisterWorkerBindings(config)
+
+ # Wrap all of that in our namespaces.
+ curr = CGNamespace.build(["mozilla", "dom"], CGWrapper(curr, post="\n"))
+ curr = CGWrapper(curr, post="\n")
+
+ # Add the includes
+ defineIncludes = [
+ CGHeaders.getDeclarationFilename(desc.interface)
+ for desc in config.getDescriptors(
+ hasInterfaceObject=True, register=True, isExposedInAnyWorker=True
+ )
+ ]
+
+ curr = CGHeaders(
+ [], [], [], [], [], defineIncludes, "RegisterWorkerBindings", curr
+ )
+
+ # Add include guards.
+ curr = CGIncludeGuard("RegisterWorkerBindings", curr)
+
+ # Done.
+ return curr
+
+ @staticmethod
+ def RegisterWorkerDebuggerBindings(config):
+
+ curr = CGRegisterWorkerDebuggerBindings(config)
+
+ # Wrap all of that in our namespaces.
+ curr = CGNamespace.build(["mozilla", "dom"], CGWrapper(curr, post="\n"))
+ curr = CGWrapper(curr, post="\n")
+
+ # Add the includes
+ defineIncludes = [
+ CGHeaders.getDeclarationFilename(desc.interface)
+ for desc in config.getDescriptors(
+ hasInterfaceObject=True, register=True, isExposedInWorkerDebugger=True
+ )
+ ]
+
+ curr = CGHeaders(
+ [], [], [], [], [], defineIncludes, "RegisterWorkerDebuggerBindings", curr
+ )
+
+ # Add include guards.
+ curr = CGIncludeGuard("RegisterWorkerDebuggerBindings", curr)
+
+ # Done.
+ return curr
+
+ @staticmethod
+ def RegisterWorkletBindings(config):
+
+ curr = CGRegisterWorkletBindings(config)
+
+ # Wrap all of that in our namespaces.
+ curr = CGNamespace.build(["mozilla", "dom"], CGWrapper(curr, post="\n"))
+ curr = CGWrapper(curr, post="\n")
+
+ # Add the includes
+ defineIncludes = [
+ CGHeaders.getDeclarationFilename(desc.interface)
+ for desc in config.getDescriptors(
+ hasInterfaceObject=True, register=True, isExposedInAnyWorklet=True
+ )
+ ]
+
+ curr = CGHeaders(
+ [], [], [], [], [], defineIncludes, "RegisterWorkletBindings", curr
+ )
+
+ # Add include guards.
+ curr = CGIncludeGuard("RegisterWorkletBindings", curr)
+
+ # Done.
+ return curr
+
+ @staticmethod
+ def RegisterShadowRealmBindings(config):
+
+ curr = CGRegisterShadowRealmBindings(config)
+
+ # Wrap all of that in our namespaces.
+ curr = CGNamespace.build(["mozilla", "dom"], CGWrapper(curr, post="\n"))
+ curr = CGWrapper(curr, post="\n")
+
+ # Add the includes
+ defineIncludes = [
+ CGHeaders.getDeclarationFilename(desc.interface)
+ for desc in config.getDescriptors(
+ hasInterfaceObject=True, register=True, isExposedInShadowRealms=True
+ )
+ ]
+
+ curr = CGHeaders(
+ [], [], [], [], [], defineIncludes, "RegisterShadowRealmBindings", curr
+ )
+
+ # Add include guards.
+ curr = CGIncludeGuard("RegisterShadowRealmBindings", curr)
+
+ # Done.
+ return curr
+
+ @staticmethod
+ def UnionTypes(config):
+ unionTypes = UnionsForFile(config, None)
+ (
+ includes,
+ implincludes,
+ declarations,
+ traverseMethods,
+ unlinkMethods,
+ unionStructs,
+ ) = UnionTypes(unionTypes, config)
+
+ unionStructs = dependencySortDictionariesAndUnionsAndCallbacks(unionStructs)
+
+ unions = CGList(
+ traverseMethods
+ + unlinkMethods
+ + [CGUnionStruct(t, config) for t in unionStructs]
+ + [CGUnionStruct(t, config, True) for t in unionStructs],
+ "\n",
+ )
+
+ includes.add("mozilla/OwningNonNull.h")
+ includes.add("mozilla/dom/UnionMember.h")
+ includes.add("mozilla/dom/BindingDeclarations.h")
+ # BindingUtils.h is only needed for SetToObject.
+ # If it stops being inlined or stops calling CallerSubsumes
+ # both this bit and the bit in CGBindingRoot can be removed.
+ includes.add("mozilla/dom/BindingUtils.h")
+
+ # Wrap all of that in our namespaces.
+ curr = CGNamespace.build(["mozilla", "dom"], unions)
+
+ curr = CGWrapper(curr, post="\n")
+
+ builder = ForwardDeclarationBuilder()
+ for className, isStruct in declarations:
+ builder.add(className, isStruct=isStruct)
+
+ curr = CGList([builder.build(), curr], "\n")
+
+ curr = CGHeaders([], [], [], [], includes, implincludes, "UnionTypes", curr)
+
+ # Add include guards.
+ curr = CGIncludeGuard("UnionTypes", curr)
+
+ # Done.
+ return curr
+
+ @staticmethod
+ def WebIDLPrefs(config):
+ prefs = set()
+ headers = set(["mozilla/dom/WebIDLPrefs.h"])
+ for d in config.getDescriptors(hasInterfaceOrInterfacePrototypeObject=True):
+ for m in d.interface.members:
+ pref = PropertyDefiner.getStringAttr(m, "Pref")
+ if pref:
+ headers.add(prefHeader(pref))
+ prefs.add((pref, prefIdentifier(pref)))
+ prefs = sorted(prefs)
+ declare = fill(
+ """
+ enum class WebIDLPrefIndex : uint8_t {
+ NoPref,
+ $*{prefs}
+ };
+ typedef bool (*WebIDLPrefFunc)();
+ extern const WebIDLPrefFunc sWebIDLPrefs[${len}];
+ """,
+ prefs=",\n".join(map(lambda p: "// " + p[0] + "\n" + p[1], prefs)) + "\n",
+ len=len(prefs) + 1,
+ )
+ define = fill(
+ """
+ const WebIDLPrefFunc sWebIDLPrefs[] = {
+ nullptr,
+ $*{prefs}
+ };
+ """,
+ prefs=",\n".join(
+ map(lambda p: "// " + p[0] + "\nStaticPrefs::" + p[1], prefs)
+ )
+ + "\n",
+ )
+ prefFunctions = CGGeneric(declare=declare, define=define)
+
+ # Wrap all of that in our namespaces.
+ curr = CGNamespace.build(["mozilla", "dom"], prefFunctions)
+
+ curr = CGWrapper(curr, post="\n")
+
+ curr = CGHeaders([], [], [], [], [], headers, "WebIDLPrefs", curr)
+
+ # Add include guards.
+ curr = CGIncludeGuard("WebIDLPrefs", curr)
+
+ # Done.
+ return curr
+
+ @staticmethod
+ def WebIDLSerializable(config):
+ # We need a declaration of StructuredCloneTags in the header.
+ declareIncludes = set(
+ [
+ "mozilla/dom/DOMJSClass.h",
+ "mozilla/dom/StructuredCloneTags.h",
+ "js/TypeDecls.h",
+ ]
+ )
+ defineIncludes = set(
+ ["mozilla/dom/WebIDLSerializable.h", "mozilla/PerfectHash.h"]
+ )
+ names = list()
+ for d in config.getDescriptors(isSerializable=True):
+ names.append(d.name)
+ defineIncludes.add(CGHeaders.getDeclarationFilename(d.interface))
+
+ if len(names) == 0:
+ # We can't really create a PerfectHash out of this, but also there's
+ # not much point to this file if we have no [Serializable] objects.
+ # Just spit out an empty file.
+ return CGIncludeGuard("WebIDLSerializable", CGGeneric(""))
+
+ # If we had a lot of serializable things, it might be worth it to use a
+ # PerfectHash here, or an array ordered by sctag value and binary
+ # search. But setting those up would require knowing in this python
+ # code the values of the various SCTAG_DOM_*. We could hardcode them
+ # here and add static asserts that the values are right, or switch to
+ # code-generating StructuredCloneTags.h or something. But in practice,
+ # there's a pretty small number of serializable interfaces, and just
+ # doing a linear walk is fine. It's not obviously worse than the
+ # if-cascade we used to have. Let's just make sure we notice if we do
+ # end up with a lot of serializable things here.
+ #
+ # Also, in practice it looks like compilers compile this linear walk to
+ # an out-of-bounds check followed by a direct index into an array, by
+ # basically making a second copy of this array ordered by tag, with the
+ # holes filled in. Again, worth checking whether this still happens if
+ # we have too many serializable things.
+ if len(names) > 20:
+ raise TypeError(
+ "We now have %s serializable interfaces. "
+ "Double-check that the compiler is still "
+ "generating a jump table." % len(names)
+ )
+
+ entries = list()
+ # Make sure we have stable ordering.
+ for name in sorted(names):
+ # Strip off trailing newline to make our formatting look right.
+ entries.append(
+ fill(
+ """
+ {
+ /* mTag */ ${tag},
+ /* mDeserialize */ ${name}_Binding::Deserialize
+ }
+ """,
+ tag=StructuredCloneTag(name),
+ name=name,
+ )[:-1]
+ )
+
+ declare = dedent(
+ """
+ WebIDLDeserializer LookupDeserializer(StructuredCloneTags aTag);
+ """
+ )
+ define = fill(
+ """
+ struct WebIDLSerializableEntry {
+ StructuredCloneTags mTag;
+ WebIDLDeserializer mDeserialize;
+ };
+
+ static const WebIDLSerializableEntry sEntries[] = {
+ $*{entries}
+ };
+
+ WebIDLDeserializer LookupDeserializer(StructuredCloneTags aTag) {
+ for (auto& entry : sEntries) {
+ if (entry.mTag == aTag) {
+ return entry.mDeserialize;
+ }
+ }
+ return nullptr;
+ }
+ """,
+ entries=",\n".join(entries) + "\n",
+ )
+
+ code = CGGeneric(declare=declare, define=define)
+
+ # Wrap all of that in our namespaces.
+ curr = CGNamespace.build(["mozilla", "dom"], code)
+
+ curr = CGWrapper(curr, post="\n")
+
+ curr = CGHeaders(
+ [], [], [], [], declareIncludes, defineIncludes, "WebIDLSerializable", curr
+ )
+
+ # Add include guards.
+ curr = CGIncludeGuard("WebIDLSerializable", curr)
+
+ # Done.
+ return curr
+
+
+# Code generator for simple events
+class CGEventGetter(CGNativeMember):
+ def __init__(self, descriptor, attr):
+ ea = descriptor.getExtendedAttributes(attr, getter=True)
+ CGNativeMember.__init__(
+ self,
+ descriptor,
+ attr,
+ CGSpecializedGetter.makeNativeName(descriptor, attr),
+ (attr.type, []),
+ ea,
+ resultNotAddRefed=not attr.type.isSequence(),
+ )
+ self.body = self.getMethodBody()
+
+ def getArgs(self, returnType, argList):
+ if "needsErrorResult" in self.extendedAttrs:
+ raise TypeError("Event code generator does not support [Throws]!")
+ if "canOOM" in self.extendedAttrs:
+ raise TypeError("Event code generator does not support [CanOOM]!")
+ if not self.member.isAttr():
+ raise TypeError("Event code generator does not support methods")
+ if self.member.isStatic():
+ raise TypeError("Event code generators does not support static attributes")
+ return CGNativeMember.getArgs(self, returnType, argList)
+
+ def getMethodBody(self):
+ type = self.member.type
+ memberName = CGDictionary.makeMemberName(self.member.identifier.name)
+ if (
+ (type.isPrimitive() and type.tag() in builtinNames)
+ or type.isEnum()
+ or type.isPromise()
+ or type.isGeckoInterface()
+ ):
+ return "return " + memberName + ";\n"
+ if type.isJSString():
+ # https://bugzilla.mozilla.org/show_bug.cgi?id=1580167
+ raise TypeError("JSString not supported as member of a generated event")
+ if (
+ type.isDOMString()
+ or type.isByteString()
+ or type.isUSVString()
+ or type.isUTF8String()
+ ):
+ return "aRetVal = " + memberName + ";\n"
+ if type.isSpiderMonkeyInterface() or type.isObject():
+ return fill(
+ """
+ if (${memberName}) {
+ JS::ExposeObjectToActiveJS(${memberName});
+ }
+ aRetVal.set(${memberName});
+ return;
+ """,
+ memberName=memberName,
+ )
+ if type.isAny():
+ return fill(
+ """
+ ${selfName}(aRetVal);
+ """,
+ selfName=self.name,
+ )
+ if type.isUnion():
+ return "aRetVal = " + memberName + ";\n"
+ if type.isSequence():
+ if type.nullable():
+ return (
+ "if ("
+ + memberName
+ + ".IsNull()) { aRetVal.SetNull(); } else { aRetVal.SetValue("
+ + memberName
+ + ".Value().Clone()); }\n"
+ )
+ else:
+ return "aRetVal = " + memberName + ".Clone();\n"
+ raise TypeError("Event code generator does not support this type!")
+
+ def declare(self, cgClass):
+ if (
+ getattr(self.member, "originatingInterface", cgClass.descriptor.interface)
+ != cgClass.descriptor.interface
+ ):
+ return ""
+ return CGNativeMember.declare(self, cgClass)
+
+ def define(self, cgClass):
+ if (
+ getattr(self.member, "originatingInterface", cgClass.descriptor.interface)
+ != cgClass.descriptor.interface
+ ):
+ return ""
+ return CGNativeMember.define(self, cgClass)
+
+
+class CGEventSetter(CGNativeMember):
+ def __init__(self):
+ raise TypeError("Event code generator does not support setters!")
+
+
+class CGEventMethod(CGNativeMember):
+ def __init__(self, descriptor, method, signature, isConstructor, breakAfter=True):
+ self.isInit = False
+
+ CGNativeMember.__init__(
+ self,
+ descriptor,
+ method,
+ CGSpecializedMethod.makeNativeName(descriptor, method),
+ signature,
+ descriptor.getExtendedAttributes(method),
+ breakAfter=breakAfter,
+ variadicIsSequence=True,
+ )
+ self.originalArgs = list(self.args)
+
+ iface = descriptor.interface
+ allowed = isConstructor
+ if not allowed and iface.getExtendedAttribute("LegacyEventInit"):
+ # Allow it, only if it fits the initFooEvent profile exactly
+ # We could check the arg types but it's not worth the effort.
+ if (
+ method.identifier.name == "init" + iface.identifier.name
+ and signature[1][0].type.isDOMString()
+ and signature[1][1].type.isBoolean()
+ and signature[1][2].type.isBoolean()
+ and
+ # -3 on the left to ignore the type, bubbles, and cancelable parameters
+ # -1 on the right to ignore the .trusted property which bleeds through
+ # here because it is [Unforgeable].
+ len(signature[1]) - 3
+ == len([x for x in iface.members if x.isAttr()]) - 1
+ ):
+ allowed = True
+ self.isInit = True
+
+ if not allowed:
+ raise TypeError("Event code generator does not support methods!")
+
+ def getArgs(self, returnType, argList):
+ args = [self.getArg(arg) for arg in argList]
+ return args
+
+ def getArg(self, arg):
+ decl, ref = self.getArgType(
+ arg.type, arg.canHaveMissingValue(), "Variadic" if arg.variadic else False
+ )
+ if ref:
+ decl = CGWrapper(decl, pre="const ", post="&")
+
+ name = arg.identifier.name
+ name = "a" + name[0].upper() + name[1:]
+ return Argument(decl.define(), name)
+
+ def declare(self, cgClass):
+ if self.isInit:
+ constructorForNativeCaller = ""
+ else:
+ self.args = list(self.originalArgs)
+ self.args.insert(0, Argument("mozilla::dom::EventTarget*", "aOwner"))
+ constructorForNativeCaller = CGNativeMember.declare(self, cgClass)
+
+ self.args = list(self.originalArgs)
+ if needCx(None, self.arguments(), [], considerTypes=True, static=True):
+ self.args.insert(0, Argument("JSContext*", "aCx"))
+ if not self.isInit:
+ self.args.insert(0, Argument("const GlobalObject&", "aGlobal"))
+
+ return constructorForNativeCaller + CGNativeMember.declare(self, cgClass)
+
+ def defineInit(self, cgClass):
+ iface = self.descriptorProvider.interface
+ members = ""
+ while iface.identifier.name != "Event":
+ i = 3 # Skip the boilerplate args: type, bubble,s cancelable.
+ for m in iface.members:
+ if m.isAttr():
+ # We need to initialize all the member variables that do
+ # not come from Event.
+ if (
+ getattr(m, "originatingInterface", iface).identifier.name
+ == "Event"
+ ):
+ continue
+ name = CGDictionary.makeMemberName(m.identifier.name)
+ members += "%s = %s;\n" % (name, self.args[i].name)
+ i += 1
+ iface = iface.parent
+
+ self.body = fill(
+ """
+ InitEvent(${typeArg}, ${bubblesArg}, ${cancelableArg});
+ ${members}
+ """,
+ typeArg=self.args[0].name,
+ bubblesArg=self.args[1].name,
+ cancelableArg=self.args[2].name,
+ members=members,
+ )
+
+ return CGNativeMember.define(self, cgClass)
+
+ def define(self, cgClass):
+ self.args = list(self.originalArgs)
+ if self.isInit:
+ return self.defineInit(cgClass)
+ members = ""
+ holdJS = ""
+ iface = self.descriptorProvider.interface
+ while iface.identifier.name != "Event":
+ for m in self.descriptorProvider.getDescriptor(
+ iface.identifier.name
+ ).interface.members:
+ if m.isAttr():
+ # We initialize all the other member variables in the
+ # Constructor except those ones coming from the Event.
+ if (
+ getattr(
+ m, "originatingInterface", cgClass.descriptor.interface
+ ).identifier.name
+ == "Event"
+ ):
+ continue
+ name = CGDictionary.makeMemberName(m.identifier.name)
+ if m.type.isSequence():
+ # For sequences we may not be able to do a simple
+ # assignment because the underlying types may not match.
+ # For example, the argument can be a
+ # Sequence<OwningNonNull<SomeInterface>> while our
+ # member is an nsTArray<RefPtr<SomeInterface>>. So
+ # use AppendElements, which is actually a template on
+ # the incoming type on nsTArray and does the right thing
+ # for this case.
+ target = name
+ source = "%s.%s" % (self.args[1].name, name)
+ sequenceCopy = "e->%s.AppendElements(%s);\n"
+ if m.type.nullable():
+ sequenceCopy = CGIfWrapper(
+ CGGeneric(sequenceCopy), "!%s.IsNull()" % source
+ ).define()
+ target += ".SetValue()"
+ source += ".Value()"
+ members += sequenceCopy % (target, source)
+ elif m.type.isSpiderMonkeyInterface():
+ srcname = "%s.%s" % (self.args[1].name, name)
+ if m.type.nullable():
+ members += fill(
+ """
+ if (${srcname}.IsNull()) {
+ e->${varname} = nullptr;
+ } else {
+ e->${varname} = ${srcname}.Value().Obj();
+ }
+ """,
+ varname=name,
+ srcname=srcname,
+ )
+ else:
+ members += fill(
+ """
+ e->${varname}.set(${srcname}.Obj());
+ """,
+ varname=name,
+ srcname=srcname,
+ )
+ else:
+ members += "e->%s = %s.%s;\n" % (name, self.args[1].name, name)
+ if (
+ m.type.isAny()
+ or m.type.isObject()
+ or m.type.isSpiderMonkeyInterface()
+ ):
+ holdJS = "mozilla::HoldJSObjects(e.get());\n"
+ iface = iface.parent
+
+ self.body = fill(
+ """
+ RefPtr<${nativeType}> e = new ${nativeType}(aOwner);
+ bool trusted = e->Init(aOwner);
+ e->InitEvent(${eventType}, ${eventInit}.mBubbles, ${eventInit}.mCancelable);
+ $*{members}
+ e->SetTrusted(trusted);
+ e->SetComposed(${eventInit}.mComposed);
+ $*{holdJS}
+ return e.forget();
+ """,
+ nativeType=self.descriptorProvider.nativeType.split("::")[-1],
+ eventType=self.args[0].name,
+ eventInit=self.args[1].name,
+ members=members,
+ holdJS=holdJS,
+ )
+
+ self.args.insert(0, Argument("mozilla::dom::EventTarget*", "aOwner"))
+ constructorForNativeCaller = CGNativeMember.define(self, cgClass) + "\n"
+ self.args = list(self.originalArgs)
+ self.body = fill(
+ """
+ nsCOMPtr<mozilla::dom::EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports());
+ return Constructor(owner, ${arg0}, ${arg1});
+ """,
+ arg0=self.args[0].name,
+ arg1=self.args[1].name,
+ )
+ if needCx(None, self.arguments(), [], considerTypes=True, static=True):
+ self.args.insert(0, Argument("JSContext*", "aCx"))
+ self.args.insert(0, Argument("const GlobalObject&", "aGlobal"))
+ return constructorForNativeCaller + CGNativeMember.define(self, cgClass)
+
+
+class CGEventClass(CGBindingImplClass):
+ """
+ Codegen for the actual Event class implementation for this descriptor
+ """
+
+ def __init__(self, descriptor):
+ CGBindingImplClass.__init__(
+ self,
+ descriptor,
+ CGEventMethod,
+ CGEventGetter,
+ CGEventSetter,
+ False,
+ "WrapObjectInternal",
+ )
+ members = []
+ extraMethods = []
+ self.membersNeedingCC = []
+ self.membersNeedingTrace = []
+
+ for m in descriptor.interface.members:
+ if (
+ getattr(m, "originatingInterface", descriptor.interface)
+ != descriptor.interface
+ ):
+ continue
+
+ if m.isAttr():
+ if m.type.isAny():
+ self.membersNeedingTrace.append(m)
+ # Add a getter that doesn't need a JSContext. Note that we
+ # don't need to do this if our originating interface is not
+ # the descriptor's interface, because in that case we
+ # wouldn't generate the getter that _does_ need a JSContext
+ # either.
+ extraMethods.append(
+ ClassMethod(
+ CGSpecializedGetter.makeNativeName(descriptor, m),
+ "void",
+ [Argument("JS::MutableHandle<JS::Value>", "aRetVal")],
+ const=True,
+ body=fill(
+ """
+ JS::ExposeValueToActiveJS(${memberName});
+ aRetVal.set(${memberName});
+ """,
+ memberName=CGDictionary.makeMemberName(
+ m.identifier.name
+ ),
+ ),
+ )
+ )
+ elif m.type.isObject() or m.type.isSpiderMonkeyInterface():
+ self.membersNeedingTrace.append(m)
+ elif typeNeedsRooting(m.type):
+ raise TypeError(
+ "Need to implement tracing for event member of type %s" % m.type
+ )
+ elif idlTypeNeedsCycleCollection(m.type):
+ self.membersNeedingCC.append(m)
+
+ nativeType = self.getNativeTypeForIDLType(m.type).define()
+ members.append(
+ ClassMember(
+ CGDictionary.makeMemberName(m.identifier.name),
+ nativeType,
+ visibility="private",
+ body="body",
+ )
+ )
+
+ parent = self.descriptor.interface.parent
+ self.parentType = self.descriptor.getDescriptor(
+ parent.identifier.name
+ ).nativeType.split("::")[-1]
+ self.nativeType = self.descriptor.nativeType.split("::")[-1]
+
+ if self.needCC():
+ isupportsDecl = fill(
+ """
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(${nativeType}, ${parentType})
+ """,
+ nativeType=self.nativeType,
+ parentType=self.parentType,
+ )
+ else:
+ isupportsDecl = fill(
+ """
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(${nativeType}, ${parentType})
+ """,
+ nativeType=self.nativeType,
+ parentType=self.parentType,
+ )
+
+ baseDeclarations = fill(
+ """
+ public:
+ $*{isupportsDecl}
+
+ protected:
+ virtual ~${nativeType}();
+ explicit ${nativeType}(mozilla::dom::EventTarget* aOwner);
+
+ """,
+ isupportsDecl=isupportsDecl,
+ nativeType=self.nativeType,
+ parentType=self.parentType,
+ )
+
+ className = self.nativeType
+ asConcreteTypeMethod = ClassMethod(
+ "As%s" % className,
+ "%s*" % className,
+ [],
+ virtual=True,
+ body="return this;\n",
+ breakAfterReturnDecl=" ",
+ override=True,
+ )
+ extraMethods.append(asConcreteTypeMethod)
+
+ CGClass.__init__(
+ self,
+ className,
+ bases=[ClassBase(self.parentType)],
+ methods=extraMethods + self.methodDecls,
+ members=members,
+ extradeclarations=baseDeclarations,
+ )
+
+ def getWrapObjectBody(self):
+ return (
+ "return %s_Binding::Wrap(aCx, this, aGivenProto);\n" % self.descriptor.name
+ )
+
+ def needCC(self):
+ return len(self.membersNeedingCC) != 0 or len(self.membersNeedingTrace) != 0
+
+ def implTraverse(self):
+ retVal = ""
+ for m in self.membersNeedingCC:
+ retVal += (
+ " NS_IMPL_CYCLE_COLLECTION_TRAVERSE(%s)\n"
+ % CGDictionary.makeMemberName(m.identifier.name)
+ )
+ return retVal
+
+ def implUnlink(self):
+ retVal = ""
+ for m in self.membersNeedingCC:
+ retVal += (
+ " NS_IMPL_CYCLE_COLLECTION_UNLINK(%s)\n"
+ % CGDictionary.makeMemberName(m.identifier.name)
+ )
+ for m in self.membersNeedingTrace:
+ name = CGDictionary.makeMemberName(m.identifier.name)
+ if m.type.isAny():
+ retVal += " tmp->" + name + ".setUndefined();\n"
+ elif m.type.isObject() or m.type.isSpiderMonkeyInterface():
+ retVal += " tmp->" + name + " = nullptr;\n"
+ else:
+ raise TypeError("Unknown traceable member type %s" % m.type)
+ return retVal
+
+ def implTrace(self):
+ retVal = ""
+ for m in self.membersNeedingTrace:
+ retVal += (
+ " NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(%s)\n"
+ % CGDictionary.makeMemberName(m.identifier.name)
+ )
+ return retVal
+
+ def define(self):
+ for m in self.membersNeedingTrace:
+ if not (
+ m.type.isAny() or m.type.isObject() or m.type.isSpiderMonkeyInterface()
+ ):
+ raise TypeError("Unknown traceable member type %s" % m.type)
+
+ if len(self.membersNeedingTrace) > 0:
+ dropJS = "mozilla::DropJSObjects(this);\n"
+ else:
+ dropJS = ""
+ # Just override CGClass and do our own thing
+ ctorParams = (
+ "aOwner, nullptr, nullptr" if self.parentType == "Event" else "aOwner"
+ )
+
+ if self.needCC():
+ classImpl = fill(
+ """
+
+ NS_IMPL_CYCLE_COLLECTION_CLASS(${nativeType})
+
+ NS_IMPL_ADDREF_INHERITED(${nativeType}, ${parentType})
+ NS_IMPL_RELEASE_INHERITED(${nativeType}, ${parentType})
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(${nativeType}, ${parentType})
+ $*{traverse}
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+ NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(${nativeType}, ${parentType})
+ $*{trace}
+ NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(${nativeType}, ${parentType})
+ $*{unlink}
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(${nativeType})
+ NS_INTERFACE_MAP_END_INHERITING(${parentType})
+ """,
+ nativeType=self.nativeType,
+ parentType=self.parentType,
+ traverse=self.implTraverse(),
+ unlink=self.implUnlink(),
+ trace=self.implTrace(),
+ )
+ else:
+ classImpl = ""
+
+ classImpl += fill(
+ """
+
+ ${nativeType}::${nativeType}(mozilla::dom::EventTarget* aOwner)
+ : ${parentType}(${ctorParams})
+ {
+ }
+
+ ${nativeType}::~${nativeType}()
+ {
+ $*{dropJS}
+ }
+
+ """,
+ nativeType=self.nativeType,
+ ctorParams=ctorParams,
+ parentType=self.parentType,
+ dropJS=dropJS,
+ )
+
+ return classImpl + CGBindingImplClass.define(self)
+
+ def getNativeTypeForIDLType(self, type):
+ if type.isPrimitive() and type.tag() in builtinNames:
+ nativeType = CGGeneric(builtinNames[type.tag()])
+ if type.nullable():
+ nativeType = CGTemplatedType("Nullable", nativeType)
+ elif type.isEnum():
+ nativeType = CGGeneric(type.unroll().inner.identifier.name)
+ if type.nullable():
+ nativeType = CGTemplatedType("Nullable", nativeType)
+ elif type.isJSString():
+ nativeType = CGGeneric("JS::Heap<JSString*>")
+ elif type.isDOMString() or type.isUSVString():
+ nativeType = CGGeneric("nsString")
+ elif type.isByteString() or type.isUTF8String():
+ nativeType = CGGeneric("nsCString")
+ elif type.isPromise():
+ nativeType = CGGeneric("RefPtr<Promise>")
+ elif type.isGeckoInterface():
+ iface = type.unroll().inner
+ nativeType = self.descriptor.getDescriptor(iface.identifier.name).nativeType
+ # Now trim off unnecessary namespaces
+ nativeType = nativeType.split("::")
+ if nativeType[0] == "mozilla":
+ nativeType.pop(0)
+ if nativeType[0] == "dom":
+ nativeType.pop(0)
+ nativeType = CGWrapper(
+ CGGeneric("::".join(nativeType)), pre="RefPtr<", post=">"
+ )
+ elif type.isAny():
+ nativeType = CGGeneric("JS::Heap<JS::Value>")
+ elif type.isObject() or type.isSpiderMonkeyInterface():
+ nativeType = CGGeneric("JS::Heap<JSObject*>")
+ elif type.isUnion():
+ nativeType = CGGeneric(CGUnionStruct.unionTypeDecl(type, True))
+ elif type.isSequence():
+ if type.nullable():
+ innerType = type.inner.inner
+ else:
+ innerType = type.inner
+ if (
+ not innerType.isPrimitive()
+ and not innerType.isEnum()
+ and not innerType.isDOMString()
+ and not innerType.isByteString()
+ and not innerType.isUTF8String()
+ and not innerType.isPromise()
+ and not innerType.isGeckoInterface()
+ ):
+ raise TypeError(
+ "Don't know how to properly manage GC/CC for "
+ "event member of type %s" % type
+ )
+ nativeType = CGTemplatedType(
+ "nsTArray", self.getNativeTypeForIDLType(innerType)
+ )
+ if type.nullable():
+ nativeType = CGTemplatedType("Nullable", nativeType)
+ else:
+ raise TypeError("Don't know how to declare event member of type %s" % type)
+ return nativeType
+
+
+class CGEventRoot(CGThing):
+ def __init__(self, config, interfaceName):
+ descriptor = config.getDescriptor(interfaceName)
+
+ self.root = CGWrapper(CGEventClass(descriptor), pre="\n", post="\n")
+
+ self.root = CGNamespace.build(["mozilla", "dom"], self.root)
+
+ self.root = CGList(
+ [CGClassForwardDeclare("JSContext", isStruct=True), self.root]
+ )
+
+ parent = descriptor.interface.parent.identifier.name
+
+ # Throw in our #includes
+ self.root = CGHeaders(
+ [descriptor],
+ [],
+ [],
+ [],
+ [
+ config.getDescriptor(parent).headerFile,
+ "mozilla/Attributes.h",
+ "mozilla/dom/%sBinding.h" % interfaceName,
+ "mozilla/dom/BindingUtils.h",
+ ],
+ [
+ "%s.h" % interfaceName,
+ "js/GCAPI.h",
+ "mozilla/HoldDropJSObjects.h",
+ "mozilla/dom/Nullable.h",
+ ],
+ "",
+ self.root,
+ config,
+ )
+
+ # And now some include guards
+ self.root = CGIncludeGuard(interfaceName, self.root)
+
+ self.root = CGWrapper(
+ self.root,
+ pre=(
+ AUTOGENERATED_WITH_SOURCE_WARNING_COMMENT
+ % os.path.basename(descriptor.interface.filename())
+ ),
+ )
+
+ self.root = CGWrapper(
+ self.root,
+ pre=dedent(
+ """
+ /* -*- 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/. */
+
+ """
+ ),
+ )
+
+ def declare(self):
+ return self.root.declare()
+
+ def define(self):
+ return self.root.define()
diff --git a/dom/bindings/Configuration.py b/dom/bindings/Configuration.py
new file mode 100644
index 0000000000..1bfdcdd100
--- /dev/null
+++ b/dom/bindings/Configuration.py
@@ -0,0 +1,1235 @@
+# 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/.
+
+import io
+import itertools
+import os
+from collections import defaultdict
+
+import six
+from WebIDL import IDLIncludesStatement
+
+autogenerated_comment = "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n"
+
+
+def toStringBool(arg):
+ """
+ Converts IDL/Python Boolean (True/False) to C++ Boolean (true/false)
+ """
+ return str(not not arg).lower()
+
+
+class DescriptorProvider:
+ """
+ A way of getting descriptors for interface names. Subclasses must
+ have a getDescriptor method callable with the interface name only.
+
+ Subclasses must also have a getConfig() method that returns a
+ Configuration.
+ """
+
+ def __init__(self):
+ pass
+
+
+def isChildPath(path, basePath):
+ path = os.path.normpath(path)
+ return os.path.commonprefix((path, basePath)) == basePath
+
+
+class Configuration(DescriptorProvider):
+ """
+ Represents global configuration state based on IDL parse data and
+ the configuration file.
+ """
+
+ def __init__(self, filename, webRoots, parseData, generatedEvents=[]):
+ DescriptorProvider.__init__(self)
+
+ # Read the configuration file.
+ glbl = {}
+ exec(io.open(filename, encoding="utf-8").read(), glbl)
+ config = glbl["DOMInterfaces"]
+
+ webRoots = tuple(map(os.path.normpath, webRoots))
+
+ def isInWebIDLRoot(path):
+ return any(isChildPath(path, root) for root in webRoots)
+
+ # Build descriptors for all the interfaces we have in the parse data.
+ # This allows callers to specify a subset of interfaces by filtering
+ # |parseData|.
+ self.descriptors = []
+ self.interfaces = {}
+ self.descriptorsByName = {}
+ self.dictionariesByName = {}
+ self.generatedEvents = generatedEvents
+ self.maxProtoChainLength = 0
+ for thing in parseData:
+ if isinstance(thing, IDLIncludesStatement):
+ # Our build system doesn't support dep build involving
+ # addition/removal of "includes" statements that appear in a
+ # different .webidl file than their LHS interface. Make sure we
+ # don't have any of those. See similar block below for partial
+ # interfaces!
+ if thing.interface.filename() != thing.filename():
+ raise TypeError(
+ "The binding build system doesn't really support "
+ "'includes' statements which don't appear in the "
+ "file in which the left-hand side of the statement is "
+ "defined.\n"
+ "%s\n"
+ "%s" % (thing.location, thing.interface.location)
+ )
+
+ assert not thing.isType()
+
+ if (
+ not thing.isInterface()
+ and not thing.isNamespace()
+ and not thing.isInterfaceMixin()
+ ):
+ continue
+ # Our build system doesn't support dep builds involving
+ # addition/removal of partial interfaces/namespaces/mixins that
+ # appear in a different .webidl file than the
+ # interface/namespace/mixin they are extending. Make sure we don't
+ # have any of those. See similar block above for "includes"
+ # statements!
+ if not thing.isExternal():
+ for partial in thing.getPartials():
+ if partial.filename() != thing.filename():
+ raise TypeError(
+ "The binding build system doesn't really support "
+ "partial interfaces/namespaces/mixins which don't "
+ "appear in the file in which the "
+ "interface/namespace/mixin they are extending is "
+ "defined. Don't do this.\n"
+ "%s\n"
+ "%s" % (partial.location, thing.location)
+ )
+
+ # The rest of the logic doesn't apply to mixins.
+ if thing.isInterfaceMixin():
+ continue
+
+ iface = thing
+ if not iface.isExternal():
+ if not (
+ iface.getExtendedAttribute("ChromeOnly")
+ or iface.getExtendedAttribute("Func")
+ == ["nsContentUtils::IsCallerChromeOrFuzzingEnabled"]
+ or not iface.hasInterfaceObject()
+ or isInWebIDLRoot(iface.filename())
+ ):
+ raise TypeError(
+ "Interfaces which are exposed to the web may only be "
+ "defined in a DOM WebIDL root %r. Consider marking "
+ "the interface [ChromeOnly] or "
+ "[Func='nsContentUtils::IsCallerChromeOrFuzzingEnabled'] "
+ "if you do not want it exposed to the web.\n"
+ "%s" % (webRoots, iface.location)
+ )
+
+ self.interfaces[iface.identifier.name] = iface
+
+ entry = config.get(iface.identifier.name, {})
+ assert not isinstance(entry, list)
+
+ desc = Descriptor(self, iface, entry)
+ self.descriptors.append(desc)
+ # Setting up descriptorsByName while iterating through interfaces
+ # means we can get the nativeType of iterable interfaces without
+ # having to do multiple loops.
+ assert desc.interface.identifier.name not in self.descriptorsByName
+ self.descriptorsByName[desc.interface.identifier.name] = desc
+
+ # Keep the descriptor list sorted for determinism.
+ self.descriptors.sort(key=lambda x: x.name)
+
+ self.descriptorsByFile = {}
+ for d in self.descriptors:
+ self.descriptorsByFile.setdefault(d.interface.filename(), []).append(d)
+
+ self.enums = [e for e in parseData if e.isEnum()]
+
+ self.dictionaries = [d for d in parseData if d.isDictionary()]
+ self.dictionariesByName = {d.identifier.name: d for d in self.dictionaries}
+
+ self.callbacks = [
+ c for c in parseData if c.isCallback() and not c.isInterface()
+ ]
+
+ # Dictionary mapping from a union type name to a set of filenames where
+ # union types with that name are used.
+ self.filenamesPerUnion = defaultdict(set)
+
+ # Dictionary mapping from a filename to a list of types for
+ # the union types used in that file. If a union type is used
+ # in multiple files then it will be added to the list for the
+ # None key. Note that the list contains a type for every use
+ # of a union type, so there can be multiple entries with union
+ # types that have the same name.
+ self.unionsPerFilename = defaultdict(list)
+
+ def addUnion(t):
+ filenamesForUnion = self.filenamesPerUnion[t.name]
+ if t.filename() not in filenamesForUnion:
+ # We have a to be a bit careful: some of our built-in
+ # typedefs are for unions, and those unions end up with
+ # "<unknown>" as the filename. If that happens, we don't
+ # want to try associating this union with one particular
+ # filename, since there isn't one to associate it with,
+ # really.
+ if t.filename() == "<unknown>":
+ uniqueFilenameForUnion = None
+ elif len(filenamesForUnion) == 0:
+ # This is the first file that we found a union with this
+ # name in, record the union as part of the file.
+ uniqueFilenameForUnion = t.filename()
+ else:
+ # We already found a file that contains a union with
+ # this name.
+ if len(filenamesForUnion) == 1:
+ # This is the first time we found a union with this
+ # name in another file.
+ for f in filenamesForUnion:
+ # Filter out unions with this name from the
+ # unions for the file where we previously found
+ # them.
+ unionsForFilename = [
+ u for u in self.unionsPerFilename[f] if u.name != t.name
+ ]
+ if len(unionsForFilename) == 0:
+ del self.unionsPerFilename[f]
+ else:
+ self.unionsPerFilename[f] = unionsForFilename
+ # Unions with this name appear in multiple files, record
+ # the filename as None, so that we can detect that.
+ uniqueFilenameForUnion = None
+ self.unionsPerFilename[uniqueFilenameForUnion].append(t)
+ filenamesForUnion.add(t.filename())
+
+ def addUnions(t):
+ t = findInnermostType(t)
+ if t.isUnion():
+ addUnion(t)
+ for m in t.flatMemberTypes:
+ addUnions(m)
+
+ for (t, _) in getAllTypes(self.descriptors, self.dictionaries, self.callbacks):
+ addUnions(t)
+
+ for d in getDictionariesConvertedToJS(
+ self.descriptors, self.dictionaries, self.callbacks
+ ):
+ d.needsConversionToJS = True
+
+ for d in getDictionariesConvertedFromJS(
+ self.descriptors, self.dictionaries, self.callbacks
+ ):
+ d.needsConversionFromJS = True
+
+ # Collect all the global names exposed on a Window object (to implement
+ # the hash for looking up these names when resolving a property).
+ self.windowGlobalNames = []
+ for desc in self.getDescriptors(registersGlobalNamesOnWindow=True):
+ self.windowGlobalNames.append((desc.name, desc))
+ self.windowGlobalNames.extend(
+ (n.identifier.name, desc) for n in desc.interface.legacyFactoryFunctions
+ )
+ self.windowGlobalNames.extend(
+ (n, desc) for n in desc.interface.legacyWindowAliases
+ )
+
+ # Collect a sorted list of strings that we want to concatenate into
+ # one big string and a dict mapping each string to its offset in the
+ # concatenated string.
+
+ # We want the names of all the interfaces with a prototype (for
+ # implementing @@toStringTag).
+ names = set(
+ d.interface.getClassName()
+ for d in self.getDescriptors(hasInterfaceOrInterfacePrototypeObject=True)
+ )
+
+ # Now also add the names from windowGlobalNames, we need them for the
+ # perfect hash that we build for these.
+ names.update(n[0] for n in self.windowGlobalNames)
+
+ # Sorting is not strictly necessary, but makes the generated code a bit
+ # more readable.
+ names = sorted(names)
+
+ # We can't rely on being able to pass initial=0 to itertools.accumulate
+ # because it was only added in version 3.8, so define an accumulate
+ # function that chains the initial value into the iterator.
+ def accumulate(iterable, initial):
+ return itertools.accumulate(itertools.chain([initial], iterable))
+
+ # Calculate the offset of each name in the concatenated string. Note that
+ # we need to add 1 to the length to account for the null terminating each
+ # name.
+ offsets = accumulate(map(lambda n: len(n) + 1, names), initial=0)
+ self.namesStringOffsets = list(zip(names, offsets))
+
+ def getInterface(self, ifname):
+ return self.interfaces[ifname]
+
+ def getDescriptors(self, **filters):
+ """Gets the descriptors that match the given filters."""
+ curr = self.descriptors
+ # Collect up our filters, because we may have a webIDLFile filter that
+ # we always want to apply first.
+ tofilter = [(lambda x: x.interface.isExternal(), False)]
+ for key, val in six.iteritems(filters):
+ if key == "webIDLFile":
+ # Special-case this part to make it fast, since most of our
+ # getDescriptors calls are conditioned on a webIDLFile. We may
+ # not have this key, in which case we have no descriptors
+ # either.
+ curr = self.descriptorsByFile.get(val, [])
+ continue
+ elif key == "hasInterfaceObject":
+
+ def getter(x):
+ return x.interface.hasInterfaceObject()
+
+ elif key == "hasInterfacePrototypeObject":
+
+ def getter(x):
+ return x.interface.hasInterfacePrototypeObject()
+
+ elif key == "hasInterfaceOrInterfacePrototypeObject":
+
+ def getter(x):
+ return x.hasInterfaceOrInterfacePrototypeObject()
+
+ elif key == "isCallback":
+
+ def getter(x):
+ return x.interface.isCallback()
+
+ elif key == "isJSImplemented":
+
+ def getter(x):
+ return x.interface.isJSImplemented()
+
+ elif key == "isExposedInAnyWorker":
+
+ def getter(x):
+ return x.interface.isExposedInAnyWorker()
+
+ elif key == "isExposedInWorkerDebugger":
+
+ def getter(x):
+ return x.interface.isExposedInWorkerDebugger()
+
+ elif key == "isExposedInAnyWorklet":
+
+ def getter(x):
+ return x.interface.isExposedInAnyWorklet()
+
+ elif key == "isExposedInWindow":
+
+ def getter(x):
+ return x.interface.isExposedInWindow()
+
+ elif key == "isExposedInShadowRealms":
+
+ def getter(x):
+ return x.interface.isExposedInShadowRealms()
+
+ elif key == "isSerializable":
+
+ def getter(x):
+ return x.interface.isSerializable()
+
+ else:
+ # Have to watch out: just closing over "key" is not enough,
+ # since we're about to mutate its value
+ getter = (lambda attrName: lambda x: getattr(x, attrName))(key)
+ tofilter.append((getter, val))
+ for f in tofilter:
+ curr = [x for x in curr if f[0](x) == f[1]]
+ return curr
+
+ def getEnums(self, webIDLFile):
+ return [e for e in self.enums if e.filename() == webIDLFile]
+
+ def getDictionaries(self, webIDLFile):
+ return [d for d in self.dictionaries if d.filename() == webIDLFile]
+
+ def getCallbacks(self, webIDLFile):
+ return [c for c in self.callbacks if c.filename() == webIDLFile]
+
+ def getDescriptor(self, interfaceName):
+ """
+ Gets the appropriate descriptor for the given interface name.
+ """
+ # We may have optimized out this descriptor, but the chances of anyone
+ # asking about it are then slim. Put the check for that _after_ we've
+ # done our normal lookup. But that means we have to do our normal
+ # lookup in a way that will not throw if it fails.
+ d = self.descriptorsByName.get(interfaceName, None)
+ if d:
+ return d
+
+ raise NoSuchDescriptorError("For " + interfaceName + " found no matches")
+
+ def getConfig(self):
+ return self
+
+ def getDictionariesConvertibleToJS(self):
+ return [d for d in self.dictionaries if d.needsConversionToJS]
+
+ def getDictionariesConvertibleFromJS(self):
+ return [d for d in self.dictionaries if d.needsConversionFromJS]
+
+ def getDictionaryIfExists(self, dictionaryName):
+ return self.dictionariesByName.get(dictionaryName, None)
+
+
+class NoSuchDescriptorError(TypeError):
+ def __init__(self, str):
+ TypeError.__init__(self, str)
+
+
+def methodReturnsJSObject(method):
+ assert method.isMethod()
+
+ for signature in method.signatures():
+ returnType = signature[0]
+ if returnType.isObject() or returnType.isSpiderMonkeyInterface():
+ return True
+
+ return False
+
+
+def MemberIsLegacyUnforgeable(member, descriptor):
+ # Note: "or" and "and" return either their LHS or RHS, not
+ # necessarily booleans. Make sure to return a boolean from this
+ # method, because callers will compare its return value to
+ # booleans.
+ return bool(
+ (member.isAttr() or member.isMethod())
+ and not member.isStatic()
+ and (
+ member.isLegacyUnforgeable()
+ or descriptor.interface.getExtendedAttribute("LegacyUnforgeable")
+ )
+ )
+
+
+class Descriptor(DescriptorProvider):
+ """
+ Represents a single descriptor for an interface. See Bindings.conf.
+ """
+
+ def __init__(self, config, interface, desc):
+ DescriptorProvider.__init__(self)
+ self.config = config
+ self.interface = interface
+
+ self.wantsXrays = not interface.isExternal() and interface.isExposedInWindow()
+
+ if self.wantsXrays:
+ # We could try to restrict self.wantsXrayExpandoClass further. For
+ # example, we could set it to false if all of our slots store
+ # Gecko-interface-typed things, because we don't use Xray expando
+ # slots for those. But note that we would need to check the types
+ # of not only the members of "interface" but also of all its
+ # ancestors, because those can have members living in our slots too.
+ # For now, do the simple thing.
+ self.wantsXrayExpandoClass = interface.totalMembersInSlots != 0
+
+ # Read the desc, and fill in the relevant defaults.
+ ifaceName = self.interface.identifier.name
+ # For generated iterator interfaces for other iterable interfaces, we
+ # just use IterableIterator as the native type, templated on the
+ # nativeType of the iterable interface. That way we can have a
+ # templated implementation for all the duplicated iterator
+ # functionality.
+ if self.interface.isIteratorInterface():
+ itrName = self.interface.iterableInterface.identifier.name
+ itrDesc = self.getDescriptor(itrName)
+ nativeTypeDefault = iteratorNativeType(itrDesc)
+ elif self.interface.isAsyncIteratorInterface():
+ itrName = self.interface.asyncIterableInterface.identifier.name
+ itrDesc = self.getDescriptor(itrName)
+ nativeTypeDefault = iteratorNativeType(itrDesc)
+
+ elif self.interface.isExternal():
+ nativeTypeDefault = "nsIDOM" + ifaceName
+ else:
+ nativeTypeDefault = "mozilla::dom::" + ifaceName
+
+ self.nativeType = desc.get("nativeType", nativeTypeDefault)
+ # Now create a version of nativeType that doesn't have extra
+ # mozilla::dom:: at the beginning.
+ prettyNativeType = self.nativeType.split("::")
+ if prettyNativeType[0] == "mozilla":
+ prettyNativeType.pop(0)
+ if prettyNativeType[0] == "dom":
+ prettyNativeType.pop(0)
+ self.prettyNativeType = "::".join(prettyNativeType)
+
+ self.jsImplParent = desc.get("jsImplParent", self.nativeType)
+
+ # Do something sane for JSObject
+ if self.nativeType == "JSObject":
+ headerDefault = "js/TypeDecls.h"
+ elif self.interface.isCallback() or self.interface.isJSImplemented():
+ # A copy of CGHeaders.getDeclarationFilename; we can't
+ # import it here, sadly.
+ # Use our local version of the header, not the exported one, so that
+ # test bindings, which don't export, will work correctly.
+ basename = os.path.basename(self.interface.filename())
+ headerDefault = basename.replace(".webidl", "Binding.h")
+ else:
+ if not self.interface.isExternal() and self.interface.getExtendedAttribute(
+ "HeaderFile"
+ ):
+ headerDefault = self.interface.getExtendedAttribute("HeaderFile")[0]
+ elif (
+ self.interface.isIteratorInterface()
+ or self.interface.isAsyncIteratorInterface()
+ ):
+ headerDefault = "mozilla/dom/IterableIterator.h"
+ else:
+ headerDefault = self.nativeType
+ headerDefault = headerDefault.replace("::", "/") + ".h"
+ self.headerFile = desc.get("headerFile", headerDefault)
+ self.headerIsDefault = self.headerFile == headerDefault
+ if self.jsImplParent == self.nativeType:
+ self.jsImplParentHeader = self.headerFile
+ else:
+ self.jsImplParentHeader = self.jsImplParent.replace("::", "/") + ".h"
+
+ self.notflattened = desc.get("notflattened", False)
+ self.register = desc.get("register", True)
+
+ # If we're concrete, we need to crawl our ancestor interfaces and mark
+ # them as having a concrete descendant.
+ concreteDefault = (
+ not self.interface.isExternal()
+ and not self.interface.isCallback()
+ and not self.interface.isNamespace()
+ and
+ # We're going to assume that leaf interfaces are
+ # concrete; otherwise what's the point? Also
+ # interfaces with constructors had better be
+ # concrete; otherwise how can you construct them?
+ (
+ not self.interface.hasChildInterfaces()
+ or self.interface.ctor() is not None
+ )
+ )
+
+ self.concrete = desc.get("concrete", concreteDefault)
+ self.hasLegacyUnforgeableMembers = self.concrete and any(
+ MemberIsLegacyUnforgeable(m, self) for m in self.interface.members
+ )
+ self.operations = {
+ "IndexedGetter": None,
+ "IndexedSetter": None,
+ "IndexedDeleter": None,
+ "NamedGetter": None,
+ "NamedSetter": None,
+ "NamedDeleter": None,
+ "Stringifier": None,
+ "LegacyCaller": None,
+ }
+
+ self.hasDefaultToJSON = False
+
+ # Stringifiers need to be set up whether an interface is
+ # concrete or not, because they're actually prototype methods and hence
+ # can apply to instances of descendant interfaces. Legacy callers and
+ # named/indexed operations only need to be set up on concrete
+ # interfaces, since they affect the JSClass we end up using, not the
+ # prototype object.
+ def addOperation(operation, m):
+ if not self.operations[operation]:
+ self.operations[operation] = m
+
+ # Since stringifiers go on the prototype, we only need to worry
+ # about our own stringifier, not those of our ancestor interfaces.
+ if not self.interface.isExternal():
+ for m in self.interface.members:
+ if m.isMethod() and m.isStringifier():
+ addOperation("Stringifier", m)
+ if m.isMethod() and m.isDefaultToJSON():
+ self.hasDefaultToJSON = True
+
+ # We keep track of instrumente props for all non-external interfaces.
+ self.instrumentedProps = []
+ instrumentedProps = self.interface.getExtendedAttribute("InstrumentedProps")
+ if instrumentedProps:
+ # It's actually a one-element list, with the list
+ # we want as the only element.
+ self.instrumentedProps = instrumentedProps[0]
+
+ # Check that we don't have duplicated instrumented props.
+ uniqueInstrumentedProps = set(self.instrumentedProps)
+ if len(uniqueInstrumentedProps) != len(self.instrumentedProps):
+ duplicates = [
+ p
+ for p in uniqueInstrumentedProps
+ if self.instrumentedProps.count(p) > 1
+ ]
+ raise TypeError(
+ "Duplicated instrumented properties: %s.\n%s"
+ % (duplicates, self.interface.location)
+ )
+
+ if self.concrete:
+ self.proxy = False
+ iface = self.interface
+ for m in iface.members:
+ # Don't worry about inheriting legacycallers either: in
+ # practice these are on most-derived prototypes.
+ if m.isMethod() and m.isLegacycaller():
+ if not m.isIdentifierLess():
+ raise TypeError(
+ "We don't support legacycaller with "
+ "identifier.\n%s" % m.location
+ )
+ if len(m.signatures()) != 1:
+ raise TypeError(
+ "We don't support overloaded "
+ "legacycaller.\n%s" % m.location
+ )
+ addOperation("LegacyCaller", m)
+
+ while iface:
+ for m in iface.members:
+ if not m.isMethod():
+ continue
+
+ def addIndexedOrNamedOperation(operation, m):
+ if m.isIndexed():
+ operation = "Indexed" + operation
+ else:
+ assert m.isNamed()
+ operation = "Named" + operation
+ addOperation(operation, m)
+
+ if m.isGetter():
+ addIndexedOrNamedOperation("Getter", m)
+ if m.isSetter():
+ addIndexedOrNamedOperation("Setter", m)
+ if m.isDeleter():
+ addIndexedOrNamedOperation("Deleter", m)
+ if m.isLegacycaller() and iface != self.interface:
+ raise TypeError(
+ "We don't support legacycaller on "
+ "non-leaf interface %s.\n%s" % (iface, iface.location)
+ )
+
+ iface.setUserData("hasConcreteDescendant", True)
+ iface = iface.parent
+
+ self.proxy = (
+ self.supportsIndexedProperties()
+ or (
+ self.supportsNamedProperties() and not self.hasNamedPropertiesObject
+ )
+ or self.isMaybeCrossOriginObject()
+ )
+
+ if self.proxy:
+ if self.isMaybeCrossOriginObject() and (
+ self.supportsIndexedProperties() or self.supportsNamedProperties()
+ ):
+ raise TypeError(
+ "We don't support named or indexed "
+ "properties on maybe-cross-origin objects. "
+ "This lets us assume that their proxy "
+ "hooks are never called via Xrays. "
+ "Fix %s.\n%s" % (self.interface, self.interface.location)
+ )
+
+ if not self.operations["IndexedGetter"] and (
+ self.operations["IndexedSetter"]
+ or self.operations["IndexedDeleter"]
+ ):
+ raise SyntaxError(
+ "%s supports indexed properties but does "
+ "not have an indexed getter.\n%s"
+ % (self.interface, self.interface.location)
+ )
+ if not self.operations["NamedGetter"] and (
+ self.operations["NamedSetter"] or self.operations["NamedDeleter"]
+ ):
+ raise SyntaxError(
+ "%s supports named properties but does "
+ "not have a named getter.\n%s"
+ % (self.interface, self.interface.location)
+ )
+ iface = self.interface
+ while iface:
+ iface.setUserData("hasProxyDescendant", True)
+ iface = iface.parent
+
+ if desc.get("wantsQI", None) is not None:
+ self._wantsQI = desc.get("wantsQI", None)
+ self.wrapperCache = (
+ not self.interface.isCallback()
+ and not self.interface.isIteratorInterface()
+ and not self.interface.isAsyncIteratorInterface()
+ and desc.get("wrapperCache", True)
+ )
+
+ self.name = interface.identifier.name
+
+ # self.implicitJSContext is a list of names of methods and attributes
+ # that need a JSContext.
+ if self.interface.isJSImplemented():
+ self.implicitJSContext = ["constructor"]
+ else:
+ self.implicitJSContext = desc.get("implicitJSContext", [])
+ assert isinstance(self.implicitJSContext, list)
+
+ self._binaryNames = {}
+
+ if not self.interface.isExternal():
+
+ def maybeAddBinaryName(member):
+ binaryName = member.getExtendedAttribute("BinaryName")
+ if binaryName:
+ assert isinstance(binaryName, list)
+ assert len(binaryName) == 1
+ self._binaryNames.setdefault(
+ (member.identifier.name, member.isStatic()), binaryName[0]
+ )
+
+ for member in self.interface.members:
+ if not member.isAttr() and not member.isMethod():
+ continue
+ maybeAddBinaryName(member)
+
+ ctor = self.interface.ctor()
+ if ctor:
+ maybeAddBinaryName(ctor)
+
+ # Some default binary names for cases when nothing else got set.
+ self._binaryNames.setdefault(("__legacycaller", False), "LegacyCall")
+ self._binaryNames.setdefault(("__stringifier", False), "Stringify")
+
+ # Build the prototype chain.
+ self.prototypeChain = []
+ self.needsMissingPropUseCounters = False
+ parent = interface
+ while parent:
+ self.needsMissingPropUseCounters = (
+ self.needsMissingPropUseCounters
+ or parent.getExtendedAttribute("InstrumentedProps")
+ )
+ self.prototypeChain.insert(0, parent.identifier.name)
+ parent = parent.parent
+ config.maxProtoChainLength = max(
+ config.maxProtoChainLength, len(self.prototypeChain)
+ )
+
+ self.hasOrdinaryObjectPrototype = desc.get("hasOrdinaryObjectPrototype", False)
+
+ def binaryNameFor(self, name, isStatic):
+ return self._binaryNames.get((name, isStatic), name)
+
+ @property
+ def prototypeNameChain(self):
+ return [self.getDescriptor(p).name for p in self.prototypeChain]
+
+ @property
+ def parentPrototypeName(self):
+ if len(self.prototypeChain) == 1:
+ return None
+ return self.getDescriptor(self.prototypeChain[-2]).name
+
+ def hasInterfaceOrInterfacePrototypeObject(self):
+ return (
+ self.interface.hasInterfaceObject()
+ or self.interface.hasInterfacePrototypeObject()
+ )
+
+ @property
+ def hasNamedPropertiesObject(self):
+ return self.isGlobal() and self.supportsNamedProperties()
+
+ def getExtendedAttributes(self, member, getter=False, setter=False):
+ def ensureValidBoolExtendedAttribute(attr, name):
+ if attr is not None and attr is not True:
+ raise TypeError("Unknown value for '%s': %s" % (name, attr[0]))
+
+ def ensureValidThrowsExtendedAttribute(attr):
+ ensureValidBoolExtendedAttribute(attr, "Throws")
+
+ def ensureValidCanOOMExtendedAttribute(attr):
+ ensureValidBoolExtendedAttribute(attr, "CanOOM")
+
+ def maybeAppendNeedsErrorResultToAttrs(attrs, throws):
+ ensureValidThrowsExtendedAttribute(throws)
+ if throws is not None:
+ attrs.append("needsErrorResult")
+
+ def maybeAppendCanOOMToAttrs(attrs, canOOM):
+ ensureValidCanOOMExtendedAttribute(canOOM)
+ if canOOM is not None:
+ attrs.append("canOOM")
+
+ def maybeAppendNeedsSubjectPrincipalToAttrs(attrs, needsSubjectPrincipal):
+ if (
+ needsSubjectPrincipal is not None
+ and needsSubjectPrincipal is not True
+ and needsSubjectPrincipal != ["NonSystem"]
+ ):
+ raise TypeError(
+ "Unknown value for 'NeedsSubjectPrincipal': %s"
+ % needsSubjectPrincipal[0]
+ )
+
+ if needsSubjectPrincipal is not None:
+ attrs.append("needsSubjectPrincipal")
+ if needsSubjectPrincipal == ["NonSystem"]:
+ attrs.append("needsNonSystemSubjectPrincipal")
+
+ name = member.identifier.name
+ throws = self.interface.isJSImplemented() or member.getExtendedAttribute(
+ "Throws"
+ )
+ canOOM = member.getExtendedAttribute("CanOOM")
+ needsSubjectPrincipal = member.getExtendedAttribute("NeedsSubjectPrincipal")
+ attrs = []
+ if name in self.implicitJSContext:
+ attrs.append("implicitJSContext")
+ if member.isMethod():
+ if self.interface.isAsyncIteratorInterface() and name == "next":
+ attrs.append("implicitJSContext")
+ # JSObject-returning [NewObject] methods must be fallible,
+ # since they have to (fallibly) allocate the new JSObject.
+ if member.getExtendedAttribute("NewObject"):
+ if member.returnsPromise():
+ throws = True
+ elif methodReturnsJSObject(member):
+ canOOM = True
+ maybeAppendNeedsErrorResultToAttrs(attrs, throws)
+ maybeAppendCanOOMToAttrs(attrs, canOOM)
+ maybeAppendNeedsSubjectPrincipalToAttrs(attrs, needsSubjectPrincipal)
+ return attrs
+
+ assert member.isAttr()
+ assert bool(getter) != bool(setter)
+ if throws is None:
+ throwsAttr = "GetterThrows" if getter else "SetterThrows"
+ throws = member.getExtendedAttribute(throwsAttr)
+ maybeAppendNeedsErrorResultToAttrs(attrs, throws)
+ if canOOM is None:
+ canOOMAttr = "GetterCanOOM" if getter else "SetterCanOOM"
+ canOOM = member.getExtendedAttribute(canOOMAttr)
+ maybeAppendCanOOMToAttrs(attrs, canOOM)
+ if needsSubjectPrincipal is None:
+ needsSubjectPrincipalAttr = (
+ "GetterNeedsSubjectPrincipal"
+ if getter
+ else "SetterNeedsSubjectPrincipal"
+ )
+ needsSubjectPrincipal = member.getExtendedAttribute(
+ needsSubjectPrincipalAttr
+ )
+ maybeAppendNeedsSubjectPrincipalToAttrs(attrs, needsSubjectPrincipal)
+ return attrs
+
+ def supportsIndexedProperties(self):
+ return self.operations["IndexedGetter"] is not None
+
+ def lengthNeedsCallerType(self):
+ """
+ Determine whether our length getter needs a caller type; this is needed
+ in some indexed-getter proxy algorithms. The idea is that if our
+ indexed getter needs a caller type, our automatically-generated Length()
+ calls need one too.
+ """
+ assert self.supportsIndexedProperties()
+ indexedGetter = self.operations["IndexedGetter"]
+ return indexedGetter.getExtendedAttribute("NeedsCallerType")
+
+ def supportsNamedProperties(self):
+ return self.operations["NamedGetter"] is not None
+
+ def supportedNamesNeedCallerType(self):
+ """
+ Determine whether our GetSupportedNames call needs a caller type. The
+ idea is that if your named getter needs a caller type, then so does
+ GetSupportedNames.
+ """
+ assert self.supportsNamedProperties()
+ namedGetter = self.operations["NamedGetter"]
+ return namedGetter.getExtendedAttribute("NeedsCallerType")
+
+ def isMaybeCrossOriginObject(self):
+ # If we're isGlobal and have cross-origin members, we're a Window, and
+ # that's not a cross-origin object. The WindowProxy is.
+ return (
+ self.concrete
+ and self.interface.hasCrossOriginMembers
+ and not self.isGlobal()
+ )
+
+ def needsHeaderInclude(self):
+ """
+ An interface doesn't need a header file if it is not concrete, not
+ pref-controlled, has no prototype object, has no static methods or
+ attributes and has no parent. The parent matters because we assert
+ things about refcounting that depend on the actual underlying type if we
+ have a parent.
+
+ """
+ return (
+ self.interface.isExternal()
+ or self.concrete
+ or self.interface.hasInterfacePrototypeObject()
+ or any(
+ (m.isAttr() or m.isMethod()) and m.isStatic()
+ for m in self.interface.members
+ )
+ or self.interface.parent
+ )
+
+ def hasThreadChecks(self):
+ # isExposedConditionally does not necessarily imply thread checks
+ # (since at least [SecureContext] is independent of them), but we're
+ # only used to decide whether to include nsThreadUtils.h, so we don't
+ # worry about that.
+ return (
+ self.isExposedConditionally() and not self.interface.isExposedInWindow()
+ ) or self.interface.isExposedInSomeButNotAllWorkers()
+
+ def hasCEReactions(self):
+ return any(
+ m.getExtendedAttribute("CEReactions") for m in self.interface.members
+ )
+
+ def isExposedConditionally(self):
+ return (
+ self.interface.isExposedConditionally()
+ or self.interface.isExposedInSomeButNotAllWorkers()
+ )
+
+ def needsXrayResolveHooks(self):
+ """
+ Generally, any interface with NeedResolve needs Xray
+ resolveOwnProperty and enumerateOwnProperties hooks. But for
+ the special case of plugin-loading elements, we do NOT want
+ those, because we don't want to instantiate plug-ins simply
+ due to chrome touching them and that's all those hooks do on
+ those elements. So we special-case those here.
+ """
+ return self.interface.getExtendedAttribute(
+ "NeedResolve"
+ ) and self.interface.identifier.name not in [
+ "HTMLObjectElement",
+ "HTMLEmbedElement",
+ ]
+
+ def needsXrayNamedDeleterHook(self):
+ return self.operations["NamedDeleter"] is not None
+
+ def isGlobal(self):
+ """
+ Returns true if this is the primary interface for a global object
+ of some sort.
+ """
+ return self.interface.getExtendedAttribute("Global")
+
+ @property
+ def namedPropertiesEnumerable(self):
+ """
+ Returns whether this interface should have enumerable named properties
+ """
+ assert self.proxy
+ assert self.supportsNamedProperties()
+ iface = self.interface
+ while iface:
+ if iface.getExtendedAttribute("LegacyUnenumerableNamedProperties"):
+ return False
+ iface = iface.parent
+ return True
+
+ @property
+ def registersGlobalNamesOnWindow(self):
+ return (
+ self.interface.hasInterfaceObject()
+ and self.interface.isExposedInWindow()
+ and self.register
+ )
+
+ def getDescriptor(self, interfaceName):
+ """
+ Gets the appropriate descriptor for the given interface name.
+ """
+ return self.config.getDescriptor(interfaceName)
+
+ def getConfig(self):
+ return self.config
+
+
+# Some utility methods
+def getTypesFromDescriptor(descriptor, includeArgs=True, includeReturns=True):
+ """
+ Get argument and/or return types for all members of the descriptor. By
+ default returns all argument types (which includes types of writable
+ attributes) and all return types (which includes types of all attributes).
+ """
+ assert includeArgs or includeReturns # Must want _something_.
+ members = [m for m in descriptor.interface.members]
+ if descriptor.interface.ctor():
+ members.append(descriptor.interface.ctor())
+ members.extend(descriptor.interface.legacyFactoryFunctions)
+ signatures = [s for m in members if m.isMethod() for s in m.signatures()]
+ types = []
+ for s in signatures:
+ assert len(s) == 2
+ (returnType, arguments) = s
+ if includeReturns:
+ types.append(returnType)
+ if includeArgs:
+ types.extend(a.type for a in arguments)
+
+ types.extend(
+ a.type
+ for a in members
+ if (a.isAttr() and (includeReturns or (includeArgs and not a.readonly)))
+ )
+
+ if descriptor.interface.maplikeOrSetlikeOrIterable:
+ maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable
+ if maplikeOrSetlikeOrIterable.isMaplike():
+ # The things we expand into may or may not correctly indicate in
+ # their formal IDL types what things we have as return values. For
+ # example, "keys" returns the moral equivalent of sequence<keyType>
+ # but just claims to return "object". Similarly, "values" returns
+ # the moral equivalent of sequence<valueType> but claims to return
+ # "object". And due to bug 1155340, "get" claims to return "any"
+ # instead of the right type. So let's just manually work around
+ # that lack of specificity. For our arguments, we already enforce
+ # the right types at the IDL level, so those will get picked up
+ # correctly.
+ assert maplikeOrSetlikeOrIterable.hasKeyType()
+ assert maplikeOrSetlikeOrIterable.hasValueType()
+ if includeReturns:
+ types.append(maplikeOrSetlikeOrIterable.keyType)
+ types.append(maplikeOrSetlikeOrIterable.valueType)
+ elif maplikeOrSetlikeOrIterable.isSetlike():
+ assert maplikeOrSetlikeOrIterable.hasKeyType()
+ assert maplikeOrSetlikeOrIterable.hasValueType()
+ assert (
+ maplikeOrSetlikeOrIterable.keyType
+ == maplikeOrSetlikeOrIterable.valueType
+ )
+ # As in the maplike case, we don't always declare our return values
+ # quite correctly.
+ if includeReturns:
+ types.append(maplikeOrSetlikeOrIterable.keyType)
+ else:
+ assert (
+ maplikeOrSetlikeOrIterable.isIterable()
+ or maplikeOrSetlikeOrIterable.isAsyncIterable()
+ )
+ # As in the maplike/setlike cases we don't do a good job of
+ # declaring our actual return types, while our argument types, if
+ # any, are declared fine.
+ if includeReturns:
+ if maplikeOrSetlikeOrIterable.hasKeyType():
+ types.append(maplikeOrSetlikeOrIterable.keyType)
+ if maplikeOrSetlikeOrIterable.hasValueType():
+ types.append(maplikeOrSetlikeOrIterable.valueType)
+
+ return types
+
+
+def getTypesFromDictionary(dictionary):
+ """
+ Get all member types for this dictionary
+ """
+ return [m.type for m in dictionary.members]
+
+
+def getTypesFromCallback(callback):
+ """
+ Get the types this callback depends on: its return type and the
+ types of its arguments.
+ """
+ sig = callback.signatures()[0]
+ types = [sig[0]] # Return type
+ types.extend(arg.type for arg in sig[1]) # Arguments
+ return types
+
+
+def getAllTypes(descriptors, dictionaries, callbacks):
+ """
+ Generate all the types we're dealing with. For each type, a tuple
+ containing type, dictionary is yielded. The dictionary can be None if the
+ type does not come from a dictionary.
+ """
+ for d in descriptors:
+ if d.interface.isExternal():
+ continue
+ for t in getTypesFromDescriptor(d):
+ yield (t, None)
+ for dictionary in dictionaries:
+ for t in getTypesFromDictionary(dictionary):
+ yield (t, dictionary)
+ for callback in callbacks:
+ for t in getTypesFromCallback(callback):
+ yield (t, callback)
+
+
+# For sync value iterators, we use default array implementation, for async
+# iterators and sync pair iterators, we use AsyncIterableIterator or
+# IterableIterator instead.
+def iteratorNativeType(descriptor):
+ assert descriptor.interface.isIterable() or descriptor.interface.isAsyncIterable()
+ iterableDecl = descriptor.interface.maplikeOrSetlikeOrIterable
+ assert iterableDecl.isPairIterator() or descriptor.interface.isAsyncIterable()
+ if descriptor.interface.isIterable():
+ return "mozilla::dom::IterableIterator<%s>" % descriptor.nativeType
+ needReturnMethod = toStringBool(
+ descriptor.interface.maplikeOrSetlikeOrIterable.getExtendedAttribute(
+ "GenerateReturnMethod"
+ )
+ is not None
+ )
+ return "mozilla::dom::binding_detail::AsyncIterableIteratorNative<%s, %s>" % (
+ descriptor.nativeType,
+ needReturnMethod,
+ )
+
+
+def findInnermostType(t):
+ """
+ Find the innermost type of the given type, unwrapping Promise and Record
+ types, as well as everything that unroll() unwraps.
+ """
+ while True:
+ if t.isRecord():
+ t = t.inner
+ elif t.unroll() != t:
+ t = t.unroll()
+ elif t.isPromise():
+ t = t.promiseInnerType()
+ else:
+ return t
+
+
+def getDependentDictionariesFromDictionary(d):
+ """
+ Find all the dictionaries contained in the given dictionary, as ancestors or
+ members. This returns a generator.
+ """
+ while d:
+ yield d
+ for member in d.members:
+ for next in getDictionariesFromType(member.type):
+ yield next
+ d = d.parent
+
+
+def getDictionariesFromType(type):
+ """
+ Find all the dictionaries contained in type. This can be used to find
+ dictionaries that need conversion to JS (by looking at types that get
+ converted to JS) or dictionaries that need conversion from JS (by looking at
+ types that get converted from JS).
+
+ This returns a generator.
+ """
+ type = findInnermostType(type)
+ if type.isUnion():
+ # Look for dictionaries in all the member types
+ for t in type.flatMemberTypes:
+ for next in getDictionariesFromType(t):
+ yield next
+ elif type.isDictionary():
+ # Find the dictionaries that are itself, any of its ancestors, or
+ # contained in any of its member types.
+ for d in getDependentDictionariesFromDictionary(type.inner):
+ yield d
+
+
+def getDictionariesConvertedToJS(descriptors, dictionaries, callbacks):
+ for desc in descriptors:
+ if desc.interface.isExternal():
+ continue
+
+ if desc.interface.isJSImplemented():
+ # For a JS-implemented interface, we need to-JS
+ # conversions for all the types involved.
+ for t in getTypesFromDescriptor(desc):
+ for d in getDictionariesFromType(t):
+ yield d
+ elif desc.interface.isCallback():
+ # For callbacks we only want to include the arguments, since that's
+ # where the to-JS conversion happens.
+ for t in getTypesFromDescriptor(desc, includeReturns=False):
+ for d in getDictionariesFromType(t):
+ yield d
+ else:
+ # For normal interfaces, we only want to include return values,
+ # since that's where to-JS conversion happens.
+ for t in getTypesFromDescriptor(desc, includeArgs=False):
+ for d in getDictionariesFromType(t):
+ yield d
+
+ for callback in callbacks:
+ # We only want to look at the arguments
+ sig = callback.signatures()[0]
+ for arg in sig[1]:
+ for d in getDictionariesFromType(arg.type):
+ yield d
+
+ for dictionary in dictionaries:
+ if dictionary.needsConversionToJS:
+ # It's explicitly flagged as needing to-JS conversion, and all its
+ # dependent dictionaries will need to-JS conversion too.
+ for d in getDependentDictionariesFromDictionary(dictionary):
+ yield d
+
+
+def getDictionariesConvertedFromJS(descriptors, dictionaries, callbacks):
+ for desc in descriptors:
+ if desc.interface.isExternal():
+ continue
+
+ if desc.interface.isJSImplemented():
+ # For a JS-implemented interface, we need from-JS conversions for
+ # all the types involved.
+ for t in getTypesFromDescriptor(desc):
+ for d in getDictionariesFromType(t):
+ yield d
+ elif desc.interface.isCallback():
+ # For callbacks we only want to include the return value, since
+ # that's where teh from-JS conversion happens.
+ for t in getTypesFromDescriptor(desc, includeArgs=False):
+ for d in getDictionariesFromType(t):
+ yield d
+ else:
+ # For normal interfaces, we only want to include arguments values,
+ # since that's where from-JS conversion happens.
+ for t in getTypesFromDescriptor(desc, includeReturns=False):
+ for d in getDictionariesFromType(t):
+ yield d
+
+ for callback in callbacks:
+ # We only want to look at the return value
+ sig = callback.signatures()[0]
+ for d in getDictionariesFromType(sig[0]):
+ yield d
+
+ for dictionary in dictionaries:
+ if dictionary.needsConversionFromJS:
+ # It's explicitly flagged as needing from-JS conversion, and all its
+ # dependent dictionaries will need from-JS conversion too.
+ for d in getDependentDictionariesFromDictionary(dictionary):
+ yield d
diff --git a/dom/bindings/DOMExceptionNames.h b/dom/bindings/DOMExceptionNames.h
new file mode 100644
index 0000000000..4630dd4394
--- /dev/null
+++ b/dom/bindings/DOMExceptionNames.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; 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/. */
+
+// NOTE: No include guard. This is meant to be included to generate different
+// code based on how DOMEXCEPTION is defined, possibly multiple times in a
+// single translation unit.
+
+// XXXbz This list sort of duplicates the DOM4_MSG_DEF bits of domerr.msg,
+// except that has various extra errors that are not in specs
+// (e.g. EncodingError) and has multiple definitions for the same error
+// name using different messages, which we don't need because we get the
+// message passed in. We should try to convert all consumers of the "extra"
+// error codes in there to these APIs, remove the extra bits, and just
+// include domerr.msg here.
+DOMEXCEPTION(IndexSizeError, NS_ERROR_DOM_INDEX_SIZE_ERR)
+// We don't have a DOMStringSizeError and it's deprecated anyway.
+DOMEXCEPTION(HierarchyRequestError, NS_ERROR_DOM_HIERARCHY_REQUEST_ERR)
+DOMEXCEPTION(WrongDocumentError, NS_ERROR_DOM_WRONG_DOCUMENT_ERR)
+DOMEXCEPTION(InvalidCharacterError, NS_ERROR_DOM_INVALID_CHARACTER_ERR)
+// We don't have a NoDataAllowedError and it's deprecated anyway.
+DOMEXCEPTION(NoModificationAllowedError,
+ NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR)
+DOMEXCEPTION(NotFoundError, NS_ERROR_DOM_NOT_FOUND_ERR)
+DOMEXCEPTION(NotSupportedError, NS_ERROR_DOM_NOT_SUPPORTED_ERR)
+DOMEXCEPTION(InUseAttributeError, NS_ERROR_DOM_INUSE_ATTRIBUTE_ERR)
+DOMEXCEPTION(InvalidStateError, NS_ERROR_DOM_INVALID_STATE_ERR)
+DOMEXCEPTION(SyntaxError, NS_ERROR_DOM_SYNTAX_ERR)
+DOMEXCEPTION(InvalidModificationError, NS_ERROR_DOM_INVALID_MODIFICATION_ERR)
+DOMEXCEPTION(NamespaceError, NS_ERROR_DOM_NAMESPACE_ERR)
+DOMEXCEPTION(InvalidAccessError, NS_ERROR_DOM_INVALID_ACCESS_ERR)
+// We don't have a ValidationError and it's deprecated anyway.
+DOMEXCEPTION(TypeMismatchError, NS_ERROR_DOM_TYPE_MISMATCH_ERR)
+DOMEXCEPTION(SecurityError, NS_ERROR_DOM_SECURITY_ERR)
+DOMEXCEPTION(NetworkError, NS_ERROR_DOM_NETWORK_ERR)
+DOMEXCEPTION(AbortError, NS_ERROR_DOM_ABORT_ERR)
+DOMEXCEPTION(URLMismatchError, NS_ERROR_DOM_URL_MISMATCH_ERR)
+DOMEXCEPTION(QuotaExceededError, NS_ERROR_DOM_QUOTA_EXCEEDED_ERR)
+DOMEXCEPTION(TimeoutError, NS_ERROR_DOM_TIMEOUT_ERR)
+DOMEXCEPTION(InvalidNodeTypeError, NS_ERROR_DOM_INVALID_NODE_TYPE_ERR)
+DOMEXCEPTION(DataCloneError, NS_ERROR_DOM_DATA_CLONE_ERR)
+DOMEXCEPTION(EncodingError, NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR)
+DOMEXCEPTION(NotReadableError, NS_ERROR_DOM_FILE_NOT_READABLE_ERR)
+DOMEXCEPTION(UnknownError, NS_ERROR_DOM_UNKNOWN_ERR)
+DOMEXCEPTION(ConstraintError, NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR)
+DOMEXCEPTION(DataError, NS_ERROR_DOM_DATA_ERR)
+DOMEXCEPTION(TransactionInactiveError,
+ NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR)
+DOMEXCEPTION(ReadOnlyError, NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR)
+DOMEXCEPTION(VersionError, NS_ERROR_DOM_INDEXEDDB_VERSION_ERR)
+DOMEXCEPTION(OperationError, NS_ERROR_DOM_OPERATION_ERR)
+DOMEXCEPTION(NotAllowedError, NS_ERROR_DOM_NOT_ALLOWED_ERR)
diff --git a/dom/bindings/DOMJSClass.h b/dom/bindings/DOMJSClass.h
new file mode 100644
index 0000000000..2eaf2bcccd
--- /dev/null
+++ b/dom/bindings/DOMJSClass.h
@@ -0,0 +1,622 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_DOMJSClass_h
+#define mozilla_dom_DOMJSClass_h
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "js/Object.h" // JS::GetClass, JS::GetReservedSlot
+#include "js/Wrapper.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/OriginTrials.h"
+#include "mozilla/Likely.h"
+
+#include "mozilla/dom/PrototypeList.h" // auto-generated
+#include "mozilla/dom/WebIDLPrefs.h" // auto-generated
+
+class nsCycleCollectionParticipant;
+class nsWrapperCache;
+struct JSFunctionSpec;
+struct JSPropertySpec;
+struct JSStructuredCloneReader;
+struct JSStructuredCloneWriter;
+class nsIGlobalObject;
+
+// All DOM globals must have a slot at DOM_PROTOTYPE_SLOT.
+#define DOM_PROTOTYPE_SLOT JSCLASS_GLOBAL_SLOT_COUNT
+
+// Keep this count up to date with any extra global slots added above.
+#define DOM_GLOBAL_SLOTS 1
+
+// We use these flag bits for the new bindings.
+#define JSCLASS_DOM_GLOBAL JSCLASS_USERBIT1
+#define JSCLASS_IS_DOMIFACEANDPROTOJSCLASS JSCLASS_USERBIT2
+
+namespace mozilla::dom {
+
+/**
+ * Returns true if code running in the given JSContext is allowed to access
+ * [SecureContext] API on the given JSObject.
+ *
+ * [SecureContext] API exposure is restricted to use by code in a Secure
+ * Contexts:
+ *
+ * https://w3c.github.io/webappsec-secure-contexts/
+ *
+ * Since we want [SecureContext] exposure to depend on the privileges of the
+ * running code (rather than the privileges of an object's creator), this
+ * function checks to see whether the given JSContext's Realm is flagged
+ * as a Secure Context. That allows us to make sure that system principal code
+ * (which is marked as a Secure Context) can access Secure Context API on an
+ * object in a different realm, regardless of whether the other realm is a
+ * Secure Context or not.
+ *
+ * Checking the JSContext's Realm doesn't work for expanded principal
+ * globals accessing a Secure Context web page though (e.g. those used by frame
+ * scripts). To handle that we fall back to checking whether the JSObject came
+ * from a Secure Context.
+ *
+ * Note: We'd prefer this function to live in BindingUtils.h, but we need to
+ * call it in this header, and BindingUtils.h includes us (i.e. we'd have a
+ * circular dependency between headers if it lived there).
+ */
+inline bool IsSecureContextOrObjectIsFromSecureContext(JSContext* aCx,
+ JSObject* aObj) {
+ MOZ_ASSERT(!js::IsWrapper(aObj));
+ return JS::GetIsSecureContext(js::GetContextRealm(aCx)) ||
+ JS::GetIsSecureContext(js::GetNonCCWObjectRealm(aObj));
+}
+
+typedef bool (*ResolveOwnProperty)(
+ JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> obj,
+ JS::Handle<jsid> id,
+ JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc);
+
+typedef bool (*EnumerateOwnProperties)(JSContext* cx,
+ JS::Handle<JSObject*> wrapper,
+ JS::Handle<JSObject*> obj,
+ JS::MutableHandleVector<jsid> props);
+
+typedef bool (*DeleteNamedProperty)(JSContext* cx,
+ JS::Handle<JSObject*> wrapper,
+ JS::Handle<JSObject*> obj,
+ JS::Handle<jsid> id,
+ JS::ObjectOpResult& opresult);
+
+// Returns true if the given global is of a type whose bit is set in
+// aNonExposedGlobals.
+bool IsNonExposedGlobal(JSContext* aCx, JSObject* aGlobal,
+ uint32_t aNonExposedGlobals);
+
+struct ConstantSpec {
+ const char* name;
+ JS::Value value;
+};
+
+typedef bool (*PropertyEnabled)(JSContext* cx, JSObject* global);
+
+namespace GlobalNames {
+// The names of our possible globals. These are the names of the actual
+// interfaces, not of the global names used to refer to them in IDL [Exposed]
+// annotations.
+static const uint32_t Window = 1u << 0;
+static const uint32_t DedicatedWorkerGlobalScope = 1u << 1;
+static const uint32_t SharedWorkerGlobalScope = 1u << 2;
+static const uint32_t ServiceWorkerGlobalScope = 1u << 3;
+static const uint32_t WorkerDebuggerGlobalScope = 1u << 4;
+static const uint32_t AudioWorkletGlobalScope = 1u << 5;
+static const uint32_t PaintWorkletGlobalScope = 1u << 6;
+static const uint32_t ShadowRealmGlobalScope = 1u << 7;
+
+static constexpr uint32_t kCount = 8;
+} // namespace GlobalNames
+
+struct PrefableDisablers {
+ inline bool isEnabled(JSContext* cx, JS::Handle<JSObject*> obj) const {
+ if (nonExposedGlobals &&
+ IsNonExposedGlobal(cx, JS::GetNonCCWObjectGlobal(obj),
+ nonExposedGlobals)) {
+ return false;
+ }
+ if (prefIndex != WebIDLPrefIndex::NoPref &&
+ !sWebIDLPrefs[uint16_t(prefIndex)]()) {
+ return false;
+ }
+ if (secureContext && !IsSecureContextOrObjectIsFromSecureContext(cx, obj)) {
+ return false;
+ }
+ if (trial != OriginTrial(0) &&
+ !OriginTrials::IsEnabled(cx, JS::GetNonCCWObjectGlobal(obj), trial)) {
+ // TODO(emilio): Perhaps reconsider the interaction between [Trial=""] and
+ // [Pref=""].
+ //
+ // In particular, it might be desirable to only check the trial if there
+ // is no pref or the pref is disabled.
+ return false;
+ }
+ if (enabledFunc && !enabledFunc(cx, JS::GetNonCCWObjectGlobal(obj))) {
+ return false;
+ }
+ return true;
+ }
+
+ // Index into the array of StaticPrefs
+ const WebIDLPrefIndex prefIndex;
+
+ // Bitmask of global names that we should not be exposed in.
+ const uint16_t nonExposedGlobals : GlobalNames::kCount;
+
+ // A boolean indicating whether a Secure Context is required.
+ const uint16_t secureContext : 1;
+
+ // An origin trial controlling the feature. This can be made a bitfield too if
+ // needed.
+ const OriginTrial trial;
+
+ // A function pointer to a function that can say the property is disabled
+ // even if "enabled" is set to true. If the pointer is null the value of
+ // "enabled" is used as-is.
+ const PropertyEnabled enabledFunc;
+};
+
+template <typename T>
+struct Prefable {
+ inline bool isEnabled(JSContext* cx, JS::Handle<JSObject*> obj) const {
+ MOZ_ASSERT(!js::IsWrapper(obj));
+ if (MOZ_LIKELY(!disablers)) {
+ return true;
+ }
+ return disablers->isEnabled(cx, obj);
+ }
+
+ // Things that can disable this set of specs. |nullptr| means "cannot be
+ // disabled".
+ const PrefableDisablers* const disablers;
+
+ // Array of specs, terminated in whatever way is customary for T.
+ // Null to indicate a end-of-array for Prefable, when such an
+ // indicator is needed.
+ const T* const specs;
+};
+
+enum PropertyType {
+ eStaticMethod,
+ eStaticAttribute,
+ eMethod,
+ eAttribute,
+ eUnforgeableMethod,
+ eUnforgeableAttribute,
+ eConstant,
+ ePropertyTypeCount
+};
+
+#define NUM_BITS_PROPERTY_INFO_TYPE 3
+#define NUM_BITS_PROPERTY_INFO_PREF_INDEX 13
+#define NUM_BITS_PROPERTY_INFO_SPEC_INDEX 16
+
+struct PropertyInfo {
+ private:
+ // MSVC generates static initializers if we store a jsid here, even if
+ // PropertyInfo has a constexpr constructor. See bug 1460341 and bug 1464036.
+ uintptr_t mIdBits;
+
+ public:
+ // One of PropertyType, will be used for accessing the corresponding Duo in
+ // NativePropertiesN.duos[].
+ uint32_t type : NUM_BITS_PROPERTY_INFO_TYPE;
+ // The index to the corresponding Preable in Duo.mPrefables[].
+ uint32_t prefIndex : NUM_BITS_PROPERTY_INFO_PREF_INDEX;
+ // The index to the corresponding spec in Duo.mPrefables[prefIndex].specs[].
+ uint32_t specIndex : NUM_BITS_PROPERTY_INFO_SPEC_INDEX;
+
+ void SetId(jsid aId) {
+ static_assert(sizeof(jsid) == sizeof(mIdBits),
+ "jsid should fit in mIdBits");
+ mIdBits = aId.asRawBits();
+ }
+ MOZ_ALWAYS_INLINE jsid Id() const { return jsid::fromRawBits(mIdBits); }
+
+ bool IsStaticMethod() const { return type == eStaticMethod; }
+
+ static int Compare(const PropertyInfo& aInfo1, const PropertyInfo& aInfo2) {
+ // IdToIndexComparator needs to be updated if the order here is changed!
+ if (MOZ_UNLIKELY(aInfo1.mIdBits == aInfo2.mIdBits)) {
+ MOZ_ASSERT((aInfo1.type == eMethod || aInfo1.type == eStaticMethod) &&
+ (aInfo2.type == eMethod || aInfo2.type == eStaticMethod));
+
+ bool isStatic1 = aInfo1.IsStaticMethod();
+
+ MOZ_ASSERT(isStatic1 != aInfo2.IsStaticMethod(),
+ "We shouldn't have 2 static methods with the same name!");
+
+ return isStatic1 ? -1 : 1;
+ }
+
+ return aInfo1.mIdBits < aInfo2.mIdBits ? -1 : 1;
+ }
+};
+
+static_assert(
+ ePropertyTypeCount <= 1ull << NUM_BITS_PROPERTY_INFO_TYPE,
+ "We have property type count that is > (1 << NUM_BITS_PROPERTY_INFO_TYPE)");
+
+// Conceptually, NativeProperties has seven (Prefable<T>*, PropertyInfo*) duos
+// (where T is one of JSFunctionSpec, JSPropertySpec, or ConstantSpec), one for
+// each of: static methods and attributes, methods and attributes, unforgeable
+// methods and attributes, and constants.
+//
+// That's 14 pointers, but in most instances most of the duos are all null, and
+// there are many instances. To save space we use a variable-length type,
+// NativePropertiesN<N>, to hold the data and getters to access it. It has N
+// actual duos (stored in duos[]), plus four bits for each of the 7 possible
+// duos: 1 bit that states if that duo is present, and 3 that state that duo's
+// offset (if present) in duos[].
+//
+// All duo accesses should be done via the getters, which contain assertions
+// that check we don't overrun the end of the struct. (The duo data members are
+// public only so they can be statically initialized.) These assertions should
+// never fail so long as (a) accesses to the variable-length part are guarded by
+// appropriate Has*() calls, and (b) all instances are well-formed, i.e. the
+// value of N matches the number of mHas* members that are true.
+//
+// We store all the property ids a NativePropertiesN owns in a single array of
+// PropertyInfo structs. Each struct contains an id and the information needed
+// to find the corresponding Prefable for the enabled check, as well as the
+// information needed to find the correct property descriptor in the
+// Prefable. We also store an array of indices into the PropertyInfo array,
+// sorted by bits of the corresponding jsid. Given a jsid, this allows us to
+// binary search for the index of the corresponding PropertyInfo, if any.
+//
+// Finally, we define a typedef of NativePropertiesN<7>, NativeProperties, which
+// we use as a "base" type used to refer to all instances of NativePropertiesN.
+// (7 is used because that's the maximum valid parameter, though any other
+// value 1..6 could also be used.) This is reasonable because of the
+// aforementioned assertions in the getters. Upcast() is used to convert
+// specific instances to this "base" type.
+//
+// An example
+// ----------
+// NativeProperties points to various things, and it can be hard to keep track.
+// The following example shows the layout.
+//
+// Imagine an example interface, with:
+// - 10 properties
+// - 6 methods, 3 with no disablers struct, 2 sharing the same disablers
+// struct, 1 using a different disablers struct
+// - 4 attributes, all with no disablers
+// - The property order is such that those using the same disablers structs are
+// together. (This is not guaranteed, but it makes the example simpler.)
+//
+// Each PropertyInfo also contain indices into sMethods/sMethods_specs (for
+// method infos) and sAttributes/sAttributes_specs (for attributes), which let
+// them find their spec, but these are not shown.
+//
+// sNativeProperties sNativeProperties_ sNativeProperties_
+// ---- sortedPropertyIndices[10] propertyInfos[10]
+// - <several scalar fields> ---- ----
+// - sortedPropertyIndices ----> <10 indices> +--> 0 info (method)
+// - duos[2] ---- | 1 info (method)
+// ----(methods) | 2 info (method)
+// 0 - mPrefables -------> points to sMethods below | 3 info (method)
+// - mPropertyInfos ------------------------------+ 4 info (method)
+// 1 - mPrefables -------> points to sAttributes below 5 info (method)
+// - mPropertyInfos ---------------------------------> 6 info (attr)
+// ---- 7 info (attr)
+// ---- 8 info (attr)
+// 9 info (attr)
+// ----
+//
+// sMethods has three entries (excluding the terminator) because there are
+// three disablers structs. The {nullptr,nullptr} serves as the terminator.
+// There are also END terminators within sMethod_specs; the need for these
+// terminators (as opposed to a length) is deeply embedded in SpiderMonkey.
+// Disablers structs are suffixed with the index of the first spec they cover.
+//
+// sMethods sMethods_specs
+// ---- ----
+// 0 - nullptr +----> 0 spec
+// - specs ----------------------+ 1 spec
+// 1 - disablers ---> disablers4 2 spec
+// - specs ------------------------+ 3 END
+// 2 - disablers ---> disablers7 +--> 4 spec
+// - specs ----------------------+ 5 spec
+// 3 - nullptr | 6 END
+// - nullptr +----> 7 spec
+// ---- 8 END
+//
+// sAttributes has a single entry (excluding the terminator) because all of the
+// specs lack disablers.
+//
+// sAttributes sAttributes_specs
+// ---- ----
+// 0 - nullptr +----> 0 spec
+// - specs ----------------------+ 1 spec
+// 1 - nullptr 2 spec
+// - nullptr 3 spec
+// ---- 4 END
+// ----
+template <int N>
+struct NativePropertiesN {
+ // Duo structs are stored in the duos[] array, and each element in the array
+ // could require a different T. Therefore, we can't use the correct type for
+ // mPrefables. Instead we use void* and cast to the correct type in the
+ // getters.
+ struct Duo {
+ const /*Prefable<const T>*/ void* const mPrefables;
+ PropertyInfo* const mPropertyInfos;
+ };
+
+ constexpr const NativePropertiesN<7>* Upcast() const {
+ return reinterpret_cast<const NativePropertiesN<7>*>(this);
+ }
+
+ const PropertyInfo* PropertyInfos() const { return duos[0].mPropertyInfos; }
+
+#define DO(SpecT, FieldName) \
+ public: \
+ /* The bitfields indicating the duo's presence and (if present) offset. */ \
+ const uint32_t mHas##FieldName##s : 1; \
+ const uint32_t m##FieldName##sOffset : 3; \
+ \
+ private: \
+ const Duo* FieldName##sDuo() const { \
+ MOZ_ASSERT(Has##FieldName##s()); \
+ return &duos[m##FieldName##sOffset]; \
+ } \
+ \
+ public: \
+ bool Has##FieldName##s() const { return mHas##FieldName##s; } \
+ const Prefable<const SpecT>* FieldName##s() const { \
+ return static_cast<const Prefable<const SpecT>*>( \
+ FieldName##sDuo()->mPrefables); \
+ } \
+ PropertyInfo* FieldName##PropertyInfos() const { \
+ return FieldName##sDuo()->mPropertyInfos; \
+ }
+
+ DO(JSFunctionSpec, StaticMethod)
+ DO(JSPropertySpec, StaticAttribute)
+ DO(JSFunctionSpec, Method)
+ DO(JSPropertySpec, Attribute)
+ DO(JSFunctionSpec, UnforgeableMethod)
+ DO(JSPropertySpec, UnforgeableAttribute)
+ DO(ConstantSpec, Constant)
+
+#undef DO
+
+ // The index to the iterator method in MethodPropertyInfos() array.
+ const int16_t iteratorAliasMethodIndex;
+ // The number of PropertyInfo structs that the duos manage. This is the total
+ // count across all duos.
+ const uint16_t propertyInfoCount;
+ // The sorted indices array from sorting property ids, which will be used when
+ // we binary search for a property.
+ uint16_t* sortedPropertyIndices;
+
+ const Duo duos[N];
+};
+
+// Ensure the struct has the expected size. The 8 is for the bitfields plus
+// iteratorAliasMethodIndex and idsLength; the rest is for the idsSortedIndex,
+// and duos[].
+static_assert(sizeof(NativePropertiesN<1>) == 8 + 3 * sizeof(void*), "1 size");
+static_assert(sizeof(NativePropertiesN<2>) == 8 + 5 * sizeof(void*), "2 size");
+static_assert(sizeof(NativePropertiesN<3>) == 8 + 7 * sizeof(void*), "3 size");
+static_assert(sizeof(NativePropertiesN<4>) == 8 + 9 * sizeof(void*), "4 size");
+static_assert(sizeof(NativePropertiesN<5>) == 8 + 11 * sizeof(void*), "5 size");
+static_assert(sizeof(NativePropertiesN<6>) == 8 + 13 * sizeof(void*), "6 size");
+static_assert(sizeof(NativePropertiesN<7>) == 8 + 15 * sizeof(void*), "7 size");
+
+// The "base" type.
+typedef NativePropertiesN<7> NativeProperties;
+
+struct NativePropertiesHolder {
+ const NativeProperties* regular;
+ const NativeProperties* chromeOnly;
+ // Points to a static bool that's set to true once the regular and chromeOnly
+ // NativeProperties have been inited. This is a pointer to a bool instead of
+ // a bool value because NativePropertiesHolder is stored by value in
+ // a static const NativePropertyHooks.
+ bool* inited;
+};
+
+// Helper structure for Xrays for DOM binding objects. The same instance is used
+// for instances, interface objects and interface prototype objects of a
+// specific interface.
+struct NativePropertyHooks {
+ // The hook to call for resolving indexed or named properties. May be null if
+ // there can't be any.
+ ResolveOwnProperty mResolveOwnProperty;
+ // The hook to call for enumerating indexed or named properties. May be null
+ // if there can't be any.
+ EnumerateOwnProperties mEnumerateOwnProperties;
+ // The hook to call to delete a named property. May be null if there are no
+ // named properties or no named property deleter. On success (true return)
+ // the "found" argument will be set to true if there was in fact such a named
+ // property and false otherwise. If it's set to false, the caller is expected
+ // to proceed with whatever deletion behavior it would have if there were no
+ // named properties involved at all (i.e. if the hook were null). If it's set
+ // to true, it will indicate via opresult whether the delete actually
+ // succeeded.
+ DeleteNamedProperty mDeleteNamedProperty;
+
+ // The property arrays for this interface.
+ NativePropertiesHolder mNativeProperties;
+
+ // This will be set to the ID of the interface prototype object for the
+ // interface, if it has one. If it doesn't have one it will be set to
+ // prototypes::id::_ID_Count.
+ prototypes::ID mPrototypeID;
+
+ // This will be set to the ID of the interface object for the interface, if it
+ // has one. If it doesn't have one it will be set to
+ // constructors::id::_ID_Count.
+ constructors::ID mConstructorID;
+
+ // The JSClass to use for expandos on our Xrays. Can be null, in which case
+ // Xrays will use a default class of their choice.
+ const JSClass* mXrayExpandoClass;
+};
+
+enum DOMObjectType : uint8_t {
+ eInstance,
+ eGlobalInstance,
+ eInterface,
+ eInterfacePrototype,
+ eGlobalInterfacePrototype,
+ eNamespace,
+ eNamedPropertiesObject
+};
+
+inline bool IsInstance(DOMObjectType type) {
+ return type == eInstance || type == eGlobalInstance;
+}
+
+inline bool IsInterfacePrototype(DOMObjectType type) {
+ return type == eInterfacePrototype || type == eGlobalInterfacePrototype;
+}
+
+typedef JSObject* (*AssociatedGlobalGetter)(JSContext* aCx,
+ JS::Handle<JSObject*> aObj);
+
+typedef JSObject* (*ProtoGetter)(JSContext* aCx);
+
+/**
+ * Returns a handle to the relevant WebIDL prototype object for the current
+ * compartment global (which may be a handle to null on out of memory). Once
+ * allocated, the prototype object is guaranteed to exist as long as the global
+ * does, since the global traces its array of WebIDL prototypes and
+ * constructors.
+ */
+typedef JS::Handle<JSObject*> (*ProtoHandleGetter)(JSContext* aCx);
+
+/**
+ * Serializes a WebIDL object for structured cloning. aObj may not be in the
+ * compartment of aCx in cases when we were working with a cross-compartment
+ * wrapper. aObj is expected to be an object of the DOMJSClass that we got the
+ * serializer from.
+ */
+typedef bool (*WebIDLSerializer)(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter,
+ JS::Handle<JSObject*> aObj);
+
+/**
+ * Deserializes a WebIDL object from a structured clone serialization.
+ */
+typedef JSObject* (*WebIDLDeserializer)(JSContext* aCx,
+ nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader);
+
+typedef nsWrapperCache* (*WrapperCacheGetter)(JS::Handle<JSObject*> aObj);
+
+// Special JSClass for reflected DOM objects.
+struct DOMJSClass {
+ // It would be nice to just inherit from JSClass, but that precludes pure
+ // compile-time initialization of the form |DOMJSClass = {...};|, since C++
+ // only allows brace initialization for aggregate/POD types.
+ const JSClass mBase;
+
+ // A list of interfaces that this object implements, in order of decreasing
+ // derivedness.
+ const prototypes::ID mInterfaceChain[MAX_PROTOTYPE_CHAIN_LENGTH];
+
+ // We store the DOM object in reserved slot with index DOM_OBJECT_SLOT or in
+ // the proxy private if we use a proxy object.
+ // Sometimes it's an nsISupports and sometimes it's not; this class tells
+ // us which it is.
+ const bool mDOMObjectIsISupports;
+
+ const NativePropertyHooks* mNativeHooks;
+
+ // A callback to find the associated global for our C++ object. Note that
+ // this is used in cases when that global is _changing_, so it will not match
+ // the global of the JSObject* passed in to this function!
+ AssociatedGlobalGetter mGetAssociatedGlobal;
+ ProtoHandleGetter mGetProto;
+
+ // This stores the CC participant for the native, null if this class does not
+ // implement cycle collection or if it inherits from nsISupports (we can get
+ // the CC participant by QI'ing in that case).
+ nsCycleCollectionParticipant* mParticipant;
+
+ // The serializer for this class if the relevant object is [Serializable].
+ // Null otherwise.
+ WebIDLSerializer mSerializer;
+
+ // A callback to get the wrapper cache for C++ objects that don't inherit from
+ // nsISupports, or null.
+ WrapperCacheGetter mWrapperCacheGetter;
+
+ static const DOMJSClass* FromJSClass(const JSClass* base) {
+ MOZ_ASSERT(base->flags & JSCLASS_IS_DOMJSCLASS);
+ return reinterpret_cast<const DOMJSClass*>(base);
+ }
+
+ const JSClass* ToJSClass() const { return &mBase; }
+};
+
+// Special JSClass for DOM interface and interface prototype objects.
+struct DOMIfaceAndProtoJSClass {
+ // It would be nice to just inherit from JSClass, but that precludes pure
+ // compile-time initialization of the form
+ // |DOMJSInterfaceAndPrototypeClass = {...};|, since C++ only allows brace
+ // initialization for aggregate/POD types.
+ const JSClass mBase;
+
+ // Either eInterface, eNamespace, eInterfacePrototype,
+ // eGlobalInterfacePrototype or eNamedPropertiesObject.
+ DOMObjectType mType; // uint8_t
+
+ // Boolean indicating whether this object wants a @@hasInstance property
+ // pointing to InterfaceHasInstance defined on it. Only ever true for the
+ // eInterface case.
+ bool wantsInterfaceHasInstance;
+
+ const prototypes::ID mPrototypeID; // uint16_t
+ const uint32_t mDepth;
+
+ const NativePropertyHooks* mNativeHooks;
+
+ // The value to return for Function.prototype.toString on this interface
+ // object.
+ const char* mFunToString;
+
+ ProtoGetter mGetParentProto;
+
+ static const DOMIfaceAndProtoJSClass* FromJSClass(const JSClass* base) {
+ MOZ_ASSERT(base->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS);
+ return reinterpret_cast<const DOMIfaceAndProtoJSClass*>(base);
+ }
+
+ const JSClass* ToJSClass() const { return &mBase; }
+};
+
+class ProtoAndIfaceCache;
+
+inline bool DOMGlobalHasProtoAndIFaceCache(JSObject* global) {
+ MOZ_DIAGNOSTIC_ASSERT(JS::GetClass(global)->flags & JSCLASS_DOM_GLOBAL);
+ // This can be undefined if we GC while creating the global
+ return !JS::GetReservedSlot(global, DOM_PROTOTYPE_SLOT).isUndefined();
+}
+
+inline bool HasProtoAndIfaceCache(JSObject* global) {
+ if (!(JS::GetClass(global)->flags & JSCLASS_DOM_GLOBAL)) {
+ return false;
+ }
+ return DOMGlobalHasProtoAndIFaceCache(global);
+}
+
+inline ProtoAndIfaceCache* GetProtoAndIfaceCache(JSObject* global) {
+ MOZ_DIAGNOSTIC_ASSERT(JS::GetClass(global)->flags & JSCLASS_DOM_GLOBAL);
+ return static_cast<ProtoAndIfaceCache*>(
+ JS::GetReservedSlot(global, DOM_PROTOTYPE_SLOT).toPrivate());
+}
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_DOMJSClass_h */
diff --git a/dom/bindings/DOMJSProxyHandler.cpp b/dom/bindings/DOMJSProxyHandler.cpp
new file mode 100644
index 0000000000..a0f93afcb9
--- /dev/null
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -0,0 +1,331 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/DOMJSProxyHandler.h"
+#include "xpcpublic.h"
+#include "xpcprivate.h"
+#include "XPCWrapper.h"
+#include "WrapperFactory.h"
+#include "nsWrapperCacheInlines.h"
+#include "mozilla/dom/BindingUtils.h"
+
+#include "jsapi.h"
+#include "js/friend/DOMProxy.h" // JS::DOMProxyShadowsResult, JS::ExpandoAndGeneration, JS::SetDOMProxyInformation
+#include "js/PropertyAndElement.h" // JS_AlreadyHasOwnPropertyById, JS_DefineProperty, JS_DefinePropertyById, JS_DeleteProperty, JS_DeletePropertyById
+#include "js/Object.h" // JS::GetCompartment
+
+using namespace JS;
+
+namespace mozilla::dom {
+
+jsid s_length_id = JS::PropertyKey::Void();
+
+bool DefineStaticJSVals(JSContext* cx) {
+ return AtomizeAndPinJSString(cx, s_length_id, "length");
+}
+
+const char DOMProxyHandler::family = 0;
+
+JS::DOMProxyShadowsResult DOMProxyShadows(JSContext* cx,
+ JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id) {
+ using DOMProxyShadowsResult = JS::DOMProxyShadowsResult;
+
+ JS::Rooted<JSObject*> expando(cx, DOMProxyHandler::GetExpandoObject(proxy));
+ JS::Value v = js::GetProxyPrivate(proxy);
+ bool isOverrideBuiltins = !v.isObject() && !v.isUndefined();
+ if (expando) {
+ bool hasOwn;
+ if (!JS_AlreadyHasOwnPropertyById(cx, expando, id, &hasOwn))
+ return DOMProxyShadowsResult::ShadowCheckFailed;
+
+ if (hasOwn) {
+ return isOverrideBuiltins
+ ? DOMProxyShadowsResult::ShadowsViaIndirectExpando
+ : DOMProxyShadowsResult::ShadowsViaDirectExpando;
+ }
+ }
+
+ if (!isOverrideBuiltins) {
+ // Our expando, if any, didn't shadow, so we're not shadowing at all.
+ return DOMProxyShadowsResult::DoesntShadow;
+ }
+
+ bool hasOwn;
+ if (!GetProxyHandler(proxy)->hasOwn(cx, proxy, id, &hasOwn))
+ return DOMProxyShadowsResult::ShadowCheckFailed;
+
+ return hasOwn ? DOMProxyShadowsResult::Shadows
+ : DOMProxyShadowsResult::DoesntShadowUnique;
+}
+
+// Store the information for the specialized ICs.
+struct SetDOMProxyInformation {
+ SetDOMProxyInformation() {
+ JS::SetDOMProxyInformation((const void*)&DOMProxyHandler::family,
+ DOMProxyShadows,
+ &RemoteObjectProxyBase::sCrossOriginProxyFamily);
+ }
+};
+
+SetDOMProxyInformation gSetDOMProxyInformation;
+
+static inline void CheckExpandoObject(JSObject* proxy,
+ const JS::Value& expando) {
+#ifdef DEBUG
+ JSObject* obj = &expando.toObject();
+ MOZ_ASSERT(!js::gc::EdgeNeedsSweepUnbarriered(&obj));
+ MOZ_ASSERT(JS::GetCompartment(proxy) == JS::GetCompartment(obj));
+
+ // When we create an expando object in EnsureExpandoObject below, we preserve
+ // the wrapper. The wrapper is released when the object is unlinked, but we
+ // should never call these functions after that point.
+ nsISupports* native = UnwrapDOMObject<nsISupports>(proxy);
+ nsWrapperCache* cache;
+ // QueryInterface to nsWrapperCache will not GC.
+ JS::AutoSuppressGCAnalysis suppress;
+ CallQueryInterface(native, &cache);
+ MOZ_ASSERT(cache->PreservingWrapper());
+#endif
+}
+
+static inline void CheckExpandoAndGeneration(
+ JSObject* proxy, JS::ExpandoAndGeneration* expandoAndGeneration) {
+#ifdef DEBUG
+ JS::Value value = expandoAndGeneration->expando;
+ if (!value.isUndefined()) CheckExpandoObject(proxy, value);
+#endif
+}
+
+static inline void CheckDOMProxy(JSObject* proxy) {
+#ifdef DEBUG
+ MOZ_ASSERT(IsDOMProxy(proxy), "expected a DOM proxy object");
+ MOZ_ASSERT(!js::gc::EdgeNeedsSweepUnbarriered(&proxy));
+ nsISupports* native = UnwrapDOMObject<nsISupports>(proxy);
+ nsWrapperCache* cache;
+ // QI to nsWrapperCache cannot GC for very non-obvious reasons; see
+ // https://searchfox.org/mozilla-central/rev/55da592d85c2baf8d8818010c41d9738c97013d2/js/xpconnect/src/XPCWrappedJSClass.cpp#521,545-548
+ JS::AutoSuppressGCAnalysis nogc;
+ CallQueryInterface(native, &cache);
+ MOZ_ASSERT(cache->GetWrapperPreserveColor() == proxy);
+#endif
+}
+
+// static
+JSObject* DOMProxyHandler::GetAndClearExpandoObject(JSObject* obj) {
+ CheckDOMProxy(obj);
+
+ JS::Value v = js::GetProxyPrivate(obj);
+ if (v.isUndefined()) {
+ return nullptr;
+ }
+
+ if (v.isObject()) {
+ js::SetProxyPrivate(obj, UndefinedValue());
+ } else {
+ auto* expandoAndGeneration =
+ static_cast<JS::ExpandoAndGeneration*>(v.toPrivate());
+ v = expandoAndGeneration->expando;
+ if (v.isUndefined()) {
+ return nullptr;
+ }
+ expandoAndGeneration->expando = UndefinedValue();
+ }
+
+ CheckExpandoObject(obj, v);
+
+ return &v.toObject();
+}
+
+// static
+JSObject* DOMProxyHandler::EnsureExpandoObject(JSContext* cx,
+ JS::Handle<JSObject*> obj) {
+ CheckDOMProxy(obj);
+
+ JS::Value v = js::GetProxyPrivate(obj);
+ if (v.isObject()) {
+ CheckExpandoObject(obj, v);
+ return &v.toObject();
+ }
+
+ JS::ExpandoAndGeneration* expandoAndGeneration = nullptr;
+ if (!v.isUndefined()) {
+ expandoAndGeneration =
+ static_cast<JS::ExpandoAndGeneration*>(v.toPrivate());
+ CheckExpandoAndGeneration(obj, expandoAndGeneration);
+ if (expandoAndGeneration->expando.isObject()) {
+ return &expandoAndGeneration->expando.toObject();
+ }
+ }
+
+ JS::Rooted<JSObject*> expando(
+ cx, JS_NewObjectWithGivenProto(cx, nullptr, nullptr));
+ if (!expando) {
+ return nullptr;
+ }
+
+ nsISupports* native = UnwrapDOMObject<nsISupports>(obj);
+ nsWrapperCache* cache;
+ CallQueryInterface(native, &cache);
+ cache->PreserveWrapper(native);
+
+ if (expandoAndGeneration) {
+ expandoAndGeneration->expando.setObject(*expando);
+ return expando;
+ }
+
+ js::SetProxyPrivate(obj, ObjectValue(*expando));
+
+ return expando;
+}
+
+bool DOMProxyHandler::preventExtensions(JSContext* cx,
+ JS::Handle<JSObject*> proxy,
+ JS::ObjectOpResult& result) const {
+ // always extensible per WebIDL
+ return result.failCantPreventExtensions();
+}
+
+bool DOMProxyHandler::isExtensible(JSContext* cx, JS::Handle<JSObject*> proxy,
+ bool* extensible) const {
+ *extensible = true;
+ return true;
+}
+
+bool BaseDOMProxyHandler::getOwnPropertyDescriptor(
+ JSContext* cx, Handle<JSObject*> proxy, Handle<jsid> id,
+ MutableHandle<Maybe<PropertyDescriptor>> desc) const {
+ return getOwnPropDescriptor(cx, proxy, id, /* ignoreNamedProps = */ false,
+ desc);
+}
+
+bool DOMProxyHandler::defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ Handle<PropertyDescriptor> desc,
+ JS::ObjectOpResult& result,
+ bool* done) const {
+ if (xpc::WrapperFactory::IsXrayWrapper(proxy)) {
+ return result.succeed();
+ }
+
+ JS::Rooted<JSObject*> expando(cx, EnsureExpandoObject(cx, proxy));
+ if (!expando) {
+ return false;
+ }
+
+ if (!JS_DefinePropertyById(cx, expando, id, desc, result)) {
+ return false;
+ }
+ *done = true;
+ return true;
+}
+
+bool DOMProxyHandler::set(JSContext* cx, Handle<JSObject*> proxy,
+ Handle<jsid> id, Handle<JS::Value> v,
+ Handle<JS::Value> receiver,
+ ObjectOpResult& result) const {
+ MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
+ "Should not have a XrayWrapper here");
+ bool done;
+ if (!setCustom(cx, proxy, id, v, &done)) {
+ return false;
+ }
+ if (done) {
+ return result.succeed();
+ }
+
+ // Make sure to ignore our named properties when checking for own
+ // property descriptors for a set.
+ Rooted<Maybe<PropertyDescriptor>> ownDesc(cx);
+ if (!getOwnPropDescriptor(cx, proxy, id, /* ignoreNamedProps = */ true,
+ &ownDesc)) {
+ return false;
+ }
+
+ return js::SetPropertyIgnoringNamedGetter(cx, proxy, id, v, receiver, ownDesc,
+ result);
+}
+
+bool DOMProxyHandler::delete_(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::ObjectOpResult& result) const {
+ JS::Rooted<JSObject*> expando(cx);
+ if (!xpc::WrapperFactory::IsXrayWrapper(proxy) &&
+ (expando = GetExpandoObject(proxy))) {
+ return JS_DeletePropertyById(cx, expando, id, result);
+ }
+
+ return result.succeed();
+}
+
+bool BaseDOMProxyHandler::ownPropertyKeys(
+ JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandleVector<jsid> props) const {
+ return ownPropNames(cx, proxy,
+ JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props);
+}
+
+bool BaseDOMProxyHandler::getPrototypeIfOrdinary(
+ JSContext* cx, JS::Handle<JSObject*> proxy, bool* isOrdinary,
+ JS::MutableHandle<JSObject*> proto) const {
+ *isOrdinary = true;
+ proto.set(GetStaticPrototype(proxy));
+ return true;
+}
+
+bool BaseDOMProxyHandler::getOwnEnumerablePropertyKeys(
+ JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandleVector<jsid> props) const {
+ return ownPropNames(cx, proxy, JSITER_OWNONLY, props);
+}
+
+bool DOMProxyHandler::setCustom(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id, JS::Handle<JS::Value> v,
+ bool* done) const {
+ *done = false;
+ return true;
+}
+
+// static
+JSObject* DOMProxyHandler::GetExpandoObject(JSObject* obj) {
+ CheckDOMProxy(obj);
+
+ JS::Value v = js::GetProxyPrivate(obj);
+ if (v.isObject()) {
+ CheckExpandoObject(obj, v);
+ return &v.toObject();
+ }
+
+ if (v.isUndefined()) {
+ return nullptr;
+ }
+
+ auto* expandoAndGeneration =
+ static_cast<JS::ExpandoAndGeneration*>(v.toPrivate());
+ CheckExpandoAndGeneration(obj, expandoAndGeneration);
+
+ v = expandoAndGeneration->expando;
+ return v.isUndefined() ? nullptr : &v.toObject();
+}
+
+void ShadowingDOMProxyHandler::trace(JSTracer* trc, JSObject* proxy) const {
+ DOMProxyHandler::trace(trc, proxy);
+
+ MOZ_ASSERT(IsDOMProxy(proxy), "expected a DOM proxy object");
+ JS::Value v = js::GetProxyPrivate(proxy);
+ MOZ_ASSERT(!v.isObject(), "Should not have expando object directly!");
+
+ // The proxy's private slot is set when we allocate the proxy,
+ // so it cannot be |undefined|.
+ MOZ_ASSERT(!v.isUndefined());
+
+ auto* expandoAndGeneration =
+ static_cast<JS::ExpandoAndGeneration*>(v.toPrivate());
+ JS::TraceEdge(trc, &expandoAndGeneration->expando,
+ "Shadowing DOM proxy expando");
+}
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/DOMJSProxyHandler.h b/dom/bindings/DOMJSProxyHandler.h
new file mode 100644
index 0000000000..8e68b32980
--- /dev/null
+++ b/dom/bindings/DOMJSProxyHandler.h
@@ -0,0 +1,163 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_DOMJSProxyHandler_h
+#define mozilla_dom_DOMJSProxyHandler_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Maybe.h"
+
+#include "jsapi.h"
+#include "js/Object.h" // JS::GetClass
+#include "js/Proxy.h"
+
+namespace mozilla::dom {
+
+/**
+ * DOM proxies store the expando object in the private slot.
+ *
+ * The expando object is a plain JSObject whose properties correspond to
+ * "expandos" (custom properties set by the script author).
+ *
+ * The exact value stored in the proxy's private slot depends on whether the
+ * interface is annotated with the [OverrideBuiltins] extended attribute.
+ *
+ * If it is, the proxy is initialized with a PrivateValue, which contains a
+ * pointer to a JS::ExpandoAndGeneration object; this contains a pointer to
+ * the actual expando object as well as the "generation" of the object. The
+ * proxy handler will trace the expando object stored in the
+ * JS::ExpandoAndGeneration while the proxy itself is alive.
+ *
+ * If it is not, the proxy is initialized with an UndefinedValue. In
+ * EnsureExpandoObject, it is set to an ObjectValue that points to the
+ * expando object directly. (It is set back to an UndefinedValue only when
+ * the object is about to die.)
+ */
+
+class BaseDOMProxyHandler : public js::BaseProxyHandler {
+ public:
+ explicit constexpr BaseDOMProxyHandler(const void* aProxyFamily,
+ bool aHasPrototype = false)
+ : js::BaseProxyHandler(aProxyFamily, aHasPrototype) {}
+
+ // Implementations of methods that can be implemented in terms of
+ // other lower-level methods.
+ bool getOwnPropertyDescriptor(
+ JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) const override;
+ virtual bool ownPropertyKeys(
+ JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandleVector<jsid> props) const override;
+
+ virtual bool getPrototypeIfOrdinary(
+ JSContext* cx, JS::Handle<JSObject*> proxy, bool* isOrdinary,
+ JS::MutableHandle<JSObject*> proto) const override;
+
+ // We override getOwnEnumerablePropertyKeys() and implement it directly
+ // instead of using the default implementation, which would call
+ // ownPropertyKeys and then filter out the non-enumerable ones. This avoids
+ // unnecessary work during enumeration.
+ virtual bool getOwnEnumerablePropertyKeys(
+ JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandleVector<jsid> props) const override;
+
+ protected:
+ // Hook for subclasses to implement shared ownPropertyKeys()/keys()
+ // functionality. The "flags" argument is either JSITER_OWNONLY (for keys())
+ // or JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS (for
+ // ownPropertyKeys()).
+ virtual bool ownPropNames(JSContext* cx, JS::Handle<JSObject*> proxy,
+ unsigned flags,
+ JS::MutableHandleVector<jsid> props) const = 0;
+
+ // Hook for subclasses to allow set() to ignore named props while other things
+ // that look at property descriptors see them. This is intentionally not
+ // named getOwnPropertyDescriptor to avoid subclasses that override it hiding
+ // our public getOwnPropertyDescriptor.
+ virtual bool getOwnPropDescriptor(
+ JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ bool ignoreNamedProps,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) const = 0;
+};
+
+class DOMProxyHandler : public BaseDOMProxyHandler {
+ public:
+ constexpr DOMProxyHandler() : BaseDOMProxyHandler(&family) {}
+
+ bool defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::Handle<JS::PropertyDescriptor> desc,
+ JS::ObjectOpResult& result) const override {
+ bool unused;
+ return defineProperty(cx, proxy, id, desc, result, &unused);
+ }
+ virtual bool defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::Handle<JS::PropertyDescriptor> desc,
+ JS::ObjectOpResult& result, bool* done) const;
+ bool delete_(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ JS::ObjectOpResult& result) const override;
+ bool preventExtensions(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::ObjectOpResult& result) const override;
+ bool isExtensible(JSContext* cx, JS::Handle<JSObject*> proxy,
+ bool* extensible) const override;
+ bool set(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver,
+ JS::ObjectOpResult& result) const override;
+
+ /*
+ * If assigning to proxy[id] hits a named setter with OverrideBuiltins or
+ * an indexed setter, call it and set *done to true on success. Otherwise, set
+ * *done to false.
+ */
+ virtual bool setCustom(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id, JS::Handle<JS::Value> v,
+ bool* done) const;
+
+ /*
+ * Get the expando object for the given DOM proxy.
+ */
+ static JSObject* GetExpandoObject(JSObject* obj);
+
+ /*
+ * Clear the expando object for the given DOM proxy and return it. This
+ * function will ensure that the returned object is exposed to active JS if
+ * the given object is exposed.
+ *
+ * GetAndClearExpandoObject does not DROP or clear the preserving wrapper
+ * flag.
+ */
+ static JSObject* GetAndClearExpandoObject(JSObject* obj);
+
+ /*
+ * Ensure that the given proxy (obj) has an expando object, and return it.
+ * Returns null on failure.
+ */
+ static JSObject* EnsureExpandoObject(JSContext* cx,
+ JS::Handle<JSObject*> obj);
+
+ static const char family;
+};
+
+// Class used by shadowing handlers (the ones that have [OverrideBuiltins].
+// This handles tracing the expando in JS::ExpandoAndGeneration.
+class ShadowingDOMProxyHandler : public DOMProxyHandler {
+ virtual void trace(JSTracer* trc, JSObject* proxy) const override;
+};
+
+inline bool IsDOMProxy(JSObject* obj) {
+ return js::IsProxy(obj) &&
+ js::GetProxyHandler(obj)->family() == &DOMProxyHandler::family;
+}
+
+inline const DOMProxyHandler* GetDOMProxyHandler(JSObject* obj) {
+ MOZ_ASSERT(IsDOMProxy(obj));
+ return static_cast<const DOMProxyHandler*>(js::GetProxyHandler(obj));
+}
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_DOMProxyHandler_h */
diff --git a/dom/bindings/DOMString.h b/dom/bindings/DOMString.h
new file mode 100644
index 0000000000..9507f27c9d
--- /dev/null
+++ b/dom/bindings/DOMString.h
@@ -0,0 +1,332 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_DOMString_h
+#define mozilla_dom_DOMString_h
+
+#include "nsString.h"
+#include "nsStringBuffer.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "nsDOMString.h"
+#include "nsAtom.h"
+
+namespace mozilla::dom {
+
+/**
+ * A class for representing string return values. This can be either passed to
+ * callees that have an nsString or nsAString out param or passed to a callee
+ * that actually knows about this class and can work with it. Such a callee may
+ * call these setters:
+ *
+ * SetKnownLiveStringBuffer
+ * SetStringBuffer
+ * SetKnownLiveString
+ * SetKnownLiveAtom
+ * SetNull
+ *
+ * to assign a value to the DOMString without instantiating an actual nsString
+ * in the process, or use AsAString() to instantiate an nsString and work with
+ * it. These options are mutually exclusive! Don't do more than one of them.
+ *
+ * It's only OK to call
+ * SetKnownLiveStringBuffer/SetKnownLiveString/SetKnownLiveAtom if the caller of
+ * the method in question plans to keep holding a strong ref to the stringbuffer
+ * involved, whether it's a raw nsStringBuffer, or stored inside the string or
+ * atom being passed. In the string/atom cases that means the caller must own
+ * the string or atom, and not mutate it (in the string case) for the lifetime
+ * of the DOMString.
+ *
+ * The proper way to extract a value is to check IsNull(). If not null, then
+ * check IsEmpty(). If neither of those is true, check HasStringBuffer(). If
+ * that's true, call StringBuffer()/StringBufferLength(). If HasStringBuffer()
+ * returns false, check HasLiteral, and if that returns true call
+ * Literal()/LiteralLength(). If HasLiteral() is false, call AsAString() and
+ * get the value from that.
+ */
+class MOZ_STACK_CLASS DOMString {
+ public:
+ DOMString() : mStringBuffer(nullptr), mLength(0), mState(State::Empty) {}
+ ~DOMString() {
+ MOZ_ASSERT(!mString || !mStringBuffer, "Shouldn't have both present!");
+ if (mState == State::OwnedStringBuffer) {
+ MOZ_ASSERT(mStringBuffer);
+ mStringBuffer->Release();
+ }
+ }
+
+ operator nsString&() { return AsAString(); }
+
+ // It doesn't make any sense to convert a DOMString to a const nsString or
+ // nsAString reference; this class is meant for outparams only.
+ operator const nsString&() = delete;
+ operator const nsAString&() = delete;
+
+ nsString& AsAString() {
+ MOZ_ASSERT(mState == State::Empty || mState == State::String,
+ "Moving from nonempty state to another nonempty state?");
+ MOZ_ASSERT(!mStringBuffer, "We already have a stringbuffer?");
+ if (!mString) {
+ mString.emplace();
+ mState = State::String;
+ }
+ return *mString;
+ }
+
+ bool HasStringBuffer() const {
+ MOZ_ASSERT(!mString || !mStringBuffer, "Shouldn't have both present!");
+ MOZ_ASSERT(mState > State::Null,
+ "Caller should have checked IsNull() and IsEmpty() first");
+ return mState >= State::OwnedStringBuffer;
+ }
+
+ // Get the stringbuffer. This can only be called if HasStringBuffer()
+ // returned true. If that's true, it will never return null. Note that
+ // constructing a string from this nsStringBuffer with length given by
+ // StringBufferLength() might give you something that is not null-terminated.
+ nsStringBuffer* StringBuffer() const {
+ MOZ_ASSERT(HasStringBuffer(),
+ "Don't ask for the stringbuffer if we don't have it");
+ MOZ_ASSERT(mStringBuffer, "We better have a stringbuffer if we claim to");
+ return mStringBuffer;
+ }
+
+ // Get the length of the stringbuffer. Can only be called if
+ // HasStringBuffer().
+ uint32_t StringBufferLength() const {
+ MOZ_ASSERT(HasStringBuffer(),
+ "Don't call this if there is no stringbuffer");
+ return mLength;
+ }
+
+ // Tell the DOMString to relinquish ownership of its nsStringBuffer to the
+ // caller. Can only be called if HasStringBuffer().
+ void RelinquishBufferOwnership() {
+ MOZ_ASSERT(HasStringBuffer(),
+ "Don't call this if there is no stringbuffer");
+ if (mState == State::OwnedStringBuffer) {
+ // Just hand that ref over.
+ mState = State::UnownedStringBuffer;
+ } else {
+ // Caller should end up holding a ref.
+ mStringBuffer->AddRef();
+ }
+ }
+
+ bool HasLiteral() const {
+ MOZ_ASSERT(!mString || !mStringBuffer, "Shouldn't have both present!");
+ MOZ_ASSERT(mState > State::Null,
+ "Caller should have checked IsNull() and IsEmpty() first");
+ return mState == State::Literal;
+ }
+
+ // Get the literal string. This can only be called if HasLiteral()
+ // returned true. If that's true, it will never return null.
+ const char16_t* Literal() const {
+ MOZ_ASSERT(HasLiteral(), "Don't ask for the literal if we don't have it");
+ MOZ_ASSERT(mLiteral, "We better have a literal if we claim to");
+ return mLiteral;
+ }
+
+ // Get the length of the literal. Can only be called if HasLiteral().
+ uint32_t LiteralLength() const {
+ MOZ_ASSERT(HasLiteral(), "Don't call this if there is no literal");
+ return mLength;
+ }
+
+ bool HasAtom() const {
+ MOZ_ASSERT(!mString || !mStringBuffer, "Shouldn't have both present!");
+ MOZ_ASSERT(mState > State::Null,
+ "Caller should have checked IsNull() and IsEmpty() first");
+ return mState == State::UnownedAtom;
+ }
+
+ // Get the atom. This can only be called if HasAtom() returned true. If
+ // that's true, it will never return null.
+ nsDynamicAtom* Atom() const {
+ MOZ_ASSERT(HasAtom(), "Don't ask for the atom if we don't have it");
+ MOZ_ASSERT(mAtom, "We better have an atom if we claim to");
+ return mAtom;
+ }
+
+ // Initialize the DOMString to a (nsStringBuffer, length) pair. The length
+ // does NOT have to be the full length of the (null-terminated) string in the
+ // nsStringBuffer.
+ void SetKnownLiveStringBuffer(nsStringBuffer* aStringBuffer,
+ uint32_t aLength) {
+ MOZ_ASSERT(mState == State::Empty, "We're already set to a value");
+ if (aLength != 0) {
+ SetStringBufferInternal(aStringBuffer, aLength);
+ mState = State::UnownedStringBuffer;
+ }
+ // else nothing to do
+ }
+
+ // Like SetKnownLiveStringBuffer, but holds a reference to the nsStringBuffer.
+ void SetStringBuffer(nsStringBuffer* aStringBuffer, uint32_t aLength) {
+ MOZ_ASSERT(mState == State::Empty, "We're already set to a value");
+ if (aLength != 0) {
+ SetStringBufferInternal(aStringBuffer, aLength);
+ aStringBuffer->AddRef();
+ mState = State::OwnedStringBuffer;
+ }
+ // else nothing to do
+ }
+
+ void SetKnownLiveString(const nsAString& aString) {
+ MOZ_ASSERT(mString.isNothing(), "We already have a string?");
+ MOZ_ASSERT(mState == State::Empty, "We're already set to a value");
+ MOZ_ASSERT(!mStringBuffer, "Setting stringbuffer twice?");
+ if (MOZ_UNLIKELY(aString.IsVoid())) {
+ SetNull();
+ } else if (!aString.IsEmpty()) {
+ nsStringBuffer* buf = nsStringBuffer::FromString(aString);
+ if (buf) {
+ SetKnownLiveStringBuffer(buf, aString.Length());
+ } else if (aString.IsLiteral()) {
+ SetLiteralInternal(aString.BeginReading(), aString.Length());
+ } else {
+ AsAString() = aString;
+ }
+ }
+ }
+
+ enum NullHandling { eTreatNullAsNull, eTreatNullAsEmpty, eNullNotExpected };
+
+ void SetKnownLiveAtom(nsAtom* aAtom, NullHandling aNullHandling) {
+ MOZ_ASSERT(mString.isNothing(), "We already have a string?");
+ MOZ_ASSERT(mState == State::Empty, "We're already set to a value");
+ MOZ_ASSERT(!mAtom, "Setting atom twice?");
+ MOZ_ASSERT(aAtom || aNullHandling != eNullNotExpected);
+ if (aNullHandling == eNullNotExpected || aAtom) {
+ if (aAtom->IsStatic()) {
+ // Static atoms are backed by literals. Explicitly call AsStatic() here
+ // to avoid the extra IsStatic() checks in nsAtom::GetUTF16String().
+ SetLiteralInternal(aAtom->AsStatic()->GetUTF16String(),
+ aAtom->GetLength());
+ } else {
+ mAtom = aAtom->AsDynamic();
+ mState = State::UnownedAtom;
+ }
+ } else if (aNullHandling == eTreatNullAsNull) {
+ SetNull();
+ }
+ }
+
+ void SetNull() {
+ MOZ_ASSERT(!mStringBuffer, "Should have no stringbuffer if null");
+ MOZ_ASSERT(mString.isNothing(), "Should have no string if null");
+ MOZ_ASSERT(mState == State::Empty, "Already set to a value?");
+ mState = State::Null;
+ }
+
+ bool IsNull() const {
+ MOZ_ASSERT(!mStringBuffer || mString.isNothing(),
+ "How could we have a stringbuffer and a nonempty string?");
+ return mState == State::Null || (mString && mString->IsVoid());
+ }
+
+ bool IsEmpty() const {
+ MOZ_ASSERT(!mStringBuffer || mString.isNothing(),
+ "How could we have a stringbuffer and a nonempty string?");
+ // This is not exact, because we might still have an empty XPCOM string.
+ // But that's OK; in that case the callers will try the XPCOM string
+ // themselves.
+ return mState == State::Empty;
+ }
+
+ void ToString(nsAString& aString) {
+ if (IsNull()) {
+ SetDOMStringToNull(aString);
+ } else if (IsEmpty()) {
+ aString.Truncate();
+ } else if (HasStringBuffer()) {
+ // Don't share the nsStringBuffer with aString if the result would not
+ // be null-terminated.
+ nsStringBuffer* buf = StringBuffer();
+ uint32_t len = StringBufferLength();
+ auto chars = static_cast<char16_t*>(buf->Data());
+ if (chars[len] == '\0') {
+ // Safe to share the buffer.
+ buf->ToString(len, aString);
+ } else {
+ // We need to copy, unfortunately.
+ aString.Assign(chars, len);
+ }
+ } else if (HasLiteral()) {
+ aString.AssignLiteral(Literal(), LiteralLength());
+ } else if (HasAtom()) {
+ mAtom->ToString(aString);
+ } else {
+ aString = AsAString();
+ }
+ }
+
+ private:
+ void SetStringBufferInternal(nsStringBuffer* aStringBuffer,
+ uint32_t aLength) {
+ MOZ_ASSERT(mString.isNothing(), "We already have a string?");
+ MOZ_ASSERT(mState == State::Empty, "We're already set to a value");
+ MOZ_ASSERT(!mStringBuffer, "Setting stringbuffer twice?");
+ MOZ_ASSERT(aStringBuffer, "Why are we getting null?");
+ MOZ_ASSERT(aLength != 0, "Should not have empty string here");
+ mStringBuffer = aStringBuffer;
+ mLength = aLength;
+ }
+
+ void SetLiteralInternal(const char16_t* aLiteral, uint32_t aLength) {
+ MOZ_ASSERT(!mLiteral, "What's going on here?");
+ mLiteral = aLiteral;
+ mLength = aLength;
+ mState = State::Literal;
+ }
+
+ enum class State : uint8_t {
+ Empty, // An empty string. Default state.
+ Null, // Null (not a string at all)
+
+ // All states that involve actual string data should come after
+ // Empty and Null.
+
+ String, // An XPCOM string stored in mString.
+ Literal, // A string literal (static lifetime).
+ UnownedAtom, // mAtom is valid and we are not holding a ref.
+ // If we ever add an OwnedAtom state, XPCStringConvert::DynamicAtomToJSVal
+ // will need to grow an out param for whether the atom was shared.
+ OwnedStringBuffer, // mStringBuffer is valid and we have a ref to it.
+ UnownedStringBuffer, // mStringBuffer is valid; we are not holding a ref.
+ // The two string buffer values must come last. This lets us avoid doing
+ // two tests to figure out whether we have a stringbuffer.
+ };
+
+ // We need to be able to act like a string as needed
+ Maybe<nsAutoString> mString;
+
+ union {
+ // The nsStringBuffer in the OwnedStringBuffer/UnownedStringBuffer cases.
+ nsStringBuffer* MOZ_UNSAFE_REF(
+ "The ways in which this can be safe are "
+ "documented above and enforced through "
+ "assertions") mStringBuffer;
+ // The literal in the Literal case.
+ const char16_t* mLiteral;
+ // The atom in the UnownedAtom case.
+ nsDynamicAtom* MOZ_UNSAFE_REF(
+ "The ways in which this can be safe are "
+ "documented above and enforced through "
+ "assertions") mAtom;
+ };
+
+ // Length in the stringbuffer and literal cases.
+ uint32_t mLength;
+
+ State mState;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_DOMString_h
diff --git a/dom/bindings/ErrorIPCUtils.h b/dom/bindings/ErrorIPCUtils.h
new file mode 100644
index 0000000000..064d851503
--- /dev/null
+++ b/dom/bindings/ErrorIPCUtils.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 IPC_ErrorIPCUtils_h
+#define IPC_ErrorIPCUtils_h
+
+#include <utility>
+
+#include "ipc/EnumSerializer.h"
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ErrorResult.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::dom::ErrNum>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::ErrNum, mozilla::dom::ErrNum(0),
+ mozilla::dom::ErrNum(mozilla::dom::Err_Limit)> {};
+
+template <>
+struct ParamTraits<mozilla::ErrorResult> {
+ typedef mozilla::ErrorResult paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ // It should be the case that mMightHaveUnreportedJSException can only be
+ // true when we're expecting a JS exception. We cannot send such messages
+ // over the IPC channel since there is no sane way of transferring the JS
+ // value over to the other side. Callers should never do that.
+ MOZ_ASSERT_IF(aParam.IsJSException(),
+ aParam.mMightHaveUnreportedJSException);
+ if (aParam.IsJSException()
+#ifdef DEBUG
+ || aParam.mMightHaveUnreportedJSException
+#endif
+ ) {
+ MOZ_CRASH(
+ "Cannot encode an ErrorResult representing a Javascript exception");
+ }
+
+ WriteParam(aWriter, aParam.mResult);
+ WriteParam(aWriter, aParam.IsErrorWithMessage());
+ WriteParam(aWriter, aParam.IsDOMException());
+ if (aParam.IsErrorWithMessage()) {
+ aParam.SerializeMessage(aWriter);
+ } else if (aParam.IsDOMException()) {
+ aParam.SerializeDOMExceptionInfo(aWriter);
+ }
+ }
+
+ static void Write(MessageWriter* aWriter, paramType&& aParam) {
+ Write(aWriter, static_cast<const paramType&>(aParam));
+ aParam.SuppressException();
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ paramType readValue;
+ if (!ReadParam(aReader, &readValue.mResult)) {
+ return false;
+ }
+ bool hasMessage = false;
+ if (!ReadParam(aReader, &hasMessage)) {
+ return false;
+ }
+ bool hasDOMExceptionInfo = false;
+ if (!ReadParam(aReader, &hasDOMExceptionInfo)) {
+ return false;
+ }
+ if (hasMessage && hasDOMExceptionInfo) {
+ // Shouldn't have both!
+ return false;
+ }
+ if (hasMessage && !readValue.DeserializeMessage(aReader)) {
+ return false;
+ } else if (hasDOMExceptionInfo &&
+ !readValue.DeserializeDOMExceptionInfo(aReader)) {
+ return false;
+ }
+ *aResult = std::move(readValue);
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::CopyableErrorResult> {
+ typedef mozilla::CopyableErrorResult paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ ParamTraits<mozilla::ErrorResult>::Write(aWriter, aParam);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ // We can't cast *aResult to ErrorResult&, so cheat and just cast
+ // to ErrorResult*.
+ return ParamTraits<mozilla::ErrorResult>::Read(
+ aReader, reinterpret_cast<mozilla::ErrorResult*>(aResult));
+ }
+};
+
+} // namespace IPC
+
+#endif
diff --git a/dom/bindings/ErrorResult.h b/dom/bindings/ErrorResult.h
new file mode 100644
index 0000000000..8b2f7e6164
--- /dev/null
+++ b/dom/bindings/ErrorResult.h
@@ -0,0 +1,928 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/**
+ * A set of structs for tracking exceptions that need to be thrown to JS:
+ * ErrorResult and IgnoredErrorResult.
+ *
+ * Conceptually, these structs represent either success or an exception in the
+ * process of being thrown. This means that a failing ErrorResult _must_ be
+ * handled in one of the following ways before coming off the stack:
+ *
+ * 1) Suppressed via SuppressException().
+ * 2) Converted to a pure nsresult return value via StealNSResult().
+ * 3) Converted to an actual pending exception on a JSContext via
+ * MaybeSetPendingException.
+ * 4) Converted to an exception JS::Value (probably to then reject a Promise
+ * with) via dom::ToJSValue.
+ *
+ * An IgnoredErrorResult will automatically do the first of those four things.
+ */
+
+#ifndef mozilla_ErrorResult_h
+#define mozilla_ErrorResult_h
+
+#include <stdarg.h>
+
+#include <new>
+#include <utility>
+
+#include "js/GCAnnotations.h"
+#include "js/ErrorReport.h"
+#include "js/Value.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Utf8.h"
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nscore.h"
+
+namespace IPC {
+class Message;
+class MessageReader;
+class MessageWriter;
+template <typename>
+struct ParamTraits;
+} // namespace IPC
+class PickleIterator;
+
+namespace mozilla {
+
+namespace dom {
+
+class Promise;
+
+enum ErrNum : uint16_t {
+#define MSG_DEF(_name, _argc, _has_context, _exn, _str) _name,
+#include "mozilla/dom/Errors.msg"
+#undef MSG_DEF
+ Err_Limit
+};
+
+// Debug-only compile-time table of the number of arguments of each error, for
+// use in static_assert.
+#if defined(DEBUG) && (defined(__clang__) || defined(__GNUC__))
+uint16_t constexpr ErrorFormatNumArgs[] = {
+# define MSG_DEF(_name, _argc, _has_context, _exn, _str) _argc,
+# include "mozilla/dom/Errors.msg"
+# undef MSG_DEF
+};
+#endif
+
+// Table of whether various error messages want a context arg.
+bool constexpr ErrorFormatHasContext[] = {
+#define MSG_DEF(_name, _argc, _has_context, _exn, _str) _has_context,
+#include "mozilla/dom/Errors.msg"
+#undef MSG_DEF
+};
+
+// Table of the kinds of exceptions error messages will produce.
+JSExnType constexpr ErrorExceptionType[] = {
+#define MSG_DEF(_name, _argc, _has_context, _exn, _str) _exn,
+#include "mozilla/dom/Errors.msg"
+#undef MSG_DEF
+};
+
+uint16_t GetErrorArgCount(const ErrNum aErrorNumber);
+
+namespace binding_detail {
+void ThrowErrorMessage(JSContext* aCx, const unsigned aErrorNumber, ...);
+} // namespace binding_detail
+
+template <ErrNum errorNumber, typename... Ts>
+inline bool ThrowErrorMessage(JSContext* aCx, Ts&&... aArgs) {
+#if defined(DEBUG) && (defined(__clang__) || defined(__GNUC__))
+ static_assert(ErrorFormatNumArgs[errorNumber] == sizeof...(aArgs),
+ "Pass in the right number of arguments");
+#endif
+ binding_detail::ThrowErrorMessage(aCx, static_cast<unsigned>(errorNumber),
+ std::forward<Ts>(aArgs)...);
+ return false;
+}
+
+template <typename CharT>
+struct TStringArrayAppender {
+ static void Append(nsTArray<nsTString<CharT>>& aArgs, uint16_t aCount) {
+ MOZ_RELEASE_ASSERT(aCount == 0,
+ "Must give at least as many string arguments as are "
+ "required by the ErrNum.");
+ }
+
+ // Allow passing nsAString/nsACString instances for our args.
+ template <typename... Ts>
+ static void Append(nsTArray<nsTString<CharT>>& aArgs, uint16_t aCount,
+ const nsTSubstring<CharT>& aFirst, Ts&&... aOtherArgs) {
+ if (aCount == 0) {
+ MOZ_ASSERT(false,
+ "There should not be more string arguments provided than are "
+ "required by the ErrNum.");
+ return;
+ }
+ aArgs.AppendElement(aFirst);
+ Append(aArgs, aCount - 1, std::forward<Ts>(aOtherArgs)...);
+ }
+
+ // Also allow passing literal instances for our args.
+ template <int N, typename... Ts>
+ static void Append(nsTArray<nsTString<CharT>>& aArgs, uint16_t aCount,
+ const CharT (&aFirst)[N], Ts&&... aOtherArgs) {
+ if (aCount == 0) {
+ MOZ_ASSERT(false,
+ "There should not be more string arguments provided than are "
+ "required by the ErrNum.");
+ return;
+ }
+ aArgs.AppendElement(nsTLiteralString<CharT>(aFirst));
+ Append(aArgs, aCount - 1, std::forward<Ts>(aOtherArgs)...);
+ }
+};
+
+using StringArrayAppender = TStringArrayAppender<char16_t>;
+using CStringArrayAppender = TStringArrayAppender<char>;
+
+} // namespace dom
+
+class ErrorResult;
+class OOMReporter;
+class CopyableErrorResult;
+
+namespace binding_danger {
+
+/**
+ * Templated implementation class for various ErrorResult-like things. The
+ * instantiations differ only in terms of their cleanup policies (used in the
+ * destructor), which they can specify via the template argument. Note that
+ * this means it's safe to reinterpret_cast between the instantiations unless
+ * you plan to invoke the destructor through such a cast pointer.
+ *
+ * A cleanup policy consists of two booleans: whether to assert that we've been
+ * reported or suppressed, and whether to then go ahead and suppress the
+ * exception.
+ */
+template <typename CleanupPolicy>
+class TErrorResult {
+ public:
+ TErrorResult()
+ : mResult(NS_OK)
+#ifdef DEBUG
+ ,
+ mMightHaveUnreportedJSException(false),
+ mUnionState(HasNothing)
+#endif
+ {
+ }
+
+ ~TErrorResult() {
+ AssertInOwningThread();
+
+ if (CleanupPolicy::assertHandled) {
+ // Consumers should have called one of MaybeSetPendingException
+ // (possibly via ToJSValue), StealNSResult, and SuppressException
+ AssertReportedOrSuppressed();
+ }
+
+ if (CleanupPolicy::suppress) {
+ SuppressException();
+ }
+
+ // And now assert that we're in a good final state.
+ AssertReportedOrSuppressed();
+ }
+
+ TErrorResult(TErrorResult&& aRHS)
+ // Initialize mResult and whatever else we need to default-initialize, so
+ // the ClearUnionData call in our operator= will do the right thing
+ // (nothing).
+ : TErrorResult() {
+ *this = std::move(aRHS);
+ }
+ TErrorResult& operator=(TErrorResult&& aRHS);
+
+ explicit TErrorResult(nsresult aRv) : TErrorResult() { AssignErrorCode(aRv); }
+
+ operator ErrorResult&();
+ operator const ErrorResult&() const;
+ operator OOMReporter&();
+
+ // This method is deprecated. Consumers should Throw*Error with the
+ // appropriate DOMException name if they are throwing a DOMException. If they
+ // have a random nsresult which may or may not correspond to a DOMException
+ // type, they should consider using an appropriate DOMException with an
+ // informative message and calling the relevant Throw*Error.
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG Throw(nsresult rv) {
+ MOZ_ASSERT(NS_FAILED(rv), "Please don't try throwing success");
+ AssignErrorCode(rv);
+ }
+
+ // Duplicate our current state on the given TErrorResult object. Any
+ // existing errors or messages on the target will be suppressed before
+ // cloning. Our own error state remains unchanged.
+ void CloneTo(TErrorResult& aRv) const;
+
+ // Use SuppressException when you want to suppress any exception that might be
+ // on the TErrorResult. After this call, the TErrorResult will be back a "no
+ // exception thrown" state.
+ void SuppressException();
+
+ // Use StealNSResult() when you want to safely convert the TErrorResult to
+ // an nsresult that you will then return to a caller. This will
+ // SuppressException(), since there will no longer be a way to report it.
+ nsresult StealNSResult() {
+ nsresult rv = ErrorCode();
+ SuppressException();
+ // Don't propagate out our internal error codes that have special meaning.
+ if (rv == NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR ||
+ rv == NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR ||
+ rv == NS_ERROR_INTERNAL_ERRORRESULT_JS_EXCEPTION ||
+ rv == NS_ERROR_INTERNAL_ERRORRESULT_DOMEXCEPTION) {
+ // What to pick here?
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ return rv;
+ }
+
+ // Use MaybeSetPendingException to convert a TErrorResult to a pending
+ // exception on the given JSContext. This is the normal "throw an exception"
+ // codepath.
+ //
+ // The return value is false if the TErrorResult represents success, true
+ // otherwise. This does mean that in JSAPI method implementations you can't
+ // just use this as |return rv.MaybeSetPendingException(cx)| (though you could
+ // |return !rv.MaybeSetPendingException(cx)|), but in practice pretty much any
+ // consumer would want to do some more work on the success codepath. So
+ // instead the way you use this is:
+ //
+ // if (rv.MaybeSetPendingException(cx)) {
+ // bail out here
+ // }
+ // go on to do something useful
+ //
+ // The success path is inline, since it should be the common case and we don't
+ // want to pay the price of a function call in some of the consumers of this
+ // method in the common case.
+ //
+ // Note that a true return value does NOT mean there is now a pending
+ // exception on aCx, due to uncatchable exceptions. It should still be
+ // considered equivalent to a JSAPI failure in terms of what callers should do
+ // after true is returned.
+ //
+ // After this call, the TErrorResult will no longer return true from Failed(),
+ // since the exception will have moved to the JSContext.
+ //
+ // If "context" is not null and our exception has a useful message string, the
+ // string "%s: ", with the value of "context" replacing %s, will be prepended
+ // to the message string. The passed-in string must be ASCII.
+ [[nodiscard]] bool MaybeSetPendingException(
+ JSContext* cx, const char* description = nullptr) {
+ WouldReportJSException();
+ if (!Failed()) {
+ return false;
+ }
+
+ SetPendingException(cx, description);
+ return true;
+ }
+
+ // Use StealExceptionFromJSContext to convert a pending exception on a
+ // JSContext to a TErrorResult. This function must be called only when a
+ // JSAPI operation failed. It assumes that lack of pending exception on the
+ // JSContext means an uncatchable exception was thrown.
+ //
+ // Codepaths that might call this method must call MightThrowJSException even
+ // if the relevant JSAPI calls do not fail.
+ //
+ // When this function returns, JS_IsExceptionPending(cx) will definitely be
+ // false.
+ void StealExceptionFromJSContext(JSContext* cx);
+
+ template <dom::ErrNum errorNumber, typename... Ts>
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG
+ ThrowTypeError(Ts&&... messageArgs) {
+ static_assert(dom::ErrorExceptionType[errorNumber] == JSEXN_TYPEERR,
+ "Throwing a non-TypeError via ThrowTypeError");
+ ThrowErrorWithMessage<errorNumber>(NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR,
+ std::forward<Ts>(messageArgs)...);
+ }
+
+ // To be used when throwing a TypeError with a completely custom
+ // message string that's only used in one spot.
+ inline void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG
+ ThrowTypeError(const nsACString& aMessage) {
+ this->template ThrowTypeError<dom::MSG_ONE_OFF_TYPEERR>(aMessage);
+ }
+
+ // To be used when throwing a TypeError with a completely custom
+ // message string that's a string literal that's only used in one spot.
+ template <int N>
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG
+ ThrowTypeError(const char (&aMessage)[N]) {
+ ThrowTypeError(nsLiteralCString(aMessage));
+ }
+
+ template <dom::ErrNum errorNumber, typename... Ts>
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG
+ ThrowRangeError(Ts&&... messageArgs) {
+ static_assert(dom::ErrorExceptionType[errorNumber] == JSEXN_RANGEERR,
+ "Throwing a non-RangeError via ThrowRangeError");
+ ThrowErrorWithMessage<errorNumber>(NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR,
+ std::forward<Ts>(messageArgs)...);
+ }
+
+ // To be used when throwing a RangeError with a completely custom
+ // message string that's only used in one spot.
+ inline void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG
+ ThrowRangeError(const nsACString& aMessage) {
+ this->template ThrowRangeError<dom::MSG_ONE_OFF_RANGEERR>(aMessage);
+ }
+
+ // To be used when throwing a RangeError with a completely custom
+ // message string that's a string literal that's only used in one spot.
+ template <int N>
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG
+ ThrowRangeError(const char (&aMessage)[N]) {
+ ThrowRangeError(nsLiteralCString(aMessage));
+ }
+
+ bool IsErrorWithMessage() const {
+ return ErrorCode() == NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR ||
+ ErrorCode() == NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR;
+ }
+
+ // Facilities for throwing a preexisting JS exception value via this
+ // TErrorResult. The contract is that any code which might end up calling
+ // ThrowJSException() or StealExceptionFromJSContext() must call
+ // MightThrowJSException() even if no exception is being thrown. Code that
+ // conditionally calls ToJSValue on this TErrorResult only if Failed() must
+ // first call WouldReportJSException even if this TErrorResult has not failed.
+ //
+ // The exn argument to ThrowJSException can be in any compartment. It does
+ // not have to be in the compartment of cx. If someone later uses it, they
+ // will wrap it into whatever compartment they're working in, as needed.
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG
+ ThrowJSException(JSContext* cx, JS::Handle<JS::Value> exn);
+ bool IsJSException() const {
+ return ErrorCode() == NS_ERROR_INTERNAL_ERRORRESULT_JS_EXCEPTION;
+ }
+
+ // Facilities for throwing DOMExceptions of whatever type a spec calls for.
+ // If an empty message string is passed to one of these Throw*Error functions,
+ // the default message string for the relevant type of DOMException will be
+ // used. The passed-in string must be UTF-8.
+#define DOMEXCEPTION(name, err) \
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG Throw##name( \
+ const nsACString& aMessage) { \
+ ThrowDOMException(err, aMessage); \
+ } \
+ \
+ template <int N> \
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG Throw##name( \
+ const char(&aMessage)[N]) { \
+ ThrowDOMException(err, aMessage); \
+ }
+
+#include "mozilla/dom/DOMExceptionNames.h"
+
+#undef DOMEXCEPTION
+
+ bool IsDOMException() const {
+ return ErrorCode() == NS_ERROR_INTERNAL_ERRORRESULT_DOMEXCEPTION;
+ }
+
+ // Flag on the TErrorResult that whatever needs throwing has been
+ // thrown on the JSContext already and we should not mess with it.
+ // If nothing was thrown, this becomes an uncatchable exception.
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG
+ NoteJSContextException(JSContext* aCx);
+
+ // Check whether the TErrorResult says to just throw whatever is on
+ // the JSContext already.
+ bool IsJSContextException() {
+ return ErrorCode() == NS_ERROR_INTERNAL_ERRORRESULT_EXCEPTION_ON_JSCONTEXT;
+ }
+
+ // Support for uncatchable exceptions.
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG ThrowUncatchableException() {
+ Throw(NS_ERROR_UNCATCHABLE_EXCEPTION);
+ }
+ bool IsUncatchableException() const {
+ return ErrorCode() == NS_ERROR_UNCATCHABLE_EXCEPTION;
+ }
+
+ void MOZ_ALWAYS_INLINE MightThrowJSException() {
+#ifdef DEBUG
+ mMightHaveUnreportedJSException = true;
+#endif
+ }
+ void MOZ_ALWAYS_INLINE WouldReportJSException() {
+#ifdef DEBUG
+ mMightHaveUnreportedJSException = false;
+#endif
+ }
+
+ // In the future, we can add overloads of Throw that take more
+ // interesting things, like strings or DOM exception types or
+ // something if desired.
+
+ // Backwards-compat to make conversion simpler. We don't call
+ // Throw() here because people can easily pass success codes to
+ // this. This operator is deprecated and ideally shouldn't be used.
+ void operator=(nsresult rv) { AssignErrorCode(rv); }
+
+ bool Failed() const { return NS_FAILED(mResult); }
+
+ bool ErrorCodeIs(nsresult rv) const { return mResult == rv; }
+
+ // For use in logging ONLY.
+ uint32_t ErrorCodeAsInt() const { return static_cast<uint32_t>(ErrorCode()); }
+
+ bool operator==(const ErrorResult& aRight) const;
+
+ protected:
+ nsresult ErrorCode() const { return mResult; }
+
+ // Helper methods for throwing DOMExceptions, for now. We can try to get rid
+ // of these once EME code is fixed to not use them and we decouple
+ // DOMExceptions from nsresult.
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG
+ ThrowDOMException(nsresult rv, const nsACString& message);
+
+ // Same thing, but using a string literal.
+ template <int N>
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG
+ ThrowDOMException(nsresult rv, const char (&aMessage)[N]) {
+ ThrowDOMException(rv, nsLiteralCString(aMessage));
+ }
+
+ // Allow Promise to call the above methods when it really needs to.
+ // Unfortunately, we can't have the definition of Promise here, so can't mark
+ // just it's MaybeRejectWithDOMException method as a friend. In any case,
+ // hopefully it's all temporary until we sort out the EME bits.
+ friend class dom::Promise;
+
+ private:
+#ifdef DEBUG
+ enum UnionState {
+ HasMessage,
+ HasDOMExceptionInfo,
+ HasJSException,
+ HasNothing
+ };
+#endif // DEBUG
+
+ friend struct IPC::ParamTraits<TErrorResult>;
+ friend struct IPC::ParamTraits<ErrorResult>;
+ void SerializeMessage(IPC::MessageWriter* aWriter) const;
+ bool DeserializeMessage(IPC::MessageReader* aReader);
+
+ void SerializeDOMExceptionInfo(IPC::MessageWriter* aWriter) const;
+ bool DeserializeDOMExceptionInfo(IPC::MessageReader* aReader);
+
+ // Helper method that creates a new Message for this TErrorResult,
+ // and returns the arguments array from that Message.
+ nsTArray<nsCString>& CreateErrorMessageHelper(const dom::ErrNum errorNumber,
+ nsresult errorType);
+
+ // Helper method to replace invalid UTF-8 characters with the replacement
+ // character. aValidUpTo is the number of characters that are known to be
+ // valid. The string might be truncated if we encounter an OOM error.
+ static void EnsureUTF8Validity(nsCString& aValue, size_t aValidUpTo);
+
+ template <dom::ErrNum errorNumber, typename... Ts>
+ void ThrowErrorWithMessage(nsresult errorType, Ts&&... messageArgs) {
+#if defined(DEBUG) && (defined(__clang__) || defined(__GNUC__))
+ static_assert(dom::ErrorFormatNumArgs[errorNumber] ==
+ sizeof...(messageArgs) +
+ int(dom::ErrorFormatHasContext[errorNumber]),
+ "Pass in the right number of arguments");
+#endif
+
+ ClearUnionData();
+
+ nsTArray<nsCString>& messageArgsArray =
+ CreateErrorMessageHelper(errorNumber, errorType);
+ uint16_t argCount = dom::GetErrorArgCount(errorNumber);
+ if (dom::ErrorFormatHasContext[errorNumber]) {
+ // Insert an empty string arg at the beginning and reduce our arg count to
+ // still be appended accordingly.
+ MOZ_ASSERT(argCount > 0,
+ "Must have at least one arg if we have a context!");
+ MOZ_ASSERT(messageArgsArray.Length() == 0,
+ "Why do we already have entries in the array?");
+ --argCount;
+ messageArgsArray.AppendElement();
+ }
+ dom::CStringArrayAppender::Append(messageArgsArray, argCount,
+ std::forward<Ts>(messageArgs)...);
+ for (nsCString& arg : messageArgsArray) {
+ size_t validUpTo = Utf8ValidUpTo(arg);
+ if (validUpTo != arg.Length()) {
+ EnsureUTF8Validity(arg, validUpTo);
+ }
+ }
+#ifdef DEBUG
+ mUnionState = HasMessage;
+#endif // DEBUG
+ }
+
+ MOZ_ALWAYS_INLINE void AssertInOwningThread() const {
+#ifdef DEBUG
+ if (CleanupPolicy::assertSameThread) {
+ NS_ASSERT_OWNINGTHREAD(TErrorResult);
+ }
+#endif
+ }
+
+ void AssignErrorCode(nsresult aRv) {
+ MOZ_ASSERT(aRv != NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR,
+ "Use ThrowTypeError()");
+ MOZ_ASSERT(aRv != NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR,
+ "Use ThrowRangeError()");
+ MOZ_ASSERT(!IsErrorWithMessage(), "Don't overwrite errors with message");
+ MOZ_ASSERT(aRv != NS_ERROR_INTERNAL_ERRORRESULT_JS_EXCEPTION,
+ "Use ThrowJSException()");
+ MOZ_ASSERT(!IsJSException(), "Don't overwrite JS exceptions");
+ MOZ_ASSERT(aRv != NS_ERROR_INTERNAL_ERRORRESULT_DOMEXCEPTION,
+ "Use Throw*Error for the appropriate DOMException name");
+ MOZ_ASSERT(!IsDOMException(), "Don't overwrite DOM exceptions");
+ MOZ_ASSERT(aRv != NS_ERROR_XPC_NOT_ENOUGH_ARGS,
+ "May need to bring back ThrowNotEnoughArgsError");
+ MOZ_ASSERT(aRv != NS_ERROR_INTERNAL_ERRORRESULT_EXCEPTION_ON_JSCONTEXT,
+ "Use NoteJSContextException");
+ mResult = aRv;
+ }
+
+ void ClearMessage();
+ void ClearDOMExceptionInfo();
+
+ // ClearUnionData will try to clear the data in our mExtra union. After this
+ // the union may be in an uninitialized state (e.g. mMessage or
+ // mDOMExceptionInfo may point to deleted memory, or mJSException may be a
+ // JS::Value containing an invalid gcthing) and the caller must either
+ // reinitialize it or change mResult to something that will not involve us
+ // touching the union anymore.
+ void ClearUnionData();
+
+ // Implementation of MaybeSetPendingException for the case when we're a
+ // failure result. See documentation of MaybeSetPendingException for the
+ // "context" argument.
+ void SetPendingException(JSContext* cx, const char* context);
+
+ // Methods for setting various specific kinds of pending exceptions. See
+ // documentation of MaybeSetPendingException for the "context" argument.
+ void SetPendingExceptionWithMessage(JSContext* cx, const char* context);
+ void SetPendingJSException(JSContext* cx);
+ void SetPendingDOMException(JSContext* cx, const char* context);
+ void SetPendingGenericErrorException(JSContext* cx);
+
+ MOZ_ALWAYS_INLINE void AssertReportedOrSuppressed() {
+ MOZ_ASSERT(!Failed());
+ MOZ_ASSERT(!mMightHaveUnreportedJSException);
+ MOZ_ASSERT(mUnionState == HasNothing);
+ }
+
+ // Special values of mResult:
+ // NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR -- ThrowTypeError() called on us.
+ // NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR -- ThrowRangeError() called on us.
+ // NS_ERROR_INTERNAL_ERRORRESULT_JS_EXCEPTION -- ThrowJSException() called
+ // on us.
+ // NS_ERROR_UNCATCHABLE_EXCEPTION -- ThrowUncatchableException called on us.
+ // NS_ERROR_INTERNAL_ERRORRESULT_DOMEXCEPTION -- ThrowDOMException() called
+ // on us.
+ nsresult mResult;
+
+ struct Message;
+ struct DOMExceptionInfo;
+ union Extra {
+ // mMessage is set by ThrowErrorWithMessage and reported (and deallocated)
+ // by SetPendingExceptionWithMessage.
+ MOZ_INIT_OUTSIDE_CTOR
+ Message* mMessage; // valid when IsErrorWithMessage()
+
+ // mJSException is set (and rooted) by ThrowJSException and reported (and
+ // unrooted) by SetPendingJSException.
+ MOZ_INIT_OUTSIDE_CTOR
+ JS::Value mJSException; // valid when IsJSException()
+
+ // mDOMExceptionInfo is set by ThrowDOMException and reported (and
+ // deallocated) by SetPendingDOMException.
+ MOZ_INIT_OUTSIDE_CTOR
+ DOMExceptionInfo* mDOMExceptionInfo; // valid when IsDOMException()
+
+ // |mJSException| has a non-trivial constructor and therefore MUST be
+ // placement-new'd into existence.
+ MOZ_PUSH_DISABLE_NONTRIVIAL_UNION_WARNINGS
+ Extra() {} // NOLINT
+ MOZ_POP_DISABLE_NONTRIVIAL_UNION_WARNINGS
+ } mExtra;
+
+ Message* InitMessage(Message* aMessage) {
+ // The |new| here switches the active arm of |mExtra|, from the compiler's
+ // point of view. Mere assignment *won't* necessarily do the right thing!
+ new (&mExtra.mMessage) Message*(aMessage);
+ return mExtra.mMessage;
+ }
+
+ JS::Value& InitJSException() {
+ // The |new| here switches the active arm of |mExtra|, from the compiler's
+ // point of view. Mere assignment *won't* necessarily do the right thing!
+ new (&mExtra.mJSException) JS::Value(); // sets to undefined
+ return mExtra.mJSException;
+ }
+
+ DOMExceptionInfo* InitDOMExceptionInfo(DOMExceptionInfo* aDOMExceptionInfo) {
+ // The |new| here switches the active arm of |mExtra|, from the compiler's
+ // point of view. Mere assignment *won't* necessarily do the right thing!
+ new (&mExtra.mDOMExceptionInfo) DOMExceptionInfo*(aDOMExceptionInfo);
+ return mExtra.mDOMExceptionInfo;
+ }
+
+#ifdef DEBUG
+ // Used to keep track of codepaths that might throw JS exceptions,
+ // for assertion purposes.
+ bool mMightHaveUnreportedJSException;
+
+ // Used to keep track of what's stored in our union right now. Note
+ // that this may be set to HasNothing even if our mResult suggests
+ // we should have something, if we have already cleaned up the
+ // something.
+ UnionState mUnionState;
+
+ // The thread that created this TErrorResult
+ NS_DECL_OWNINGTHREAD;
+#endif
+
+ // Not to be implemented, to make sure people always pass this by
+ // reference, not by value.
+ TErrorResult(const TErrorResult&) = delete;
+ void operator=(const TErrorResult&) = delete;
+} JS_HAZ_ROOTED;
+
+struct JustAssertCleanupPolicy {
+ static const bool assertHandled = true;
+ static const bool suppress = false;
+ static const bool assertSameThread = true;
+};
+
+struct AssertAndSuppressCleanupPolicy {
+ static const bool assertHandled = true;
+ static const bool suppress = true;
+ static const bool assertSameThread = true;
+};
+
+struct JustSuppressCleanupPolicy {
+ static const bool assertHandled = false;
+ static const bool suppress = true;
+ static const bool assertSameThread = true;
+};
+
+struct ThreadSafeJustSuppressCleanupPolicy {
+ static const bool assertHandled = false;
+ static const bool suppress = true;
+ static const bool assertSameThread = false;
+};
+
+} // namespace binding_danger
+
+// A class people should normally use on the stack when they plan to actually
+// do something with the exception.
+class ErrorResult : public binding_danger::TErrorResult<
+ binding_danger::AssertAndSuppressCleanupPolicy> {
+ typedef binding_danger::TErrorResult<
+ binding_danger::AssertAndSuppressCleanupPolicy>
+ BaseErrorResult;
+
+ public:
+ ErrorResult() = default;
+
+ ErrorResult(ErrorResult&& aRHS) = default;
+ // Explicitly allow moving out of a CopyableErrorResult into an ErrorResult.
+ // This is implemented below so it can see the definition of
+ // CopyableErrorResult.
+ inline explicit ErrorResult(CopyableErrorResult&& aRHS);
+
+ explicit ErrorResult(nsresult aRv) : BaseErrorResult(aRv) {}
+
+ // This operator is deprecated and ideally shouldn't be used.
+ void operator=(nsresult rv) { BaseErrorResult::operator=(rv); }
+
+ ErrorResult& operator=(ErrorResult&& aRHS) = default;
+
+ // Not to be implemented, to make sure people always pass this by
+ // reference, not by value.
+ ErrorResult(const ErrorResult&) = delete;
+ ErrorResult& operator=(const ErrorResult&) = delete;
+};
+
+template <typename CleanupPolicy>
+binding_danger::TErrorResult<CleanupPolicy>::operator ErrorResult&() {
+ return *static_cast<ErrorResult*>(
+ reinterpret_cast<TErrorResult<AssertAndSuppressCleanupPolicy>*>(this));
+}
+
+template <typename CleanupPolicy>
+binding_danger::TErrorResult<CleanupPolicy>::operator const ErrorResult&()
+ const {
+ return *static_cast<const ErrorResult*>(
+ reinterpret_cast<const TErrorResult<AssertAndSuppressCleanupPolicy>*>(
+ this));
+}
+
+// A class for use when an ErrorResult should just automatically be ignored.
+// This doesn't inherit from ErrorResult so we don't make two separate calls to
+// SuppressException.
+class IgnoredErrorResult : public binding_danger::TErrorResult<
+ binding_danger::JustSuppressCleanupPolicy> {};
+
+// A class for use when an ErrorResult needs to be copied to a lambda, into
+// an IPDL structure, etc. Since this will often involve crossing thread
+// boundaries this class will assert if you try to copy a JS exception. Only
+// use this if you are propagating internal errors. In general its best
+// to use ErrorResult by default and only convert to a CopyableErrorResult when
+// you need it.
+class CopyableErrorResult
+ : public binding_danger::TErrorResult<
+ binding_danger::ThreadSafeJustSuppressCleanupPolicy> {
+ typedef binding_danger::TErrorResult<
+ binding_danger::ThreadSafeJustSuppressCleanupPolicy>
+ BaseErrorResult;
+
+ public:
+ CopyableErrorResult() = default;
+
+ explicit CopyableErrorResult(const ErrorResult& aRight) : BaseErrorResult() {
+ auto val = reinterpret_cast<const CopyableErrorResult&>(aRight);
+ operator=(val);
+ }
+
+ CopyableErrorResult(CopyableErrorResult&& aRHS) = default;
+
+ explicit CopyableErrorResult(ErrorResult&& aRHS) : BaseErrorResult() {
+ // We must not copy JS exceptions since it can too easily lead to
+ // off-thread use. Assert this and fall back to a generic error
+ // in release builds.
+ MOZ_DIAGNOSTIC_ASSERT(
+ !aRHS.IsJSException(),
+ "Attempt to copy from ErrorResult with a JS exception value.");
+ if (aRHS.IsJSException()) {
+ aRHS.SuppressException();
+ Throw(NS_ERROR_FAILURE);
+ } else {
+ // We could avoid the cast here if we had a move constructor on
+ // TErrorResult templated on the cleanup policy type, but then we'd have
+ // to either inline the impl or force all possible instantiations or
+ // something. This is a bit simpler, and not that different from our copy
+ // constructor.
+ auto val = reinterpret_cast<CopyableErrorResult&&>(aRHS);
+ operator=(val);
+ }
+ }
+
+ explicit CopyableErrorResult(nsresult aRv) : BaseErrorResult(aRv) {}
+
+ // This operator is deprecated and ideally shouldn't be used.
+ void operator=(nsresult rv) { BaseErrorResult::operator=(rv); }
+
+ CopyableErrorResult& operator=(CopyableErrorResult&& aRHS) = default;
+
+ CopyableErrorResult(const CopyableErrorResult& aRight) : BaseErrorResult() {
+ operator=(aRight);
+ }
+
+ CopyableErrorResult& operator=(const CopyableErrorResult& aRight) {
+ // We must not copy JS exceptions since it can too easily lead to
+ // off-thread use. Assert this and fall back to a generic error
+ // in release builds.
+ MOZ_DIAGNOSTIC_ASSERT(
+ !IsJSException(),
+ "Attempt to copy to ErrorResult with a JS exception value.");
+ MOZ_DIAGNOSTIC_ASSERT(
+ !aRight.IsJSException(),
+ "Attempt to copy from ErrorResult with a JS exception value.");
+ if (aRight.IsJSException()) {
+ SuppressException();
+ Throw(NS_ERROR_FAILURE);
+ } else {
+ aRight.CloneTo(*this);
+ }
+ return *this;
+ }
+
+ // Disallow implicit converstion to non-const ErrorResult&, because that would
+ // allow people to throw exceptions on us while bypassing our checks for JS
+ // exceptions.
+ operator ErrorResult&() = delete;
+
+ // Allow conversion to ErrorResult&& so we can move out of ourselves into
+ // an ErrorResult.
+ operator ErrorResult&&() && {
+ auto* val = reinterpret_cast<ErrorResult*>(this);
+ return std::move(*val);
+ }
+};
+
+inline ErrorResult::ErrorResult(CopyableErrorResult&& aRHS)
+ : ErrorResult(reinterpret_cast<ErrorResult&&>(aRHS)) {}
+
+namespace dom {
+namespace binding_detail {
+class FastErrorResult : public mozilla::binding_danger::TErrorResult<
+ mozilla::binding_danger::JustAssertCleanupPolicy> {
+};
+} // namespace binding_detail
+} // namespace dom
+
+// We want an OOMReporter class that has the following properties:
+//
+// 1) Can be cast to from any ErrorResult-like type.
+// 2) Has a fast destructor (because we want to use it from bindings).
+// 3) Won't be randomly instantiated by non-binding code (because the fast
+// destructor is not so safe).
+// 4) Doesn't look ugly on the callee side (e.g. isn't in the binding_detail or
+// binding_danger namespace).
+//
+// We do this by creating a class that can't actually be constructed directly
+// but can be cast to from ErrorResult-like types, both implicitly and
+// explicitly.
+class OOMReporter : private dom::binding_detail::FastErrorResult {
+ public:
+ void MOZ_MUST_RETURN_FROM_CALLER_IF_THIS_IS_ARG ReportOOM() {
+ Throw(NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ // A method that turns a FastErrorResult into an OOMReporter, which we use in
+ // codegen to ensure that callees don't take an ErrorResult when they should
+ // only be taking an OOMReporter. The idea is that we can then just have a
+ // FastErrorResult on the stack and call this to produce the thing to pass to
+ // callees.
+ static OOMReporter& From(FastErrorResult& aRv) { return aRv; }
+
+ private:
+ // TErrorResult is a friend so its |operator OOMReporter&()| can work.
+ template <typename CleanupPolicy>
+ friend class binding_danger::TErrorResult;
+
+ OOMReporter() : dom::binding_detail::FastErrorResult() {}
+};
+
+template <typename CleanupPolicy>
+binding_danger::TErrorResult<CleanupPolicy>::operator OOMReporter&() {
+ return *static_cast<OOMReporter*>(
+ reinterpret_cast<TErrorResult<JustAssertCleanupPolicy>*>(this));
+}
+
+// A class for use when an ErrorResult should just automatically be
+// ignored. This is designed to be passed as a temporary only, like
+// so:
+//
+// foo->Bar(IgnoreErrors());
+class MOZ_TEMPORARY_CLASS IgnoreErrors {
+ public:
+ operator ErrorResult&() && { return mInner; }
+ operator OOMReporter&() && { return mInner; }
+
+ private:
+ // We don't use an ErrorResult member here so we don't make two separate calls
+ // to SuppressException (one from us, one from the ErrorResult destructor
+ // after asserting).
+ binding_danger::TErrorResult<binding_danger::JustSuppressCleanupPolicy>
+ mInner;
+} JS_HAZ_ROOTED;
+
+/******************************************************************************
+ ** Macros for checking results
+ ******************************************************************************/
+
+#define ENSURE_SUCCESS(res, ret) \
+ do { \
+ if (res.Failed()) { \
+ nsCString msg; \
+ msg.AppendPrintf( \
+ "ENSURE_SUCCESS(%s, %s) failed with " \
+ "result 0x%X", \
+ #res, #ret, res.ErrorCodeAsInt()); \
+ NS_WARNING(msg.get()); \
+ return ret; \
+ } \
+ } while (0)
+
+#define ENSURE_SUCCESS_VOID(res) \
+ do { \
+ if (res.Failed()) { \
+ nsCString msg; \
+ msg.AppendPrintf( \
+ "ENSURE_SUCCESS_VOID(%s) failed with " \
+ "result 0x%X", \
+ #res, res.ErrorCodeAsInt()); \
+ NS_WARNING(msg.get()); \
+ return; \
+ } \
+ } while (0)
+
+} // namespace mozilla
+
+#endif /* mozilla_ErrorResult_h */
diff --git a/dom/bindings/Errors.msg b/dom/bindings/Errors.msg
new file mode 100644
index 0000000000..912e6e06f0
--- /dev/null
+++ b/dom/bindings/Errors.msg
@@ -0,0 +1,97 @@
+/* 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/. */
+
+/*
+ * The format for each error message is:
+ *
+ * MSG_DEF(<SYMBOLIC_NAME>, <ARGUMENT_COUNT>, <CONTEXT_ARG>, <JS_EXN_TYPE>, <FORMAT_STRING>)
+ *
+ * where
+ *
+ * <SYMBOLIC_NAME> is a legal C++ identifer that will be used in the source.
+ *
+ * <ARGUMENT_COUNT> is an integer literal specifying the total number of
+ * replaceable arguments in the following format string.
+ *
+ * <CONTEXT_ARG> is a boolean indicating whether the first arg should be
+ * replaced with the context string describing what's throwing the exception,
+ * if there is such a context. If false, there should be no such replacement.
+ * If true and there is no context, the first arg will be the empty string.
+ * That means that a true value here implies the string should begin with {0}
+ * with no space after it.
+ *
+ * <JS_EXN_TYPE> is a JSExnType which specifies which kind of error the JS
+ * engine should throw.
+ *
+ * <FORMAT_STRING> is a string literal, containing <ARGUMENT_COUNT> sequences
+ * {X} where X is an integer representing the argument number that will
+ * be replaced with a string value when the error is reported.
+ */
+
+MSG_DEF(MSG_INVALID_ENUM_VALUE, 4, true, JSEXN_TYPEERR, "{0}'{2}' (value of {1}) is not a valid value for enumeration {3}.")
+MSG_DEF(MSG_INVALID_OVERLOAD_ARGCOUNT, 2, true, JSEXN_TYPEERR, "{0}{1} is not a valid argument count for any overload.")
+MSG_DEF(MSG_NOT_OBJECT, 2, true, JSEXN_TYPEERR, "{0}{1} is not an object.")
+MSG_DEF(MSG_NOT_CALLABLE, 2, true, JSEXN_TYPEERR, "{0}{1} is not callable.")
+MSG_DEF(MSG_NOT_CONSTRUCTOR, 2, true, JSEXN_TYPEERR, "{0}{1} is not a constructor.")
+MSG_DEF(MSG_DOES_NOT_IMPLEMENT_INTERFACE, 3, true, JSEXN_TYPEERR, "{0}{1} does not implement interface {2}.")
+MSG_DEF(MSG_METHOD_THIS_DOES_NOT_IMPLEMENT_INTERFACE, 2, false, JSEXN_TYPEERR, "'{0}' called on an object that does not implement interface {1}.")
+MSG_DEF(MSG_NOT_IN_UNION, 3, true, JSEXN_TYPEERR, "{0}{1} could not be converted to any of: {2}.")
+MSG_DEF(MSG_ILLEGAL_CONSTRUCTOR, 1, true, JSEXN_TYPEERR, "{0}Illegal constructor.")
+MSG_DEF(MSG_CONSTRUCTOR_WITHOUT_NEW, 1, false, JSEXN_TYPEERR, "{0} constructor: 'new' is required")
+MSG_DEF(MSG_ENFORCE_RANGE_NON_FINITE, 3, true, JSEXN_TYPEERR, "{0}{1} is not a finite value, so is out of range for {2}.")
+MSG_DEF(MSG_ENFORCE_RANGE_OUT_OF_RANGE, 3, true, JSEXN_TYPEERR, "{0}{1} is out of range for {2}.")
+MSG_DEF(MSG_CONVERSION_ERROR, 3, true, JSEXN_TYPEERR, "{0}{1} can't be converted to a {2}.")
+MSG_DEF(MSG_OVERLOAD_RESOLUTION_FAILED, 3, true, JSEXN_TYPEERR, "{0}Argument {1} is not valid for any of the {2}-argument overloads.")
+MSG_DEF(MSG_ENCODING_NOT_SUPPORTED, 2, true, JSEXN_RANGEERR, "{0}The given encoding '{1}' is not supported.")
+MSG_DEF(MSG_DOM_DECODING_FAILED, 1, true, JSEXN_TYPEERR, "{0}Decoding failed.")
+MSG_DEF(MSG_NOT_FINITE, 2, true, JSEXN_TYPEERR, "{0}{1} is not a finite floating-point value.")
+MSG_DEF(MSG_INVALID_BYTESTRING, 4, true, JSEXN_TYPEERR, "{0}Cannot convert {1} to ByteString because the character"
+ " at index {2} has value {3} which is greater than 255.")
+MSG_DEF(MSG_INVALID_URL, 2, true, JSEXN_TYPEERR, "{0}{1} is not a valid URL.")
+MSG_DEF(MSG_URL_HAS_CREDENTIALS, 2, true, JSEXN_TYPEERR, "{0}{1} is an url with embedded credentials.")
+MSG_DEF(MSG_INVALID_HEADER_NAME, 2, true, JSEXN_TYPEERR, "{0}{1} is an invalid header name.")
+MSG_DEF(MSG_INVALID_HEADER_VALUE, 2, true, JSEXN_TYPEERR, "{0}{1} is an invalid header value.")
+MSG_DEF(MSG_PERMISSION_DENIED_TO_PASS_ARG, 2, true, JSEXN_TYPEERR, "{0}Permission denied to pass cross-origin object as {1}.")
+MSG_DEF(MSG_MISSING_REQUIRED_DICTIONARY_MEMBER, 2, true, JSEXN_TYPEERR, "{0}Missing required {1}.")
+MSG_DEF(MSG_INVALID_REQUEST_METHOD, 2, true, JSEXN_TYPEERR, "{0}Invalid request method {1}.")
+MSG_DEF(MSG_INVALID_REQUEST_MODE, 2, true, JSEXN_TYPEERR, "{0}Invalid request mode {1}.")
+MSG_DEF(MSG_INVALID_REFERRER_URL, 2, true, JSEXN_TYPEERR, "{0}Invalid referrer URL {1}.")
+MSG_DEF(MSG_FETCH_BODY_CONSUMED_ERROR, 1, true, JSEXN_TYPEERR, "{0}Body has already been consumed.")
+MSG_DEF(MSG_RESPONSE_INVALID_STATUSTEXT_ERROR, 1, true, JSEXN_TYPEERR, "{0}Response statusText may not contain newline or carriage return.")
+MSG_DEF(MSG_FETCH_FAILED, 1, true, JSEXN_TYPEERR, "{0}NetworkError when attempting to fetch resource.")
+MSG_DEF(MSG_FETCH_BODY_WRONG_TYPE, 1, true, JSEXN_TYPEERR, "{0}Can't convert value to Uint8Array while consuming Body")
+MSG_DEF(MSG_INVALID_ZOOMANDPAN_VALUE_ERROR, 1, true, JSEXN_RANGEERR, "{0}Invalid zoom and pan value.")
+MSG_DEF(MSG_INVALID_TRANSFORM_ANGLE_ERROR, 1, true, JSEXN_RANGEERR, "{0}Invalid transform angle.")
+MSG_DEF(MSG_INVALID_URL_SCHEME, 3, true, JSEXN_TYPEERR, "{0}{1} URL {2} must be either http:// or https://.")
+MSG_DEF(MSG_BAD_FORMDATA, 1, true, JSEXN_TYPEERR, "{0}Could not parse content as FormData.")
+MSG_DEF(MSG_NO_ACTIVE_WORKER, 2, true, JSEXN_TYPEERR, "{0}No active worker for scope {1}.")
+MSG_DEF(MSG_INVALID_SCOPE, 3, true, JSEXN_TYPEERR, "{0}Invalid scope trying to resolve {1} with base URL {2}.")
+MSG_DEF(MSG_INVALID_KEYFRAME_OFFSETS, 1, true, JSEXN_TYPEERR, "{0}Keyframes with specified offsets must be in order and all be in the range [0, 1].")
+MSG_DEF(MSG_IS_NOT_PROMISE, 1, true, JSEXN_TYPEERR, "{0}Argument is not a Promise")
+MSG_DEF(MSG_SW_INSTALL_ERROR, 3, true, JSEXN_TYPEERR, "{0}ServiceWorker script at {1} for scope {2} encountered an error during installation.")
+MSG_DEF(MSG_SW_SCRIPT_THREW, 3, true, JSEXN_TYPEERR, "{0}ServiceWorker script at {1} for scope {2} threw an exception during script evaluation.")
+MSG_DEF(MSG_TYPEDARRAY_IS_SHARED, 2, true, JSEXN_TYPEERR, "{0}{1} can't be a SharedArrayBuffer or an ArrayBufferView backed by a SharedArrayBuffer")
+MSG_DEF(MSG_TYPEDARRAY_IS_LARGE, 2, true, JSEXN_TYPEERR, "{0}{1} can't be an ArrayBuffer or an ArrayBufferView larger than 2 GB")
+MSG_DEF(MSG_CACHE_ADD_FAILED_RESPONSE, 4, true, JSEXN_TYPEERR, "{0}Cache got {1} response with bad status {2} while trying to add request {3}")
+MSG_DEF(MSG_SW_UPDATE_BAD_REGISTRATION, 3, true, JSEXN_TYPEERR, "{0}Failed to update the ServiceWorker for scope {1} because the registration has been {2} since the update was scheduled.")
+MSG_DEF(MSG_INVALID_DURATION_ERROR, 2, true, JSEXN_TYPEERR, "{0}Invalid duration '{1}'.")
+MSG_DEF(MSG_INVALID_EASING_ERROR, 2, true, JSEXN_TYPEERR, "{0}Invalid easing '{1}'.")
+MSG_DEF(MSG_TOKENLIST_NO_SUPPORTED_TOKENS, 3, true, JSEXN_TYPEERR, "{0}{1} attribute of <{2}> does not define any supported tokens")
+MSG_DEF(MSG_TIME_VALUE_OUT_OF_RANGE, 2, true, JSEXN_TYPEERR, "{0}{1} is outside the supported range for time values.")
+MSG_DEF(MSG_ONLY_IF_CACHED_WITHOUT_SAME_ORIGIN, 2, true, JSEXN_TYPEERR, "{0}Request mode '{1}' was used, but request cache mode 'only-if-cached' can only be used with request mode 'same-origin'.")
+MSG_DEF(MSG_THRESHOLD_RANGE_ERROR, 1, true, JSEXN_RANGEERR, "{0}Threshold values must all be in the range [0, 1].")
+MSG_DEF(MSG_MATRIX_INIT_CONFLICTING_VALUE, 3, true, JSEXN_TYPEERR, "{0}Matrix init unexpectedly got different values for '{1}' and '{2}'.")
+MSG_DEF(MSG_MATRIX_INIT_EXCEEDS_2D, 2, true, JSEXN_TYPEERR, "{0}Matrix init has an unexpected 3D element '{1}' which cannot coexist with 'is2D: true'.")
+MSG_DEF(MSG_MATRIX_INIT_LENGTH_WRONG, 2, true, JSEXN_TYPEERR, "{0}Matrix init sequence must have a length of 6 or 16 (actual value: {1})")
+MSG_DEF(MSG_INVALID_MEDIA_VIDEO_CONFIGURATION, 1, true, JSEXN_TYPEERR, "{0}Invalid VideoConfiguration.")
+MSG_DEF(MSG_INVALID_MEDIA_AUDIO_CONFIGURATION, 1, true, JSEXN_TYPEERR, "{0}Invalid AudioConfiguration.")
+MSG_DEF(MSG_INVALID_AUDIOPARAM_METHOD_START_TIME_ERROR, 1, true, JSEXN_RANGEERR, "{0}The start time for an AudioParam method must be non-negative.")
+MSG_DEF(MSG_INVALID_AUDIOPARAM_METHOD_END_TIME_ERROR, 1, true, JSEXN_RANGEERR, "{0}The end time for an AudioParam method must be non-negative.")
+MSG_DEF(MSG_VALUE_OUT_OF_RANGE, 2, true, JSEXN_RANGEERR, "{0}The value for the {1} is outside the valid range.")
+MSG_DEF(MSG_NOT_ARRAY_NOR_UNDEFINED, 2, true, JSEXN_TYPEERR, "{0}{1} is neither an array nor undefined.")
+MSG_DEF(MSG_URL_NOT_LOADABLE, 2, true, JSEXN_TYPEERR, "{0}Access to '{1}' from script denied.")
+MSG_DEF(MSG_ONE_OFF_TYPEERR, 2, true, JSEXN_TYPEERR, "{0}{1}")
+MSG_DEF(MSG_ONE_OFF_RANGEERR, 2, true, JSEXN_RANGEERR, "{0}{1}")
+MSG_DEF(MSG_NO_CODECS_PARAMETER, 2, true, JSEXN_TYPEERR, "{0}The provided type '{1}' does not have a 'codecs' parameter.")
+MSG_DEF(MSG_JSON_INVALID_VALUE, 1, true, JSEXN_TYPEERR, "{0}Value could not be serialized as JSON.")
diff --git a/dom/bindings/Exceptions.cpp b/dom/bindings/Exceptions.cpp
new file mode 100644
index 0000000000..6e61c4ee95
--- /dev/null
+++ b/dom/bindings/Exceptions.cpp
@@ -0,0 +1,759 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/Exceptions.h"
+
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "jsapi.h"
+#include "js/SavedFrameAPI.h"
+#include "xpcpublic.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsJSPrincipals.h"
+#include "nsPIDOMWindow.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "XPCWrapper.h"
+#include "WorkerPrivate.h"
+#include "nsContentUtils.h"
+
+namespace mozilla::dom {
+
+// Throw the given exception value if it's safe. If it's not safe, then
+// synthesize and throw a new exception value for NS_ERROR_UNEXPECTED. The
+// incoming value must be in the compartment of aCx. This function guarantees
+// that an exception is pending on aCx when it returns.
+static void ThrowExceptionValueIfSafe(JSContext* aCx,
+ JS::Handle<JS::Value> exnVal,
+ Exception* aOriginalException) {
+ MOZ_ASSERT(aOriginalException);
+
+ if (!exnVal.isObject()) {
+ JS_SetPendingException(aCx, exnVal);
+ return;
+ }
+
+ JS::Rooted<JSObject*> exnObj(aCx, &exnVal.toObject());
+ MOZ_ASSERT(js::IsObjectInContextCompartment(exnObj, aCx),
+ "exnObj needs to be in the right compartment for the "
+ "CheckedUnwrapDynamic thing to make sense");
+
+ // aCx's current Realm is where we're throwing, so using it in the
+ // CheckedUnwrapDynamic check makes sense.
+ if (js::CheckedUnwrapDynamic(exnObj, aCx)) {
+ // This is an object we're allowed to work with, so just go ahead and throw
+ // it.
+ JS_SetPendingException(aCx, exnVal);
+ return;
+ }
+
+ // We could probably Throw(aCx, NS_ERROR_UNEXPECTED) here, and it would do the
+ // right thing due to there not being an existing exception on the runtime at
+ // this point, but it's clearer to explicitly do the thing we want done. This
+ // is also why we don't just call ThrowExceptionObject on the Exception we
+ // create: it would do the right thing, but that fact is not obvious.
+ RefPtr<Exception> syntheticException = CreateException(NS_ERROR_UNEXPECTED);
+ JS::Rooted<JS::Value> syntheticVal(aCx);
+ if (!GetOrCreateDOMReflector(aCx, syntheticException, &syntheticVal)) {
+ return;
+ }
+ MOZ_ASSERT(
+ syntheticVal.isObject() && !js::IsWrapper(&syntheticVal.toObject()),
+ "Must have a reflector here, not a wrapper");
+ JS_SetPendingException(aCx, syntheticVal);
+}
+
+void ThrowExceptionObject(JSContext* aCx, Exception* aException) {
+ JS::Rooted<JS::Value> thrown(aCx);
+
+ // If we stored the original thrown JS value in the exception
+ // (see XPCConvert::ConstructException) and we are in a web context
+ // (i.e., not chrome), rethrow the original value. This only applies to JS
+ // implemented components so we only need to check for this on the main
+ // thread.
+ if (NS_IsMainThread() && !nsContentUtils::IsCallerChrome() &&
+ aException->StealJSVal(thrown.address())) {
+ // Now check for the case when thrown is a number which matches
+ // aException->GetResult(). This would indicate that what actually got
+ // thrown was an nsresult value. In that situation, we should go back
+ // through dom::Throw with that nsresult value, because it will make sure to
+ // create the right sort of Exception or DOMException, with the right
+ // global.
+ if (thrown.isNumber()) {
+ nsresult exceptionResult = aException->GetResult();
+ if (double(exceptionResult) == thrown.toNumber()) {
+ Throw(aCx, exceptionResult);
+ return;
+ }
+ }
+ if (!JS_WrapValue(aCx, &thrown)) {
+ return;
+ }
+ ThrowExceptionValueIfSafe(aCx, thrown, aException);
+ return;
+ }
+
+ if (!GetOrCreateDOMReflector(aCx, aException, &thrown)) {
+ return;
+ }
+
+ ThrowExceptionValueIfSafe(aCx, thrown, aException);
+}
+
+bool Throw(JSContext* aCx, nsresult aRv, const nsACString& aMessage) {
+ if (aRv == NS_ERROR_UNCATCHABLE_EXCEPTION) {
+ // Nuke any existing exception on aCx, to make sure we're uncatchable.
+ JS_ClearPendingException(aCx);
+ return false;
+ }
+
+ if (JS_IsExceptionPending(aCx)) {
+ // Don't clobber the existing exception.
+ return false;
+ }
+
+ CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
+ RefPtr<Exception> existingException = context->GetPendingException();
+ // Make sure to clear the pending exception now. Either we're going to reuse
+ // it (and we already grabbed it), or we plan to throw something else and this
+ // pending exception is no longer relevant.
+ context->SetPendingException(nullptr);
+
+ // Ignore the pending exception if we have a non-default message passed in.
+ if (aMessage.IsEmpty() && existingException) {
+ if (aRv == existingException->GetResult()) {
+ // Reuse the existing exception.
+ ThrowExceptionObject(aCx, existingException);
+ return false;
+ }
+ }
+
+ RefPtr<Exception> finalException = CreateException(aRv, aMessage);
+ MOZ_ASSERT(finalException);
+
+ ThrowExceptionObject(aCx, finalException);
+ return false;
+}
+
+void ThrowAndReport(nsPIDOMWindowInner* aWindow, nsresult aRv) {
+ MOZ_ASSERT(aRv != NS_ERROR_UNCATCHABLE_EXCEPTION,
+ "Doesn't make sense to report uncatchable exceptions!");
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(aWindow))) {
+ return;
+ }
+
+ Throw(jsapi.cx(), aRv);
+}
+
+already_AddRefed<Exception> CreateException(nsresult aRv,
+ const nsACString& aMessage) {
+ // Do we use DOM exceptions for this error code?
+ switch (NS_ERROR_GET_MODULE(aRv)) {
+ case NS_ERROR_MODULE_DOM:
+ case NS_ERROR_MODULE_SVG:
+ case NS_ERROR_MODULE_DOM_FILE:
+ case NS_ERROR_MODULE_DOM_XPATH:
+ case NS_ERROR_MODULE_DOM_INDEXEDDB:
+ case NS_ERROR_MODULE_DOM_FILEHANDLE:
+ case NS_ERROR_MODULE_DOM_ANIM:
+ case NS_ERROR_MODULE_DOM_PUSH:
+ case NS_ERROR_MODULE_DOM_MEDIA:
+ if (aMessage.IsEmpty()) {
+ return DOMException::Create(aRv);
+ }
+ return DOMException::Create(aRv, aMessage);
+ default:
+ break;
+ }
+
+ // If not, use the default.
+ RefPtr<Exception> exception =
+ new Exception(aMessage, aRv, ""_ns, nullptr, nullptr);
+ return exception.forget();
+}
+
+already_AddRefed<nsIStackFrame> GetCurrentJSStack(int32_t aMaxDepth) {
+ // is there a current context available?
+ JSContext* cx = nsContentUtils::GetCurrentJSContext();
+
+ if (!cx || !js::GetContextRealm(cx)) {
+ return nullptr;
+ }
+
+ static const unsigned MAX_FRAMES = 100;
+ if (aMaxDepth < 0) {
+ aMaxDepth = MAX_FRAMES;
+ }
+
+ JS::StackCapture captureMode =
+ aMaxDepth == 0 ? JS::StackCapture(JS::AllFrames())
+ : JS::StackCapture(JS::MaxFrames(aMaxDepth));
+
+ return dom::exceptions::CreateStack(cx, std::move(captureMode));
+}
+
+namespace exceptions {
+
+class JSStackFrame final : public nsIStackFrame, public xpc::JSStackFrameBase {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(JSStackFrame)
+ NS_DECL_NSISTACKFRAME
+
+ // aStack must not be null.
+ explicit JSStackFrame(JS::Handle<JSObject*> aStack);
+
+ private:
+ virtual ~JSStackFrame();
+
+ void Clear() override { mStack = nullptr; }
+
+ // Remove this frame from the per-realm list of live frames,
+ // and clear out the stack pointer.
+ void UnregisterAndClear();
+
+ JS::Heap<JSObject*> mStack;
+ nsString mFormattedStack;
+
+ nsCOMPtr<nsIStackFrame> mCaller;
+ nsCOMPtr<nsIStackFrame> mAsyncCaller;
+ nsString mFilename;
+ nsString mFunname;
+ nsString mAsyncCause;
+ int32_t mSourceId;
+ int32_t mLineno;
+ int32_t mColNo;
+
+ bool mFilenameInitialized;
+ bool mFunnameInitialized;
+ bool mSourceIdInitialized;
+ bool mLinenoInitialized;
+ bool mColNoInitialized;
+ bool mAsyncCauseInitialized;
+ bool mAsyncCallerInitialized;
+ bool mCallerInitialized;
+ bool mFormattedStackInitialized;
+};
+
+JSStackFrame::JSStackFrame(JS::Handle<JSObject*> aStack)
+ : mStack(aStack),
+ mSourceId(0),
+ mLineno(0),
+ mColNo(0),
+ mFilenameInitialized(false),
+ mFunnameInitialized(false),
+ mSourceIdInitialized(false),
+ mLinenoInitialized(false),
+ mColNoInitialized(false),
+ mAsyncCauseInitialized(false),
+ mAsyncCallerInitialized(false),
+ mCallerInitialized(false),
+ mFormattedStackInitialized(false) {
+ MOZ_ASSERT(mStack);
+ MOZ_ASSERT(JS::IsUnwrappedSavedFrame(mStack));
+
+ mozilla::HoldJSObjects(this);
+
+ xpc::RegisterJSStackFrame(js::GetNonCCWObjectRealm(aStack), this);
+}
+
+JSStackFrame::~JSStackFrame() {
+ UnregisterAndClear();
+ mozilla::DropJSObjects(this);
+}
+
+void JSStackFrame::UnregisterAndClear() {
+ if (!mStack) {
+ return;
+ }
+
+ xpc::UnregisterJSStackFrame(js::GetNonCCWObjectRealm(mStack), this);
+ Clear();
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(JSStackFrame)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(JSStackFrame)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCaller)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAsyncCaller)
+ tmp->UnregisterAndClear();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(JSStackFrame)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCaller)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAsyncCaller)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(JSStackFrame)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mStack)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(JSStackFrame)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(JSStackFrame)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSStackFrame)
+ NS_INTERFACE_MAP_ENTRY(nsIStackFrame)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+// Helper method to determine the JSPrincipals* to pass to JS SavedFrame APIs.
+//
+// @argument aStack the stack we're working with; must be non-null.
+// @argument [out] aCanCache whether we can use cached JSStackFrame values.
+static JSPrincipals* GetPrincipalsForStackGetter(JSContext* aCx,
+ JS::Handle<JSObject*> aStack,
+ bool* aCanCache) {
+ MOZ_ASSERT(JS::IsUnwrappedSavedFrame(aStack));
+
+ JSPrincipals* currentPrincipals =
+ JS::GetRealmPrincipals(js::GetContextRealm(aCx));
+ JSPrincipals* stackPrincipals =
+ JS::GetRealmPrincipals(js::GetNonCCWObjectRealm(aStack));
+
+ // Fast path for when the principals are equal. This check is also necessary
+ // for workers: no nsIPrincipal there so we can't use the code below.
+ if (currentPrincipals == stackPrincipals) {
+ *aCanCache = true;
+ return stackPrincipals;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (nsJSPrincipals::get(currentPrincipals)
+ ->Subsumes(nsJSPrincipals::get(stackPrincipals))) {
+ // The current principals subsume the stack's principals. In this case use
+ // the stack's principals: the idea is that this way devtools code that's
+ // asking an exception object for a stack to display will end up with the
+ // stack the web developer would see via doing .stack in a web page, with
+ // Firefox implementation details excluded.
+
+ // Because we use the stack's principals and don't rely on the current
+ // context realm, we can use cached values.
+ *aCanCache = true;
+ return stackPrincipals;
+ }
+
+ // The stack was captured in more-privileged code, so use the less privileged
+ // principals. Don't use cached values because we don't want these values to
+ // depend on the current realm/principals.
+ *aCanCache = false;
+ return currentPrincipals;
+}
+
+// Helper method to get the value of a stack property, if it's not already
+// cached. This will make sure we skip the cache if the property value depends
+// on the (current) context's realm/principals.
+//
+// @argument aStack the stack we're working with; must be non-null.
+// @argument aPropGetter the getter function to call.
+// @argument aIsCached whether we've cached this property's value before.
+//
+// @argument [out] aCanCache whether the value can get cached.
+// @argument [out] aUseCachedValue if true, just use the cached value.
+// @argument [out] aValue the value we got from the stack.
+template <typename ReturnType, typename GetterOutParamType>
+static void GetValueIfNotCached(
+ JSContext* aCx, const JS::Heap<JSObject*>& aStack,
+ JS::SavedFrameResult (*aPropGetter)(JSContext*, JSPrincipals*,
+ JS::Handle<JSObject*>,
+ GetterOutParamType,
+ JS::SavedFrameSelfHosted),
+ bool aIsCached, bool* aCanCache, bool* aUseCachedValue, ReturnType aValue) {
+ MOZ_ASSERT(aStack);
+ MOZ_ASSERT(JS::IsUnwrappedSavedFrame(aStack));
+
+ JS::Rooted<JSObject*> stack(aCx, aStack);
+
+ JSPrincipals* principals = GetPrincipalsForStackGetter(aCx, stack, aCanCache);
+ if (*aCanCache && aIsCached) {
+ *aUseCachedValue = true;
+ return;
+ }
+
+ *aUseCachedValue = false;
+
+ aPropGetter(aCx, principals, stack, aValue,
+ JS::SavedFrameSelfHosted::Exclude);
+}
+
+NS_IMETHODIMP JSStackFrame::GetFilenameXPCOM(JSContext* aCx,
+ nsAString& aFilename) {
+ GetFilename(aCx, aFilename);
+ return NS_OK;
+}
+
+void JSStackFrame::GetFilename(JSContext* aCx, nsAString& aFilename) {
+ if (!mStack) {
+ aFilename.Truncate();
+ return;
+ }
+
+ JS::Rooted<JSString*> filename(aCx);
+ bool canCache = false, useCachedValue = false;
+ GetValueIfNotCached(aCx, mStack, JS::GetSavedFrameSource,
+ mFilenameInitialized, &canCache, &useCachedValue,
+ &filename);
+ if (useCachedValue) {
+ aFilename = mFilename;
+ return;
+ }
+
+ nsAutoJSString str;
+ if (!str.init(aCx, filename)) {
+ JS_ClearPendingException(aCx);
+ aFilename.Truncate();
+ return;
+ }
+ aFilename = str;
+
+ if (canCache) {
+ mFilename = str;
+ mFilenameInitialized = true;
+ }
+}
+
+NS_IMETHODIMP
+JSStackFrame::GetNameXPCOM(JSContext* aCx, nsAString& aFunction) {
+ GetName(aCx, aFunction);
+ return NS_OK;
+}
+
+void JSStackFrame::GetName(JSContext* aCx, nsAString& aFunction) {
+ if (!mStack) {
+ aFunction.Truncate();
+ return;
+ }
+
+ JS::Rooted<JSString*> name(aCx);
+ bool canCache = false, useCachedValue = false;
+ GetValueIfNotCached(aCx, mStack, JS::GetSavedFrameFunctionDisplayName,
+ mFunnameInitialized, &canCache, &useCachedValue, &name);
+
+ if (useCachedValue) {
+ aFunction = mFunname;
+ return;
+ }
+
+ if (name) {
+ nsAutoJSString str;
+ if (!str.init(aCx, name)) {
+ JS_ClearPendingException(aCx);
+ aFunction.Truncate();
+ return;
+ }
+ aFunction = str;
+ } else {
+ aFunction.SetIsVoid(true);
+ }
+
+ if (canCache) {
+ mFunname = aFunction;
+ mFunnameInitialized = true;
+ }
+}
+
+int32_t JSStackFrame::GetSourceId(JSContext* aCx) {
+ if (!mStack) {
+ return 0;
+ }
+
+ uint32_t id;
+ bool canCache = false, useCachedValue = false;
+ GetValueIfNotCached(aCx, mStack, JS::GetSavedFrameSourceId,
+ mSourceIdInitialized, &canCache, &useCachedValue, &id);
+
+ if (useCachedValue) {
+ return mSourceId;
+ }
+
+ if (canCache) {
+ mSourceId = id;
+ mSourceIdInitialized = true;
+ }
+
+ return id;
+}
+
+NS_IMETHODIMP
+JSStackFrame::GetSourceIdXPCOM(JSContext* aCx, int32_t* aSourceId) {
+ *aSourceId = GetSourceId(aCx);
+ return NS_OK;
+}
+
+int32_t JSStackFrame::GetLineNumber(JSContext* aCx) {
+ if (!mStack) {
+ return 0;
+ }
+
+ uint32_t line;
+ bool canCache = false, useCachedValue = false;
+ GetValueIfNotCached(aCx, mStack, JS::GetSavedFrameLine, mLinenoInitialized,
+ &canCache, &useCachedValue, &line);
+
+ if (useCachedValue) {
+ return mLineno;
+ }
+
+ if (canCache) {
+ mLineno = line;
+ mLinenoInitialized = true;
+ }
+
+ return line;
+}
+
+NS_IMETHODIMP
+JSStackFrame::GetLineNumberXPCOM(JSContext* aCx, int32_t* aLineNumber) {
+ *aLineNumber = GetLineNumber(aCx);
+ return NS_OK;
+}
+
+int32_t JSStackFrame::GetColumnNumber(JSContext* aCx) {
+ if (!mStack) {
+ return 0;
+ }
+
+ uint32_t col;
+ bool canCache = false, useCachedValue = false;
+ GetValueIfNotCached(aCx, mStack, JS::GetSavedFrameColumn, mColNoInitialized,
+ &canCache, &useCachedValue, &col);
+
+ if (useCachedValue) {
+ return mColNo;
+ }
+
+ if (canCache) {
+ mColNo = col;
+ mColNoInitialized = true;
+ }
+
+ return col;
+}
+
+NS_IMETHODIMP
+JSStackFrame::GetColumnNumberXPCOM(JSContext* aCx, int32_t* aColumnNumber) {
+ *aColumnNumber = GetColumnNumber(aCx);
+ return NS_OK;
+}
+
+NS_IMETHODIMP JSStackFrame::GetSourceLine(nsACString& aSourceLine) {
+ aSourceLine.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+JSStackFrame::GetAsyncCauseXPCOM(JSContext* aCx, nsAString& aAsyncCause) {
+ GetAsyncCause(aCx, aAsyncCause);
+ return NS_OK;
+}
+
+void JSStackFrame::GetAsyncCause(JSContext* aCx, nsAString& aAsyncCause) {
+ if (!mStack) {
+ aAsyncCause.Truncate();
+ return;
+ }
+
+ JS::Rooted<JSString*> asyncCause(aCx);
+ bool canCache = false, useCachedValue = false;
+ GetValueIfNotCached(aCx, mStack, JS::GetSavedFrameAsyncCause,
+ mAsyncCauseInitialized, &canCache, &useCachedValue,
+ &asyncCause);
+
+ if (useCachedValue) {
+ aAsyncCause = mAsyncCause;
+ return;
+ }
+
+ if (asyncCause) {
+ nsAutoJSString str;
+ if (!str.init(aCx, asyncCause)) {
+ JS_ClearPendingException(aCx);
+ aAsyncCause.Truncate();
+ return;
+ }
+ aAsyncCause = str;
+ } else {
+ aAsyncCause.SetIsVoid(true);
+ }
+
+ if (canCache) {
+ mAsyncCause = aAsyncCause;
+ mAsyncCauseInitialized = true;
+ }
+}
+
+NS_IMETHODIMP
+JSStackFrame::GetAsyncCallerXPCOM(JSContext* aCx,
+ nsIStackFrame** aAsyncCaller) {
+ *aAsyncCaller = GetAsyncCaller(aCx).take();
+ return NS_OK;
+}
+
+already_AddRefed<nsIStackFrame> JSStackFrame::GetAsyncCaller(JSContext* aCx) {
+ if (!mStack) {
+ return nullptr;
+ }
+
+ JS::Rooted<JSObject*> asyncCallerObj(aCx);
+ bool canCache = false, useCachedValue = false;
+ GetValueIfNotCached(aCx, mStack, JS::GetSavedFrameAsyncParent,
+ mAsyncCallerInitialized, &canCache, &useCachedValue,
+ &asyncCallerObj);
+
+ if (useCachedValue) {
+ nsCOMPtr<nsIStackFrame> asyncCaller = mAsyncCaller;
+ return asyncCaller.forget();
+ }
+
+ nsCOMPtr<nsIStackFrame> asyncCaller =
+ asyncCallerObj ? new JSStackFrame(asyncCallerObj) : nullptr;
+
+ if (canCache) {
+ mAsyncCaller = asyncCaller;
+ mAsyncCallerInitialized = true;
+ }
+
+ return asyncCaller.forget();
+}
+
+NS_IMETHODIMP
+JSStackFrame::GetCallerXPCOM(JSContext* aCx, nsIStackFrame** aCaller) {
+ *aCaller = GetCaller(aCx).take();
+ return NS_OK;
+}
+
+already_AddRefed<nsIStackFrame> JSStackFrame::GetCaller(JSContext* aCx) {
+ if (!mStack) {
+ return nullptr;
+ }
+
+ JS::Rooted<JSObject*> callerObj(aCx);
+ bool canCache = false, useCachedValue = false;
+ GetValueIfNotCached(aCx, mStack, JS::GetSavedFrameParent, mCallerInitialized,
+ &canCache, &useCachedValue, &callerObj);
+
+ if (useCachedValue) {
+ nsCOMPtr<nsIStackFrame> caller = mCaller;
+ return caller.forget();
+ }
+
+ nsCOMPtr<nsIStackFrame> caller =
+ callerObj ? new JSStackFrame(callerObj) : nullptr;
+
+ if (canCache) {
+ mCaller = caller;
+ mCallerInitialized = true;
+ }
+
+ return caller.forget();
+}
+
+NS_IMETHODIMP
+JSStackFrame::GetFormattedStackXPCOM(JSContext* aCx, nsAString& aStack) {
+ GetFormattedStack(aCx, aStack);
+ return NS_OK;
+}
+
+void JSStackFrame::GetFormattedStack(JSContext* aCx, nsAString& aStack) {
+ if (!mStack) {
+ aStack.Truncate();
+ return;
+ }
+
+ // Sadly we can't use GetValueIfNotCached here, because our getter
+ // returns bool, not JS::SavedFrameResult. Maybe it's possible to
+ // make the templates more complicated to deal, but in the meantime
+ // let's just inline GetValueIfNotCached here.
+
+ JS::Rooted<JSObject*> stack(aCx, mStack);
+
+ bool canCache;
+ JSPrincipals* principals = GetPrincipalsForStackGetter(aCx, stack, &canCache);
+ if (canCache && mFormattedStackInitialized) {
+ aStack = mFormattedStack;
+ return;
+ }
+
+ JS::Rooted<JSString*> formattedStack(aCx);
+ if (!JS::BuildStackString(aCx, principals, stack, &formattedStack)) {
+ JS_ClearPendingException(aCx);
+ aStack.Truncate();
+ return;
+ }
+
+ nsAutoJSString str;
+ if (!str.init(aCx, formattedStack)) {
+ JS_ClearPendingException(aCx);
+ aStack.Truncate();
+ return;
+ }
+
+ aStack = str;
+
+ if (canCache) {
+ mFormattedStack = str;
+ mFormattedStackInitialized = true;
+ }
+}
+
+NS_IMETHODIMP JSStackFrame::GetNativeSavedFrame(
+ JS::MutableHandle<JS::Value> aSavedFrame) {
+ aSavedFrame.setObjectOrNull(mStack);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+JSStackFrame::ToStringXPCOM(JSContext* aCx, nsACString& _retval) {
+ ToString(aCx, _retval);
+ return NS_OK;
+}
+
+void JSStackFrame::ToString(JSContext* aCx, nsACString& _retval) {
+ _retval.Truncate();
+
+ nsString filename;
+ GetFilename(aCx, filename);
+
+ if (filename.IsEmpty()) {
+ filename.AssignLiteral("<unknown filename>");
+ }
+
+ nsString funname;
+ GetName(aCx, funname);
+
+ if (funname.IsEmpty()) {
+ funname.AssignLiteral("<TOP_LEVEL>");
+ }
+
+ int32_t lineno = GetLineNumber(aCx);
+
+ static const char format[] = "JS frame :: %s :: %s :: line %d";
+ _retval.AppendPrintf(format, NS_ConvertUTF16toUTF8(filename).get(),
+ NS_ConvertUTF16toUTF8(funname).get(), lineno);
+}
+
+already_AddRefed<nsIStackFrame> CreateStack(JSContext* aCx,
+ JS::StackCapture&& aCaptureMode) {
+ JS::Rooted<JSObject*> stack(aCx);
+ if (!JS::CaptureCurrentStack(aCx, &stack, std::move(aCaptureMode))) {
+ return nullptr;
+ }
+
+ return CreateStack(aCx, stack);
+}
+
+already_AddRefed<nsIStackFrame> CreateStack(JSContext* aCx,
+ JS::Handle<JSObject*> aStack) {
+ if (aStack) {
+ return MakeAndAddRef<JSStackFrame>(aStack);
+ }
+ return nullptr;
+}
+
+} // namespace exceptions
+} // namespace mozilla::dom
diff --git a/dom/bindings/Exceptions.h b/dom/bindings/Exceptions.h
new file mode 100644
index 0000000000..19084aee2f
--- /dev/null
+++ b/dom/bindings/Exceptions.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_Exceptions_h__
+#define mozilla_dom_Exceptions_h__
+
+// DOM exception throwing machinery (for both main thread and workers).
+
+#include <stdint.h>
+#include "jspubtd.h"
+#include "nsString.h"
+#include "jsapi.h"
+
+class nsIStackFrame;
+class nsPIDOMWindowInner;
+template <class T>
+struct already_AddRefed;
+
+namespace mozilla::dom {
+
+class Exception;
+
+// If we're throwing a DOMException and message is empty, the default
+// message for the nsresult in question will be used.
+bool Throw(JSContext* cx, nsresult rv, const nsACString& message = ""_ns);
+
+// Create, throw and report an exception to a given window.
+void ThrowAndReport(nsPIDOMWindowInner* aWindow, nsresult aRv);
+
+// Both signatures of ThrowExceptionObject guarantee that an exception is set on
+// aCx before they return.
+void ThrowExceptionObject(JSContext* aCx, Exception* aException);
+
+// Create an exception object for the given nsresult and message. If we're
+// throwing a DOMException and aMessage is empty, the default message for the
+// nsresult in question will be used.
+//
+// This never returns null.
+already_AddRefed<Exception> CreateException(nsresult aRv,
+ const nsACString& aMessage = ""_ns);
+
+// aMaxDepth can be used to define a maximal depth for the stack trace. If the
+// value is -1, a default maximal depth will be selected. Will return null if
+// there is no JS stack right now.
+already_AddRefed<nsIStackFrame> GetCurrentJSStack(int32_t aMaxDepth = -1);
+
+// Internal stuff not intended to be widely used.
+namespace exceptions {
+
+already_AddRefed<nsIStackFrame> CreateStack(JSContext* aCx,
+ JS::StackCapture&& aCaptureMode);
+
+// Like the above, but creates a JSStackFrame wrapper for an existing
+// JS::SavedFrame object, passed as aStack.
+already_AddRefed<nsIStackFrame> CreateStack(JSContext* aCx,
+ JS::Handle<JSObject*> aStack);
+
+} // namespace exceptions
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/bindings/FakeString.h b/dom/bindings/FakeString.h
new file mode 100644
index 0000000000..f51f7890d9
--- /dev/null
+++ b/dom/bindings/FakeString.h
@@ -0,0 +1,267 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_FakeString_h__
+#define mozilla_dom_FakeString_h__
+
+#include "nsString.h"
+#include "nsStringBuffer.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Span.h"
+#include "js/String.h"
+#include "nsTStringRepr.h"
+
+namespace mozilla::dom::binding_detail {
+// A struct that has a layout compatible with nsAString, so that
+// reinterpret-casting a FakeString as a const nsAString is safe, but much
+// faster constructor and destructor behavior. FakeString uses inline storage
+// for small strings and an nsStringBuffer for longer strings. It can also
+// point to a literal (static-lifetime) string that's compiled into the binary,
+// or point at the buffer of an nsAString whose lifetime is longer than that of
+// the FakeString.
+template <typename CharT>
+struct FakeString {
+ using char_type = CharT;
+ using string_type = nsTString<CharT>;
+ using size_type = typename string_type::size_type;
+ using DataFlags = typename string_type::DataFlags;
+ using ClassFlags = typename string_type::ClassFlags;
+ using AString = nsTSubstring<CharT>;
+ using LengthStorage = mozilla::detail::nsTStringLengthStorage<CharT>;
+
+ static const size_t kInlineCapacity = 64;
+ using AutoString = nsTAutoStringN<CharT, kInlineCapacity>;
+
+ FakeString()
+ : mDataFlags(DataFlags::TERMINATED),
+ mClassFlags(ClassFlags::INLINE),
+ mInlineCapacity(kInlineCapacity - 1) {}
+
+ ~FakeString() {
+ if (mDataFlags & DataFlags::REFCOUNTED) {
+ MOZ_ASSERT(mDataInitialized);
+ nsStringBuffer::FromData(mData)->Release();
+ }
+ }
+
+ // Share aString's string buffer, if it has one; otherwise, make this string
+ // depend upon aString's data. aString should outlive this instance of
+ // FakeString.
+ void ShareOrDependUpon(const AString& aString) {
+ RefPtr<nsStringBuffer> sharedBuffer = nsStringBuffer::FromString(aString);
+ if (!sharedBuffer) {
+ InitData(aString.BeginReading(), aString.Length());
+ if (!aString.IsTerminated()) {
+ mDataFlags &= ~DataFlags::TERMINATED;
+ }
+ } else {
+ AssignFromStringBuffer(sharedBuffer.forget(), aString.Length());
+ }
+ }
+
+ void Truncate() { InitData(string_type::char_traits::sEmptyBuffer, 0); }
+
+ void SetIsVoid(bool aValue) {
+ MOZ_ASSERT(aValue, "We don't support SetIsVoid(false) on FakeString!");
+ Truncate();
+ mDataFlags |= DataFlags::VOIDED;
+ }
+
+ char_type* BeginWriting() {
+ MOZ_ASSERT(IsMutable());
+ MOZ_ASSERT(mDataInitialized);
+ return mData;
+ }
+
+ size_type Length() const { return mLength; }
+
+ operator mozilla::Span<const char_type>() const {
+ MOZ_ASSERT(mDataInitialized);
+ // Explicitly specify template argument here to avoid instantiating
+ // Span<char_type> first and then implicitly converting to Span<const
+ // char_type>
+ return mozilla::Span<const char_type>{mData, Length()};
+ }
+
+ mozilla::Result<mozilla::BulkWriteHandle<CharT>, nsresult> BulkWrite(
+ size_type aCapacity, size_type aPrefixToPreserve, bool aAllowShrinking) {
+ MOZ_ASSERT(!mDataInitialized);
+ InitData(mStorage, 0);
+ mDataFlags |= DataFlags::INLINE;
+ return ToAStringPtr()->BulkWrite(aCapacity, aPrefixToPreserve,
+ aAllowShrinking);
+ }
+
+ // Reserve space to write aLength chars, not including null-terminator.
+ bool SetLength(size_type aLength, mozilla::fallible_t const&) {
+ // Use mStorage for small strings.
+ if (aLength < kInlineCapacity) {
+ InitData(mStorage, aLength);
+ mDataFlags |= DataFlags::INLINE;
+ } else {
+ RefPtr<nsStringBuffer> buf =
+ nsStringBuffer::Alloc((aLength + 1) * sizeof(char_type));
+ if (MOZ_UNLIKELY(!buf)) {
+ return false;
+ }
+
+ AssignFromStringBuffer(buf.forget(), aLength);
+ }
+
+ MOZ_ASSERT(mDataInitialized);
+ mData[mLength] = char_type(0);
+ return true;
+ }
+
+ // Returns false on allocation failure.
+ bool EnsureMutable() {
+ MOZ_ASSERT(mDataInitialized);
+
+ if (IsMutable()) {
+ return true;
+ }
+
+ RefPtr<nsStringBuffer> buffer;
+ if (mDataFlags & DataFlags::REFCOUNTED) {
+ // Make sure we'll drop it when we're done.
+ buffer = dont_AddRef(nsStringBuffer::FromData(mData));
+ // And make sure we don't release it twice by accident.
+ }
+ const char_type* oldChars = mData;
+
+ mDataFlags = DataFlags::TERMINATED;
+#ifdef DEBUG
+ // Reset mDataInitialized because we're explicitly reinitializing
+ // it via the SetLength call.
+ mDataInitialized = false;
+#endif // DEBUG
+ // SetLength will make sure we have our own buffer to work with. Note that
+ // we may be transitioning from having a (short) readonly stringbuffer to
+ // our inline storage or whatnot. That's all fine; SetLength is responsible
+ // for setting up our flags correctly.
+ if (!SetLength(Length(), fallible)) {
+ return false;
+ }
+ MOZ_ASSERT(oldChars != mData, "Should have new chars now!");
+ MOZ_ASSERT(IsMutable(), "Why are we still not mutable?");
+ memcpy(mData, oldChars, Length() * sizeof(char_type));
+ return true;
+ }
+
+ void AssignFromStringBuffer(already_AddRefed<nsStringBuffer> aBuffer,
+ size_t aLength) {
+ InitData(static_cast<char_type*>(aBuffer.take()->Data()), aLength);
+ mDataFlags |= DataFlags::REFCOUNTED;
+ }
+
+ // The preferred way to assign literals to a FakeString. This should only be
+ // called with actual C++ literal strings (i.e. u"stuff") or character arrays
+ // that originally come from passing such literal strings.
+ template <int N>
+ void AssignLiteral(const char_type (&aData)[N]) {
+ AssignLiteral(aData, N - 1);
+ }
+
+ // Assign a literal to a FakeString when it's not an actual literal
+ // in the code, but is known to be a literal somehow (e.g. it came
+ // from an nsAString that tested true for IsLiteral()).
+ void AssignLiteral(const char_type* aData, size_t aLength) {
+ InitData(aData, aLength);
+ mDataFlags |= DataFlags::LITERAL;
+ }
+
+ // If this ever changes, change the corresponding code in the
+ // Optional<nsA[C]String> specialization as well.
+ const AString* ToAStringPtr() const {
+ return reinterpret_cast<const string_type*>(this);
+ }
+
+ operator const AString&() const { return *ToAStringPtr(); }
+
+ private:
+ AString* ToAStringPtr() { return reinterpret_cast<string_type*>(this); }
+
+ // mData is left uninitialized for optimization purposes.
+ MOZ_INIT_OUTSIDE_CTOR char_type* mData;
+ // mLength is left uninitialized for optimization purposes.
+ MOZ_INIT_OUTSIDE_CTOR uint32_t mLength;
+ DataFlags mDataFlags;
+ const ClassFlags mClassFlags;
+
+ const uint32_t mInlineCapacity;
+ char_type mStorage[kInlineCapacity];
+#ifdef DEBUG
+ bool mDataInitialized = false;
+#endif // DEBUG
+
+ FakeString(const FakeString& other) = delete;
+ void operator=(const FakeString& other) = delete;
+
+ void InitData(const char_type* aData, size_type aLength) {
+ MOZ_ASSERT(aLength <= LengthStorage::kMax, "string is too large");
+ MOZ_ASSERT(mDataFlags == DataFlags::TERMINATED);
+ MOZ_ASSERT(!mDataInitialized);
+ mData = const_cast<char_type*>(aData);
+ mLength = uint32_t(aLength);
+#ifdef DEBUG
+ mDataInitialized = true;
+#endif // DEBUG
+ }
+
+ bool IsMutable() {
+ return (mDataFlags & DataFlags::INLINE) ||
+ ((mDataFlags & DataFlags::REFCOUNTED) &&
+ !nsStringBuffer::FromData(mData)->IsReadonly());
+ }
+
+ friend class NonNull<AString>;
+
+ // A class to use for our static asserts to ensure our object layout
+ // matches that of nsString.
+ class StringAsserter;
+ friend class StringAsserter;
+
+ class StringAsserter : public AutoString {
+ public:
+ static void StaticAsserts() {
+ static_assert(sizeof(AutoString) == sizeof(FakeString),
+ "Should be binary compatible with nsTAutoString");
+ static_assert(
+ offsetof(FakeString, mInlineCapacity) == sizeof(string_type),
+ "FakeString should include all nsString members");
+ static_assert(
+ offsetof(FakeString, mData) == offsetof(StringAsserter, mData),
+ "Offset of mData should match");
+ static_assert(
+ offsetof(FakeString, mLength) == offsetof(StringAsserter, mLength),
+ "Offset of mLength should match");
+ static_assert(offsetof(FakeString, mDataFlags) ==
+ offsetof(StringAsserter, mDataFlags),
+ "Offset of mDataFlags should match");
+ static_assert(offsetof(FakeString, mClassFlags) ==
+ offsetof(StringAsserter, mClassFlags),
+ "Offset of mClassFlags should match");
+ static_assert(offsetof(FakeString, mInlineCapacity) ==
+ offsetof(StringAsserter, mInlineCapacity),
+ "Offset of mInlineCapacity should match");
+ static_assert(
+ offsetof(FakeString, mStorage) == offsetof(StringAsserter, mStorage),
+ "Offset of mStorage should match");
+ static_assert(JS::MaxStringLength <= LengthStorage::kMax,
+ "JS::MaxStringLength fits in a nsTString");
+ }
+ };
+};
+} // namespace mozilla::dom::binding_detail
+
+template <typename CharT>
+inline void AssignFromStringBuffer(
+ nsStringBuffer* aBuffer, size_t aLength,
+ mozilla::dom::binding_detail::FakeString<CharT>& aDest) {
+ aDest.AssignFromStringBuffer(do_AddRef(aBuffer), aLength);
+}
+
+#endif /* mozilla_dom_FakeString_h__ */
diff --git a/dom/bindings/GenerateCSS2PropertiesWebIDL.py b/dom/bindings/GenerateCSS2PropertiesWebIDL.py
new file mode 100644
index 0000000000..b74d8ef92d
--- /dev/null
+++ b/dom/bindings/GenerateCSS2PropertiesWebIDL.py
@@ -0,0 +1,104 @@
+# 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/.
+
+import argparse
+import runpy
+import string
+import sys
+
+# Generates a line of WebIDL with the given spelling of the property name
+# (whether camelCase, _underscorePrefixed, etc.) and the given array of
+# extended attributes.
+
+
+def generateLine(propName, extendedAttrs):
+ return " [%s] attribute [LegacyNullToEmptyString] UTF8String %s;\n" % (
+ ", ".join(extendedAttrs),
+ propName,
+ )
+
+
+def generate(output, idlFilename, dataFile):
+ propList = runpy.run_path(dataFile)["data"]
+ props = ""
+ for p in propList:
+ if "Internal" in p.flags:
+ continue
+
+ # Skip properties which aren't valid in style rules.
+ if "Style" not in p.rules:
+ continue
+
+ # Unfortunately, even some of the getters here are fallible
+ # (e.g. on nsComputedDOMStyle).
+ extendedAttrs = [
+ "CEReactions",
+ "Throws",
+ "SetterNeedsSubjectPrincipal=NonSystem",
+ ]
+
+ if p.pref != "":
+ # BackdropFilter is a special case where we want WebIDL to check
+ # a function instead of checking the pref directly.
+ if p.method == "BackdropFilter":
+ extendedAttrs.append('Func="nsCSSProps::IsBackdropFilterAvailable"')
+ else:
+ extendedAttrs.append('Pref="%s"' % p.pref)
+
+ prop = p.method
+
+ # webkit properties get a camelcase "webkitFoo" accessor
+ # as well as a capitalized "WebkitFoo" alias (added here).
+ if prop.startswith("Webkit"):
+ extendedAttrs.append('BindingAlias="%s"' % prop)
+
+ # Generate a name with camelCase spelling of property-name (or capitalized,
+ # for Moz-prefixed properties):
+ if not prop.startswith("Moz"):
+ prop = prop[0].lower() + prop[1:]
+
+ # Per spec, what's actually supposed to happen here is that we're supposed
+ # to have properties for:
+ #
+ # 1) Each supported CSS property name, camelCased.
+ # 2) Each supported name that contains or starts with dashes,
+ # without any changes to the name.
+ # 3) cssFloat
+ #
+ # Note that "float" will cause a property called "float" to exist due to (1)
+ # in that list.
+ #
+ # In practice, cssFloat is the only case in which "name" doesn't contain
+ # "-" but also doesn't match "prop". So the generateLine() call will
+ # cover (3) and all of (1) except "float". If we now add an alias
+ # for all the cases where "name" doesn't match "prop", that will cover
+ # "float" and (2).
+ if prop != p.name:
+ extendedAttrs.append('BindingAlias="%s"' % p.name)
+
+ props += generateLine(prop, extendedAttrs)
+
+ idlFile = open(idlFilename, "r")
+ idlTemplate = idlFile.read()
+ idlFile.close()
+
+ output.write(
+ "/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */\n\n"
+ + string.Template(idlTemplate).substitute({"props": props})
+ + "\n"
+ )
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("idlFilename", help="IDL property file template")
+ parser.add_argument(
+ "preprocessorHeader", help="Header file to pass through the preprocessor"
+ )
+ args = parser.parse_args()
+ generate(sys.stdout, args.idlFilename, args.preprocessorHeader)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/dom/bindings/IterableIterator.cpp b/dom/bindings/IterableIterator.cpp
new file mode 100644
index 0000000000..d0f2e5d227
--- /dev/null
+++ b/dom/bindings/IterableIterator.cpp
@@ -0,0 +1,338 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/IterableIterator.h"
+#include "mozilla/dom/Promise-inl.h"
+
+namespace mozilla::dom {
+
+// Due to IterableIterator being a templated class, we implement the necessary
+// CC bits in a superclass that IterableIterator then inherits from. This allows
+// us to put the macros outside of the header. The base class has pure virtual
+// functions for Traverse/Unlink that the templated subclasses will override.
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(IterableIteratorBase)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IterableIteratorBase)
+ tmp->TraverseHelper(cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IterableIteratorBase)
+ tmp->UnlinkHelper();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+namespace iterator_utils {
+
+void DictReturn(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
+ bool aDone, JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
+ RootedDictionary<IterableKeyOrValueResult> dict(aCx);
+ dict.mDone = aDone;
+ dict.mValue = aValue;
+ JS::Rooted<JS::Value> dictValue(aCx);
+ if (!ToJSValue(aCx, dict, &dictValue)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ aResult.set(dictValue);
+}
+
+void DictReturn(JSContext* aCx, JS::MutableHandle<JSObject*> aResult,
+ bool aDone, JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
+ JS::Rooted<JS::Value> dictValue(aCx);
+ DictReturn(aCx, &dictValue, aDone, aValue, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ aResult.set(&dictValue.toObject());
+}
+
+void KeyAndValueReturn(JSContext* aCx, JS::Handle<JS::Value> aKey,
+ JS::Handle<JS::Value> aValue,
+ JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv) {
+ RootedDictionary<IterableKeyAndValueResult> dict(aCx);
+ dict.mDone = false;
+ // Dictionary values are a Sequence, which is a FallibleTArray, so we need
+ // to check returns when appending.
+ if (!dict.mValue.AppendElement(aKey, mozilla::fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ if (!dict.mValue.AppendElement(aValue, mozilla::fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ JS::Rooted<JS::Value> dictValue(aCx);
+ if (!ToJSValue(aCx, dict, &dictValue)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ aResult.set(&dictValue.toObject());
+}
+
+} // namespace iterator_utils
+
+namespace binding_detail {
+
+static already_AddRefed<Promise> PromiseOrErr(
+ Result<RefPtr<Promise>, nsresult>&& aResult, ErrorResult& aError) {
+ if (aResult.isErr()) {
+ aError.Throw(aResult.unwrapErr());
+ return nullptr;
+ }
+
+ return aResult.unwrap().forget();
+}
+
+already_AddRefed<Promise> AsyncIterableNextImpl::NextSteps(
+ JSContext* aCx, AsyncIterableIteratorBase* aObject,
+ nsIGlobalObject* aGlobalObject, ErrorResult& aRv) {
+ // 2. If object’s is finished is true, then:
+ if (aObject->mIsFinished) {
+ // 1. Let result be CreateIterResultObject(undefined, true).
+ JS::Rooted<JS::Value> dict(aCx);
+ iterator_utils::DictReturn(aCx, &dict, true, JS::UndefinedHandleValue, aRv);
+ if (aRv.Failed()) {
+ return Promise::CreateRejectedWithErrorResult(aGlobalObject, aRv);
+ }
+
+ // 2. Perform ! Call(nextPromiseCapability.[[Resolve]], undefined,
+ // «result»).
+ // 3. Return nextPromiseCapability.[[Promise]].
+ return Promise::Resolve(aGlobalObject, aCx, dict, aRv);
+ }
+
+ // 4. Let nextPromise be the result of getting the next iteration result with
+ // object’s target and object.
+ RefPtr<Promise> nextPromise;
+ {
+ ErrorResult error;
+ nextPromise = GetNextResult(error);
+
+ error.WouldReportJSException();
+ if (error.Failed()) {
+ nextPromise = Promise::Reject(aGlobalObject, std::move(error), aRv);
+ }
+ }
+
+ // 5. Let fulfillSteps be the following steps, given next:
+ auto fulfillSteps = [](JSContext* aCx, JS::Handle<JS::Value> aNext,
+ ErrorResult& aRv,
+ const RefPtr<AsyncIterableIteratorBase>& aObject,
+ const nsCOMPtr<nsIGlobalObject>& aGlobalObject)
+ -> already_AddRefed<Promise> {
+ // 1. Set object’s ongoing promise to null.
+ aObject->mOngoingPromise = nullptr;
+
+ // 2. If next is end of iteration, then:
+ JS::Rooted<JS::Value> dict(aCx);
+ if (aNext.isMagic(binding_details::END_OF_ITERATION)) {
+ // 1. Set object’s is finished to true.
+ aObject->mIsFinished = true;
+ // 2. Return CreateIterResultObject(undefined, true).
+ iterator_utils::DictReturn(aCx, &dict, true, JS::UndefinedHandleValue,
+ aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ } else {
+ // 3. Otherwise, if interface has a pair asynchronously iterable
+ // declaration:
+ // 1. Assert: next is a value pair.
+ // 2. Return the iterator result for next and kind.
+ // 4. Otherwise:
+ // 1. Assert: interface has a value asynchronously iterable declaration.
+ // 2. Assert: next is a value of the type that appears in the
+ // declaration.
+ // 3. Let value be next, converted to an ECMAScript value.
+ // 4. Return CreateIterResultObject(value, false).
+ iterator_utils::DictReturn(aCx, &dict, false, aNext, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ }
+ // Note that ThenCatchWithCycleCollectedArgs expects a Promise, so
+ // we use Promise::Resolve here. The specs do convert this to a
+ // promise too at another point, but the end result should be the
+ // same.
+ return Promise::Resolve(aGlobalObject, aCx, dict, aRv);
+ };
+ // 7. Let rejectSteps be the following steps, given reason:
+ auto rejectSteps = [](JSContext* aCx, JS::Handle<JS::Value> aReason,
+ ErrorResult& aRv,
+ const RefPtr<AsyncIterableIteratorBase>& aObject,
+ const nsCOMPtr<nsIGlobalObject>& aGlobalObject) {
+ // 1. Set object’s ongoing promise to null.
+ aObject->mOngoingPromise = nullptr;
+ // 2. Set object’s is finished to true.
+ aObject->mIsFinished = true;
+ // 3. Throw reason.
+ return Promise::Reject(aGlobalObject, aCx, aReason, aRv);
+ };
+ // 9. Perform PerformPromiseThen(nextPromise, onFulfilled, onRejected,
+ // nextPromiseCapability).
+ Result<RefPtr<Promise>, nsresult> result =
+ nextPromise->ThenCatchWithCycleCollectedArgs(
+ std::move(fulfillSteps), std::move(rejectSteps), RefPtr{aObject},
+ nsCOMPtr{aGlobalObject});
+
+ // 10. Return nextPromiseCapability.[[Promise]].
+ return PromiseOrErr(std::move(result), aRv);
+}
+
+already_AddRefed<Promise> AsyncIterableNextImpl::Next(
+ JSContext* aCx, AsyncIterableIteratorBase* aObject,
+ nsISupports* aGlobalObject, ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> globalObject = do_QueryInterface(aGlobalObject);
+
+ // 3.7.10.2. Asynchronous iterator prototype object
+ // …
+ // 10. If ongoingPromise is not null, then:
+ if (aObject->mOngoingPromise) {
+ // 1. Let afterOngoingPromiseCapability be
+ // ! NewPromiseCapability(%Promise%).
+ // 2. Let onSettled be CreateBuiltinFunction(nextSteps, « »).
+
+ // aObject is the same object as 'this', so it's fine to capture 'this'
+ // without taking a strong reference, because we already take a strong
+ // reference to it through aObject.
+ auto onSettled = [this](JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv,
+ const RefPtr<AsyncIterableIteratorBase>& aObject,
+ const nsCOMPtr<nsIGlobalObject>& aGlobalObject)
+ MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ return NextSteps(aCx, aObject, aGlobalObject, aRv);
+ };
+
+ // 3. Perform PerformPromiseThen(ongoingPromise, onSettled, onSettled,
+ // afterOngoingPromiseCapability).
+ Result<RefPtr<Promise>, nsresult> afterOngoingPromise =
+ aObject->mOngoingPromise->ThenCatchWithCycleCollectedArgs(
+ onSettled, onSettled, RefPtr{aObject}, std::move(globalObject));
+ if (afterOngoingPromise.isErr()) {
+ aRv.Throw(afterOngoingPromise.unwrapErr());
+ return nullptr;
+ }
+
+ // 4. Set object’s ongoing promise to
+ // afterOngoingPromiseCapability.[[Promise]].
+ aObject->mOngoingPromise = afterOngoingPromise.unwrap().forget();
+ } else {
+ // 11. Otherwise:
+ // 1. Set object’s ongoing promise to the result of running nextSteps.
+ aObject->mOngoingPromise = NextSteps(aCx, aObject, globalObject, aRv);
+ }
+
+ // 12. Return object’s ongoing promise.
+ return do_AddRef(aObject->mOngoingPromise);
+}
+
+already_AddRefed<Promise> AsyncIterableReturnImpl::ReturnSteps(
+ JSContext* aCx, AsyncIterableIteratorBase* aObject,
+ nsIGlobalObject* aGlobalObject, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ // 2. If object’s is finished is true, then:
+ if (aObject->mIsFinished) {
+ // 1. Let result be CreateIterResultObject(value, true).
+ JS::Rooted<JS::Value> dict(aCx);
+ iterator_utils::DictReturn(aCx, &dict, true, aValue, aRv);
+ if (aRv.Failed()) {
+ return Promise::CreateRejectedWithErrorResult(aGlobalObject, aRv);
+ }
+
+ // 2. Perform ! Call(returnPromiseCapability.[[Resolve]], undefined,
+ // «result»).
+ // 3. Return returnPromiseCapability.[[Promise]].
+ return Promise::Resolve(aGlobalObject, aCx, dict, aRv);
+ }
+
+ // 3. Set object’s is finished to true.
+ aObject->mIsFinished = true;
+
+ // 4. Return the result of running the asynchronous iterator return algorithm
+ // for interface, given object’s target, object, and value.
+ ErrorResult error;
+ RefPtr<Promise> returnPromise = GetReturnPromise(aCx, aValue, error);
+
+ error.WouldReportJSException();
+ if (error.Failed()) {
+ return Promise::Reject(aGlobalObject, std::move(error), aRv);
+ }
+
+ return returnPromise.forget();
+}
+
+already_AddRefed<Promise> AsyncIterableReturnImpl::Return(
+ JSContext* aCx, AsyncIterableIteratorBase* aObject,
+ nsISupports* aGlobalObject, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> globalObject = do_QueryInterface(aGlobalObject);
+
+ // 3.7.10.2. Asynchronous iterator prototype object
+ // …
+ RefPtr<Promise> returnStepsPromise;
+ // 11. If ongoingPromise is not null, then:
+ if (aObject->mOngoingPromise) {
+ // 1. Let afterOngoingPromiseCapability be
+ // ! NewPromiseCapability(%Promise%).
+ // 2. Let onSettled be CreateBuiltinFunction(returnSteps, « »).
+
+ // aObject is the same object as 'this', so it's fine to capture 'this'
+ // without taking a strong reference, because we already take a strong
+ // reference to it through aObject.
+ auto onSettled =
+ [this](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ const RefPtr<AsyncIterableIteratorBase>& aObject,
+ const nsCOMPtr<nsIGlobalObject>& aGlobalObject,
+ JS::Handle<JS::Value> aVal) MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ return ReturnSteps(aCx, aObject, aGlobalObject, aVal, aRv);
+ };
+
+ // 3. Perform PerformPromiseThen(ongoingPromise, onSettled, onSettled,
+ // afterOngoingPromiseCapability).
+ Result<RefPtr<Promise>, nsresult> afterOngoingPromise =
+ aObject->mOngoingPromise->ThenCatchWithCycleCollectedArgsJS(
+ onSettled, onSettled,
+ std::make_tuple(RefPtr{aObject}, nsCOMPtr{globalObject}),
+ std::make_tuple(aValue));
+ if (afterOngoingPromise.isErr()) {
+ aRv.Throw(afterOngoingPromise.unwrapErr());
+ return nullptr;
+ }
+
+ // 4. Set returnStepsPromise to afterOngoingPromiseCapability.[[Promise]].
+ returnStepsPromise = afterOngoingPromise.unwrap().forget();
+ } else {
+ // 12. Otherwise:
+ // 1. Set returnStepsPromise to the result of running returnSteps.
+ returnStepsPromise = ReturnSteps(aCx, aObject, globalObject, aValue, aRv);
+ }
+
+ // 13. Let fulfillSteps be the following steps:
+ auto onFullFilled = [](JSContext* aCx, JS::Handle<JS::Value>,
+ ErrorResult& aRv,
+ const nsCOMPtr<nsIGlobalObject>& aGlobalObject,
+ JS::Handle<JS::Value> aVal) {
+ // 1. Return CreateIterResultObject(value, true).
+ JS::Rooted<JS::Value> dict(aCx);
+ iterator_utils::DictReturn(aCx, &dict, true, aVal, aRv);
+ return Promise::Resolve(aGlobalObject, aCx, dict, aRv);
+ };
+
+ // 14. Let onFulfilled be CreateBuiltinFunction(fulfillSteps, « »).
+ // 15. Perform PerformPromiseThen(returnStepsPromise, onFulfilled, undefined,
+ // returnPromiseCapability).
+ Result<RefPtr<Promise>, nsresult> returnPromise =
+ returnStepsPromise->ThenWithCycleCollectedArgsJS(
+ onFullFilled, std::make_tuple(std::move(globalObject)),
+ std::make_tuple(aValue));
+
+ // 16. Return returnPromiseCapability.[[Promise]].
+ return PromiseOrErr(std::move(returnPromise), aRv);
+}
+
+} // namespace binding_detail
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/IterableIterator.h b/dom/bindings/IterableIterator.h
new file mode 100644
index 0000000000..db9969da37
--- /dev/null
+++ b/dom/bindings/IterableIterator.h
@@ -0,0 +1,435 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/**
+ * The IterableIterator class is used for WebIDL interfaces that have a
+ * iterable<> member defined with two types (so a pair iterator). It handles
+ * the ES6 Iterator-like functions that are generated for the iterable
+ * interface.
+ *
+ * For iterable interfaces with a pair iterator, the implementation class will
+ * need to implement these two functions:
+ *
+ * - size_t GetIterableLength()
+ * - Returns the number of elements available to iterate over
+ * - [type] GetValueAtIndex(size_t index)
+ * - Returns the value at the requested index.
+ * - [type] GetKeyAtIndex(size_t index)
+ * - Returns the key at the requested index
+ *
+ * Examples of iterable interface implementations can be found in the bindings
+ * test directory.
+ */
+
+#ifndef mozilla_dom_IterableIterator_h
+#define mozilla_dom_IterableIterator_h
+
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "nsISupports.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/dom/IterableIteratorBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/WeakPtr.h"
+
+namespace mozilla::dom {
+
+namespace binding_details {
+
+// JS::MagicValue(END_OF_ITERATION) is the value we use for
+// https://webidl.spec.whatwg.org/#end-of-iteration. It shouldn't be returned to
+// JS, because AsyncIterableIteratorBase::NextSteps will detect it and will
+// return the result of CreateIterResultObject(undefined, true) instead
+// (discarding the magic value).
+static const JSWhyMagic END_OF_ITERATION = JS_GENERIC_MAGIC;
+
+} // namespace binding_details
+
+namespace iterator_utils {
+
+void DictReturn(JSContext* aCx, JS::MutableHandle<JSObject*> aResult,
+ bool aDone, JS::Handle<JS::Value> aValue, ErrorResult& aRv);
+
+void KeyAndValueReturn(JSContext* aCx, JS::Handle<JS::Value> aKey,
+ JS::Handle<JS::Value> aValue,
+ JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv);
+
+inline void ResolvePromiseForFinished(Promise* aPromise) {
+ aPromise->MaybeResolve(JS::MagicValue(binding_details::END_OF_ITERATION));
+}
+
+template <typename Key, typename Value>
+void ResolvePromiseWithKeyAndValue(Promise* aPromise, const Key& aKey,
+ const Value& aValue) {
+ aPromise->MaybeResolve(std::make_tuple(aKey, aValue));
+}
+
+} // namespace iterator_utils
+
+class IterableIteratorBase {
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(IterableIteratorBase)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(IterableIteratorBase)
+
+ typedef enum { Keys = 0, Values, Entries } IteratorType;
+
+ IterableIteratorBase() = default;
+
+ protected:
+ virtual ~IterableIteratorBase() = default;
+ virtual void UnlinkHelper() = 0;
+ virtual void TraverseHelper(nsCycleCollectionTraversalCallback& cb) = 0;
+};
+
+// Helpers to call iterator getter methods with the correct arguments, depending
+// on the types they return, and convert the result to JS::Values.
+
+// Helper for Get[Key,Value]AtIndex(uint32_t) methods, which accept an index and
+// return a type supported by ToJSValue.
+template <typename T, typename U>
+bool CallIterableGetter(JSContext* aCx, U (T::*aMethod)(uint32_t), T* aInst,
+ uint32_t aIndex, JS::MutableHandle<JS::Value> aResult) {
+ return ToJSValue(aCx, (aInst->*aMethod)(aIndex), aResult);
+}
+
+template <typename T, typename U>
+bool CallIterableGetter(JSContext* aCx, U (T::*aMethod)(uint32_t) const,
+ const T* aInst, uint32_t aIndex,
+ JS::MutableHandle<JS::Value> aResult) {
+ return ToJSValue(aCx, (aInst->*aMethod)(aIndex), aResult);
+}
+
+// Helper for Get[Key,Value]AtIndex(JSContext*, uint32_t, MutableHandleValue)
+// methods, which accept a JS context, index, and mutable result value handle,
+// and return true on success or false on failure.
+template <typename T>
+bool CallIterableGetter(JSContext* aCx,
+ bool (T::*aMethod)(JSContext*, uint32_t,
+ JS::MutableHandle<JS::Value>),
+ T* aInst, uint32_t aIndex,
+ JS::MutableHandle<JS::Value> aResult) {
+ return (aInst->*aMethod)(aCx, aIndex, aResult);
+}
+
+template <typename T>
+bool CallIterableGetter(JSContext* aCx,
+ bool (T::*aMethod)(JSContext*, uint32_t,
+ JS::MutableHandle<JS::Value>) const,
+ const T* aInst, uint32_t aIndex,
+ JS::MutableHandle<JS::Value> aResult) {
+ return (aInst->*aMethod)(aCx, aIndex, aResult);
+}
+
+template <typename T>
+class IterableIterator : public IterableIteratorBase {
+ public:
+ IterableIterator(T* aIterableObj, IteratorType aIteratorType)
+ : mIterableObj(aIterableObj), mIteratorType(aIteratorType), mIndex(0) {
+ MOZ_ASSERT(mIterableObj);
+ }
+
+ bool GetKeyAtIndex(JSContext* aCx, uint32_t aIndex,
+ JS::MutableHandle<JS::Value> aResult) {
+ return CallIterableGetter(aCx, &T::GetKeyAtIndex, mIterableObj.get(),
+ aIndex, aResult);
+ }
+
+ bool GetValueAtIndex(JSContext* aCx, uint32_t aIndex,
+ JS::MutableHandle<JS::Value> aResult) {
+ return CallIterableGetter(aCx, &T::GetValueAtIndex, mIterableObj.get(),
+ aIndex, aResult);
+ }
+
+ void Next(JSContext* aCx, JS::MutableHandle<JSObject*> aResult,
+ ErrorResult& aRv) {
+ JS::Rooted<JS::Value> value(aCx, JS::UndefinedValue());
+ if (mIndex >= this->mIterableObj->GetIterableLength()) {
+ iterator_utils::DictReturn(aCx, aResult, true, value, aRv);
+ return;
+ }
+ switch (mIteratorType) {
+ case IteratorType::Keys: {
+ if (!GetKeyAtIndex(aCx, mIndex, &value)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ iterator_utils::DictReturn(aCx, aResult, false, value, aRv);
+ break;
+ }
+ case IteratorType::Values: {
+ if (!GetValueAtIndex(aCx, mIndex, &value)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ iterator_utils::DictReturn(aCx, aResult, false, value, aRv);
+ break;
+ }
+ case IteratorType::Entries: {
+ JS::Rooted<JS::Value> key(aCx);
+ if (!GetKeyAtIndex(aCx, mIndex, &key)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ if (!GetValueAtIndex(aCx, mIndex, &value)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ iterator_utils::KeyAndValueReturn(aCx, key, value, aResult, aRv);
+ break;
+ }
+ default:
+ MOZ_CRASH("Invalid iterator type!");
+ }
+ ++mIndex;
+ }
+
+ protected:
+ virtual ~IterableIterator() = default;
+
+ // Since we're templated on a binding, we need to possibly CC it, but can't do
+ // that through macros. So it happens here.
+ void UnlinkHelper() final { mIterableObj = nullptr; }
+
+ virtual void TraverseHelper(nsCycleCollectionTraversalCallback& cb) override {
+ IterableIterator<T>* tmp = this;
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIterableObj);
+ }
+
+ // Binding Implementation object that we're iterating over.
+ RefPtr<T> mIterableObj;
+ // Tells whether this is a key, value, or entries iterator.
+ IteratorType mIteratorType;
+ // Current index of iteration.
+ uint32_t mIndex;
+};
+
+namespace binding_detail {
+
+class AsyncIterableNextImpl;
+class AsyncIterableReturnImpl;
+
+} // namespace binding_detail
+
+class AsyncIterableIteratorBase : public IterableIteratorBase {
+ public:
+ IteratorType GetIteratorType() { return mIteratorType; }
+
+ protected:
+ explicit AsyncIterableIteratorBase(IteratorType aIteratorType)
+ : mIteratorType(aIteratorType) {}
+
+ void UnlinkHelper() override {
+ AsyncIterableIteratorBase* tmp = this;
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOngoingPromise);
+ }
+
+ void TraverseHelper(nsCycleCollectionTraversalCallback& cb) override {
+ AsyncIterableIteratorBase* tmp = this;
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOngoingPromise);
+ }
+
+ private:
+ friend class binding_detail::AsyncIterableNextImpl;
+ friend class binding_detail::AsyncIterableReturnImpl;
+
+ // 3.7.10.1. Default asynchronous iterator objects
+ // Target is in AsyncIterableIterator
+ // Kind
+ IteratorType mIteratorType;
+ // Ongoing promise
+ RefPtr<Promise> mOngoingPromise;
+ // Is finished
+ bool mIsFinished = false;
+};
+
+template <typename T>
+class AsyncIterableIterator : public AsyncIterableIteratorBase {
+ private:
+ using IteratorData = typename T::IteratorData;
+
+ public:
+ AsyncIterableIterator(T* aIterableObj, IteratorType aIteratorType)
+ : AsyncIterableIteratorBase(aIteratorType), mIterableObj(aIterableObj) {
+ MOZ_ASSERT(mIterableObj);
+ }
+
+ IteratorData& Data() { return mData; }
+
+ protected:
+ // We'd prefer to use ImplCycleCollectionTraverse/ImplCycleCollectionUnlink on
+ // the iterator data, but unfortunately that doesn't work because it's
+ // dependent on the template parameter. Instead we detect if the data
+ // structure has Traverse and Unlink functions and call those.
+ template <typename Data>
+ auto TraverseData(Data& aData, nsCycleCollectionTraversalCallback& aCallback,
+ int) -> decltype(aData.Traverse(aCallback)) {
+ return aData.Traverse(aCallback);
+ }
+ template <typename Data>
+ void TraverseData(Data& aData, nsCycleCollectionTraversalCallback& aCallback,
+ double) {}
+
+ template <typename Data>
+ auto UnlinkData(Data& aData, int) -> decltype(aData.Unlink()) {
+ return aData.Unlink();
+ }
+ template <typename Data>
+ void UnlinkData(Data& aData, double) {}
+
+ // Since we're templated on a binding, we need to possibly CC it, but can't do
+ // that through macros. So it happens here.
+ void UnlinkHelper() final {
+ AsyncIterableIteratorBase::UnlinkHelper();
+
+ AsyncIterableIterator<T>* tmp = this;
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mIterableObj);
+ UnlinkData(tmp->mData, 0);
+ }
+
+ void TraverseHelper(nsCycleCollectionTraversalCallback& cb) final {
+ AsyncIterableIteratorBase::TraverseHelper(cb);
+
+ AsyncIterableIterator<T>* tmp = this;
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIterableObj);
+ TraverseData(tmp->mData, cb, 0);
+ }
+
+ // 3.7.10.1. Default asynchronous iterator objects
+ // Target
+ RefPtr<T> mIterableObj;
+ // Kind
+ // Ongoing promise
+ // Is finished
+ // See AsyncIterableIteratorBase
+
+ // Opaque data of the backing object.
+ IteratorData mData;
+};
+
+namespace binding_detail {
+
+template <typename T>
+using IterableIteratorWrapFunc =
+ bool (*)(JSContext* aCx, IterableIterator<T>* aObject,
+ JS::MutableHandle<JSObject*> aReflector);
+
+template <typename T, IterableIteratorWrapFunc<T> WrapFunc>
+class WrappableIterableIterator final : public IterableIterator<T> {
+ public:
+ using IterableIterator<T>::IterableIterator;
+
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aObj) {
+ MOZ_ASSERT(!aGivenProto);
+ return (*WrapFunc)(aCx, this, aObj);
+ }
+};
+
+class AsyncIterableNextImpl {
+ protected:
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Next(
+ JSContext* aCx, AsyncIterableIteratorBase* aObject,
+ nsISupports* aGlobalObject, ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT virtual already_AddRefed<Promise> GetNextResult(
+ ErrorResult& aRv) = 0;
+
+ private:
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> NextSteps(
+ JSContext* aCx, AsyncIterableIteratorBase* aObject,
+ nsIGlobalObject* aGlobalObject, ErrorResult& aRv);
+};
+
+class AsyncIterableReturnImpl {
+ protected:
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Return(
+ JSContext* aCx, AsyncIterableIteratorBase* aObject,
+ nsISupports* aGlobalObject, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT virtual already_AddRefed<Promise> GetReturnPromise(
+ JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) = 0;
+
+ private:
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> ReturnSteps(
+ JSContext* aCx, AsyncIterableIteratorBase* aObject,
+ nsIGlobalObject* aGlobalObject, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv);
+};
+
+template <typename T>
+class AsyncIterableIteratorNoReturn : public AsyncIterableIterator<T>,
+ public AsyncIterableNextImpl {
+ public:
+ using AsyncIterableIterator<T>::AsyncIterableIterator;
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Next(JSContext* aCx,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsISupports> parentObject = this->mIterableObj->GetParentObject();
+ return AsyncIterableNextImpl::Next(aCx, this, parentObject, aRv);
+ }
+
+ protected:
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> GetNextResult(
+ ErrorResult& aRv) override {
+ RefPtr<T> iterableObj(this->mIterableObj);
+ return iterableObj->GetNextIterationResult(
+ static_cast<AsyncIterableIterator<T>*>(this), aRv);
+ }
+};
+
+template <typename T>
+class AsyncIterableIteratorWithReturn : public AsyncIterableIteratorNoReturn<T>,
+ public AsyncIterableReturnImpl {
+ public:
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Return(
+ JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
+ nsCOMPtr<nsISupports> parentObject = this->mIterableObj->GetParentObject();
+ return AsyncIterableReturnImpl::Return(aCx, this, parentObject, aValue,
+ aRv);
+ }
+
+ protected:
+ using AsyncIterableIteratorNoReturn<T>::AsyncIterableIteratorNoReturn;
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> GetReturnPromise(
+ JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) override {
+ RefPtr<T> iterableObj(this->mIterableObj);
+ return iterableObj->IteratorReturn(
+ aCx, static_cast<AsyncIterableIterator<T>*>(this), aValue, aRv);
+ }
+};
+
+template <typename T, bool NeedReturnMethod>
+using AsyncIterableIteratorNative =
+ std::conditional_t<NeedReturnMethod, AsyncIterableIteratorWithReturn<T>,
+ AsyncIterableIteratorNoReturn<T>>;
+
+template <typename T, bool NeedReturnMethod>
+using AsyncIterableIteratorWrapFunc = bool (*)(
+ JSContext* aCx, AsyncIterableIteratorNative<T, NeedReturnMethod>* aObject,
+ JS::MutableHandle<JSObject*> aReflector);
+
+template <typename T, bool NeedReturnMethod,
+ AsyncIterableIteratorWrapFunc<T, NeedReturnMethod> WrapFunc,
+ typename Base = AsyncIterableIteratorNative<T, NeedReturnMethod>>
+class WrappableAsyncIterableIterator final : public Base {
+ public:
+ using Base::Base;
+
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aObj) {
+ MOZ_ASSERT(!aGivenProto);
+ return (*WrapFunc)(aCx, this, aObj);
+ }
+};
+
+} // namespace binding_detail
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_IterableIterator_h
diff --git a/dom/bindings/JSSlots.h b/dom/bindings/JSSlots.h
new file mode 100644
index 0000000000..b9640bdb5c
--- /dev/null
+++ b/dom/bindings/JSSlots.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file defines various reserved slot indices used by JavaScript
+ * reflections of DOM objects.
+ */
+#ifndef mozilla_dom_DOMSlots_h
+#define mozilla_dom_DOMSlots_h
+
+// We use slot 0 for holding the raw object. This is safe for both
+// globals and non-globals.
+// NOTE: This is baked into the Ion JIT as 0 in codegen for LGetDOMProperty and
+// LSetDOMProperty. Those constants need to be changed accordingly if this value
+// changes.
+#define DOM_OBJECT_SLOT 0
+
+// The total number of slots non-proxy DOM objects use by default.
+// Specific objects may have more for storing cached values.
+#define DOM_INSTANCE_RESERVED_SLOTS 1
+
+// Interface objects store a number of reserved slots equal to
+// DOM_INTERFACE_SLOTS_BASE + number of named constructors.
+#define DOM_INTERFACE_SLOTS_BASE 0
+
+// Interface prototype objects store a number of reserved slots equal to
+// DOM_INTERFACE_PROTO_SLOTS_BASE or DOM_INTERFACE_PROTO_SLOTS_BASE + 1 if a
+// slot for the unforgeable holder is needed.
+#define DOM_INTERFACE_PROTO_SLOTS_BASE 0
+
+// The slot index of raw pointer of dom object stored in observable array exotic
+// object. We need this in order to call the OnSet* and OnDelete* callbacks.
+#define OBSERVABLE_ARRAY_DOM_INTERFACE_SLOT 0
+
+// The slot index of backing list stored in observable array exotic object.
+#define OBSERVABLE_ARRAY_BACKING_LIST_OBJECT_SLOT 1
+
+#endif /* mozilla_dom_DOMSlots_h */
diff --git a/dom/bindings/Makefile.in b/dom/bindings/Makefile.in
new file mode 100644
index 0000000000..c2a1766be7
--- /dev/null
+++ b/dom/bindings/Makefile.in
@@ -0,0 +1,53 @@
+# 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/.
+
+webidl_base := $(topsrcdir)/dom/webidl
+
+ifdef COMPILE_ENVIRONMENT
+
+# Akin to GLOBAL_DEPS, but set early enough that webidlsrcs.mk
+# can make use of them as dependencies.
+WEBIDL_PP_DEPS := \
+ backend.mk \
+ Makefile \
+ $(DEPTH)/config/autoconf.mk \
+ $(topsrcdir)/config/config.mk \
+ $(NULL)
+
+# Generated by moz.build
+include webidlsrcs.mk
+
+# These come from webidlsrcs.mk.
+# TODO Write directly into backend.mk (bug 1281618)
+CPPSRCS += $(globalgen_sources) $(unified_binding_cpp_files)
+
+include $(topsrcdir)/config/rules.mk
+
+# Most of the logic for dependencies lives inside Python so it can be
+# used by multiple build backends. We simply have rules to generate
+# and include the .pp file.
+#
+# The generated .pp file contains all the important dependencies such as
+# changes to .webidl or .py files should result in code generation being
+# performed. But we do pull in file-lists.jon to catch file additions.
+codegen_dependencies := \
+ file-lists.json \
+ $(nonstatic_webidl_files) \
+ $(GLOBAL_DEPS) \
+ $(NULL)
+
+export:: webidl.stub
+
+# codegen.pp is created as a side-effect of the webidl action
+-include codegen.pp
+
+webidl.stub: $(codegen_dependencies)
+ $(call py_action,webidl,$(srcdir))
+ @$(TOUCH) $@
+
+.PHONY: compiletests
+compiletests:
+ $(call SUBMAKE,libs,test)
+
+endif
diff --git a/dom/bindings/NonRefcountedDOMObject.h b/dom/bindings/NonRefcountedDOMObject.h
new file mode 100644
index 0000000000..f2b519cc4d
--- /dev/null
+++ b/dom/bindings/NonRefcountedDOMObject.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_NonRefcountedDOMObject_h__
+#define mozilla_dom_NonRefcountedDOMObject_h__
+
+#include "nsISupportsImpl.h"
+
+namespace mozilla::dom {
+
+// Natives for DOM classes that aren't refcounted need to inherit from this
+// class.
+// If you're seeing objects of this class leak then natives for one of the DOM
+// classes inheriting from it are leaking. If the native for that class has
+// MOZ_COUNT_CTOR/DTOR in its constructor/destructor then it should show up in
+// the leak log too.
+class NonRefcountedDOMObject {
+ protected:
+ MOZ_COUNTED_DEFAULT_CTOR(NonRefcountedDOMObject)
+
+ MOZ_COUNTED_DTOR(NonRefcountedDOMObject)
+
+ NonRefcountedDOMObject(const NonRefcountedDOMObject& aOther)
+ : NonRefcountedDOMObject() {}
+
+ NonRefcountedDOMObject& operator=(const NonRefcountedDOMObject& aOther) {
+ NonRefcountedDOMObject();
+ return *this;
+ }
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_NonRefcountedDOMObject_h__ */
diff --git a/dom/bindings/Nullable.h b/dom/bindings/Nullable.h
new file mode 100644
index 0000000000..2ab2a4e0f0
--- /dev/null
+++ b/dom/bindings/Nullable.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_Nullable_h
+#define mozilla_dom_Nullable_h
+
+#include <ostream>
+#include <utility>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Maybe.h"
+#include "nsTArrayForwardDeclare.h"
+
+class nsCycleCollectionTraversalCallback;
+
+namespace mozilla::dom {
+
+// Support for nullable types
+template <typename T>
+struct Nullable {
+ private:
+ Maybe<T> mValue;
+
+ public:
+ Nullable() : mValue() {}
+
+ MOZ_IMPLICIT Nullable(const decltype(nullptr)&) : mValue() {}
+
+ explicit Nullable(const T& aValue) : mValue() { mValue.emplace(aValue); }
+
+ MOZ_IMPLICIT Nullable(T&& aValue) : mValue() {
+ mValue.emplace(std::move(aValue));
+ }
+
+ Nullable(Nullable<T>&& aOther) : mValue(std::move(aOther.mValue)) {}
+
+ Nullable(const Nullable<T>& aOther) : mValue(aOther.mValue) {}
+
+ void operator=(const Nullable<T>& aOther) { mValue = aOther.mValue; }
+
+ void SetValue(const T& aArgs) {
+ mValue.reset();
+ mValue.emplace(aArgs);
+ }
+
+ void SetValue(T&& aArgs) {
+ mValue.reset();
+ mValue.emplace(std::move(aArgs));
+ }
+
+ // For cases when |T| is some type with nontrivial copy behavior, we may want
+ // to get a reference to our internal copy of T and work with it directly
+ // instead of relying on the copying version of SetValue().
+ T& SetValue() {
+ if (mValue.isNothing()) {
+ mValue.emplace();
+ }
+ return mValue.ref();
+ }
+
+ void SetNull() { mValue.reset(); }
+
+ const T& Value() const { return mValue.ref(); }
+
+ T& Value() { return mValue.ref(); }
+
+ bool IsNull() const { return mValue.isNothing(); }
+
+ bool Equals(const Nullable<T>& aOtherNullable) const {
+ return mValue == aOtherNullable.mValue;
+ }
+
+ bool operator==(const Nullable<T>& aOtherNullable) const {
+ return Equals(aOtherNullable);
+ }
+
+ bool operator!=(const Nullable<T>& aOtherNullable) const {
+ return !Equals(aOtherNullable);
+ }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const Nullable& aNullable) {
+ return aStream << aNullable.mValue;
+ }
+};
+
+template <typename T>
+void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+ Nullable<T>& aNullable, const char* aName,
+ uint32_t aFlags = 0) {
+ if (!aNullable.IsNull()) {
+ ImplCycleCollectionTraverse(aCallback, aNullable.Value(), aName, aFlags);
+ }
+}
+
+template <typename T>
+void ImplCycleCollectionUnlink(Nullable<T>& aNullable) {
+ if (!aNullable.IsNull()) {
+ ImplCycleCollectionUnlink(aNullable.Value());
+ }
+}
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_Nullable_h */
diff --git a/dom/bindings/ObservableArrayProxyHandler.cpp b/dom/bindings/ObservableArrayProxyHandler.cpp
new file mode 100644
index 0000000000..931950a492
--- /dev/null
+++ b/dom/bindings/ObservableArrayProxyHandler.cpp
@@ -0,0 +1,372 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/ObservableArrayProxyHandler.h"
+
+#include "jsapi.h"
+#include "js/friend/ErrorMessages.h"
+#include "js/Conversions.h"
+#include "js/Object.h"
+#include "mozilla/dom/JSSlots.h"
+#include "mozilla/dom/ProxyHandlerUtils.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/ErrorResult.h"
+#include "nsDebug.h"
+#include "nsJSUtils.h"
+
+namespace mozilla::dom {
+
+const char ObservableArrayProxyHandler::family = 0;
+
+bool ObservableArrayProxyHandler::defineProperty(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::Handle<JS::PropertyKey> aId, JS::Handle<JS::PropertyDescriptor> aDesc,
+ JS::ObjectOpResult& aResult) const {
+ if (aId.get() == s_length_id) {
+ if (aDesc.isAccessorDescriptor()) {
+ return aResult.failNotDataDescriptor();
+ }
+ if (aDesc.hasConfigurable() && aDesc.configurable()) {
+ return aResult.failInvalidDescriptor();
+ }
+ if (aDesc.hasEnumerable() && aDesc.enumerable()) {
+ return aResult.failInvalidDescriptor();
+ }
+ if (aDesc.hasWritable() && !aDesc.writable()) {
+ return aResult.failInvalidDescriptor();
+ }
+ if (aDesc.hasValue()) {
+ JS::Rooted<JSObject*> backingListObj(aCx);
+ if (!GetBackingListObject(aCx, aProxy, &backingListObj)) {
+ return false;
+ }
+
+ return SetLength(aCx, aProxy, backingListObj, aDesc.value(), aResult);
+ }
+ return aResult.succeed();
+ }
+ uint32_t index = GetArrayIndexFromId(aId);
+ if (IsArrayIndex(index)) {
+ if (aDesc.isAccessorDescriptor()) {
+ return aResult.failNotDataDescriptor();
+ }
+ if (aDesc.hasConfigurable() && !aDesc.configurable()) {
+ return aResult.failInvalidDescriptor();
+ }
+ if (aDesc.hasEnumerable() && !aDesc.enumerable()) {
+ return aResult.failInvalidDescriptor();
+ }
+ if (aDesc.hasWritable() && !aDesc.writable()) {
+ return aResult.failInvalidDescriptor();
+ }
+ if (aDesc.hasValue()) {
+ JS::Rooted<JSObject*> backingListObj(aCx);
+ if (!GetBackingListObject(aCx, aProxy, &backingListObj)) {
+ return false;
+ }
+
+ return SetIndexedValue(aCx, aProxy, backingListObj, index, aDesc.value(),
+ aResult);
+ }
+ return aResult.succeed();
+ }
+
+ return ForwardingProxyHandler::defineProperty(aCx, aProxy, aId, aDesc,
+ aResult);
+}
+
+bool ObservableArrayProxyHandler::delete_(JSContext* aCx,
+ JS::Handle<JSObject*> aProxy,
+ JS::Handle<JS::PropertyKey> aId,
+ JS::ObjectOpResult& aResult) const {
+ if (aId.get() == s_length_id) {
+ return aResult.failCantDelete();
+ }
+ uint32_t index = GetArrayIndexFromId(aId);
+ if (IsArrayIndex(index)) {
+ JS::Rooted<JSObject*> backingListObj(aCx);
+ if (!GetBackingListObject(aCx, aProxy, &backingListObj)) {
+ return false;
+ }
+
+ uint32_t oldLen = 0;
+ if (!JS::GetArrayLength(aCx, backingListObj, &oldLen)) {
+ return false;
+ }
+
+ // We do not follow the spec (step 3.3 in
+ // https://webidl.spec.whatwg.org/#es-observable-array-deleteProperty)
+ // is because `oldLen - 1` could be `-1` if the backing list is empty, but
+ // `oldLen` is `uint32_t` in practice. See also
+ // https://github.com/whatwg/webidl/issues/1049.
+ if (oldLen != index + 1) {
+ return aResult.failBadIndex();
+ }
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!JS_GetElement(aCx, backingListObj, index, &value)) {
+ return false;
+ }
+
+ if (!OnDeleteItem(aCx, aProxy, value, index)) {
+ return false;
+ }
+
+ if (!JS::SetArrayLength(aCx, backingListObj, index)) {
+ return false;
+ }
+
+ return aResult.succeed();
+ }
+ return ForwardingProxyHandler::delete_(aCx, aProxy, aId, aResult);
+}
+
+bool ObservableArrayProxyHandler::get(JSContext* aCx,
+ JS::Handle<JSObject*> aProxy,
+ JS::Handle<JS::Value> aReceiver,
+ JS::Handle<JS::PropertyKey> aId,
+ JS::MutableHandle<JS::Value> aVp) const {
+ JS::Rooted<JSObject*> backingListObj(aCx);
+ if (!GetBackingListObject(aCx, aProxy, &backingListObj)) {
+ return false;
+ }
+
+ uint32_t length = 0;
+ if (!JS::GetArrayLength(aCx, backingListObj, &length)) {
+ return false;
+ }
+
+ if (aId.get() == s_length_id) {
+ return ToJSValue(aCx, length, aVp);
+ }
+ uint32_t index = GetArrayIndexFromId(aId);
+ if (IsArrayIndex(index)) {
+ if (index >= length) {
+ aVp.setUndefined();
+ return true;
+ }
+ return JS_GetElement(aCx, backingListObj, index, aVp);
+ }
+ return ForwardingProxyHandler::get(aCx, aProxy, aReceiver, aId, aVp);
+}
+
+bool ObservableArrayProxyHandler::getOwnPropertyDescriptor(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::Handle<JS::PropertyKey> aId,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> aDesc) const {
+ JS::Rooted<JSObject*> backingListObj(aCx);
+ if (!GetBackingListObject(aCx, aProxy, &backingListObj)) {
+ return false;
+ }
+
+ uint32_t length = 0;
+ if (!JS::GetArrayLength(aCx, backingListObj, &length)) {
+ return false;
+ }
+
+ if (aId.get() == s_length_id) {
+ JS::Rooted<JS::Value> value(aCx, JS::NumberValue(length));
+ aDesc.set(Some(JS::PropertyDescriptor::Data(
+ value, {JS::PropertyAttribute::Writable})));
+ return true;
+ }
+ uint32_t index = GetArrayIndexFromId(aId);
+ if (IsArrayIndex(index)) {
+ if (index >= length) {
+ return true;
+ }
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!JS_GetElement(aCx, backingListObj, index, &value)) {
+ return false;
+ }
+
+ aDesc.set(Some(JS::PropertyDescriptor::Data(
+ value,
+ {JS::PropertyAttribute::Configurable, JS::PropertyAttribute::Writable,
+ JS::PropertyAttribute::Enumerable})));
+ return true;
+ }
+ return ForwardingProxyHandler::getOwnPropertyDescriptor(aCx, aProxy, aId,
+ aDesc);
+}
+
+bool ObservableArrayProxyHandler::has(JSContext* aCx,
+ JS::Handle<JSObject*> aProxy,
+ JS::Handle<JS::PropertyKey> aId,
+ bool* aBp) const {
+ if (aId.get() == s_length_id) {
+ *aBp = true;
+ return true;
+ }
+ uint32_t index = GetArrayIndexFromId(aId);
+ if (IsArrayIndex(index)) {
+ uint32_t length = 0;
+ if (!GetBackingListLength(aCx, aProxy, &length)) {
+ return false;
+ }
+
+ *aBp = (index < length);
+ return true;
+ }
+ return ForwardingProxyHandler::has(aCx, aProxy, aId, aBp);
+}
+
+bool ObservableArrayProxyHandler::ownPropertyKeys(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::MutableHandleVector<jsid> aProps) const {
+ uint32_t length = 0;
+ if (!GetBackingListLength(aCx, aProxy, &length)) {
+ return false;
+ }
+
+ for (int32_t i = 0; i < int32_t(length); i++) {
+ if (!aProps.append(JS::PropertyKey::Int(i))) {
+ return false;
+ }
+ }
+ return ForwardingProxyHandler::ownPropertyKeys(aCx, aProxy, aProps);
+}
+
+bool ObservableArrayProxyHandler::preventExtensions(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::ObjectOpResult& aResult) const {
+ return aResult.failCantPreventExtensions();
+}
+
+bool ObservableArrayProxyHandler::set(JSContext* aCx,
+ JS::Handle<JSObject*> aProxy,
+ JS::Handle<JS::PropertyKey> aId,
+ JS::Handle<JS::Value> aV,
+ JS::Handle<JS::Value> aReceiver,
+ JS::ObjectOpResult& aResult) const {
+ if (aId.get() == s_length_id) {
+ JS::Rooted<JSObject*> backingListObj(aCx);
+ if (!GetBackingListObject(aCx, aProxy, &backingListObj)) {
+ return false;
+ }
+
+ return SetLength(aCx, aProxy, backingListObj, aV, aResult);
+ }
+ uint32_t index = GetArrayIndexFromId(aId);
+ if (IsArrayIndex(index)) {
+ JS::Rooted<JSObject*> backingListObj(aCx);
+ if (!GetBackingListObject(aCx, aProxy, &backingListObj)) {
+ return false;
+ }
+
+ return SetIndexedValue(aCx, aProxy, backingListObj, index, aV, aResult);
+ }
+ return ForwardingProxyHandler::set(aCx, aProxy, aId, aV, aReceiver, aResult);
+}
+
+bool ObservableArrayProxyHandler::GetBackingListObject(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::MutableHandle<JSObject*> aBackingListObject) const {
+ // Retrieve the backing list object from the reserved slot on the proxy
+ // object. If it doesn't exist yet, create it.
+ JS::Rooted<JS::Value> slotValue(aCx);
+ slotValue = js::GetProxyReservedSlot(
+ aProxy, OBSERVABLE_ARRAY_BACKING_LIST_OBJECT_SLOT);
+ if (slotValue.isUndefined()) {
+ JS::Rooted<JSObject*> newBackingListObj(aCx);
+ newBackingListObj.set(JS::NewArrayObject(aCx, 0));
+ if (NS_WARN_IF(!newBackingListObj)) {
+ return false;
+ }
+ slotValue = JS::ObjectValue(*newBackingListObj);
+ js::SetProxyReservedSlot(aProxy, OBSERVABLE_ARRAY_BACKING_LIST_OBJECT_SLOT,
+ slotValue);
+ }
+ aBackingListObject.set(&slotValue.toObject());
+ return true;
+}
+
+bool ObservableArrayProxyHandler::GetBackingListLength(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy, uint32_t* aLength) const {
+ JS::Rooted<JSObject*> backingListObj(aCx);
+ if (!GetBackingListObject(aCx, aProxy, &backingListObj)) {
+ return false;
+ }
+
+ return JS::GetArrayLength(aCx, backingListObj, aLength);
+}
+
+bool ObservableArrayProxyHandler::SetLength(JSContext* aCx,
+ JS::Handle<JSObject*> aProxy,
+ uint32_t aLength) const {
+ JS::Rooted<JSObject*> backingListObj(aCx);
+ if (!GetBackingListObject(aCx, aProxy, &backingListObj)) {
+ return false;
+ }
+
+ JS::ObjectOpResult result;
+ if (!SetLength(aCx, aProxy, backingListObj, aLength, result)) {
+ return false;
+ }
+
+ return result ? true : result.reportError(aCx, aProxy);
+}
+
+bool ObservableArrayProxyHandler::SetLength(JSContext* aCx,
+ JS::Handle<JSObject*> aProxy,
+ JS::Handle<JSObject*> aBackingList,
+ uint32_t aLength,
+ JS::ObjectOpResult& aResult) const {
+ uint32_t oldLen;
+ if (!JS::GetArrayLength(aCx, aBackingList, &oldLen)) {
+ return false;
+ }
+
+ if (aLength > oldLen) {
+ return aResult.failBadArrayLength();
+ }
+
+ bool ok = true;
+ uint32_t len = oldLen;
+ for (; len > aLength; len--) {
+ uint32_t indexToDelete = len - 1;
+ JS::Rooted<JS::Value> value(aCx);
+ if (!JS_GetElement(aCx, aBackingList, indexToDelete, &value)) {
+ ok = false;
+ break;
+ }
+
+ if (!OnDeleteItem(aCx, aProxy, value, indexToDelete)) {
+ ok = false;
+ break;
+ }
+ }
+
+ return JS::SetArrayLength(aCx, aBackingList, len) && ok ? aResult.succeed()
+ : false;
+}
+
+bool ObservableArrayProxyHandler::SetLength(JSContext* aCx,
+ JS::Handle<JSObject*> aProxy,
+ JS::Handle<JSObject*> aBackingList,
+ JS::Handle<JS::Value> aValue,
+ JS::ObjectOpResult& aResult) const {
+ uint32_t uint32Len;
+ if (!ToUint32(aCx, aValue, &uint32Len)) {
+ return false;
+ }
+
+ double numberLen;
+ if (!ToNumber(aCx, aValue, &numberLen)) {
+ return false;
+ }
+
+ if (uint32Len != numberLen) {
+ JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr,
+ JSMSG_BAD_INDEX);
+ return false;
+ }
+
+ return SetLength(aCx, aProxy, aBackingList, uint32Len, aResult);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/ObservableArrayProxyHandler.h b/dom/bindings/ObservableArrayProxyHandler.h
new file mode 100644
index 0000000000..d0a9bdfaf4
--- /dev/null
+++ b/dom/bindings/ObservableArrayProxyHandler.h
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_ObservableArrayProxyHandler_h
+#define mozilla_dom_ObservableArrayProxyHandler_h
+
+#include "js/TypeDecls.h"
+#include "js/Wrapper.h"
+
+namespace mozilla::dom {
+
+/**
+ * Proxy handler for observable array exotic object.
+ *
+ * The indexed properties are stored in the backing list object in reserved slot
+ * of the proxy object with special treatment intact.
+ *
+ * The additional properties are stored in the proxy target object.
+ */
+
+class ObservableArrayProxyHandler : public js::ForwardingProxyHandler {
+ public:
+ explicit constexpr ObservableArrayProxyHandler()
+ : js::ForwardingProxyHandler(&family) {}
+
+ // Implementations of methods that can be implemented in terms of
+ // other lower-level methods.
+ bool defineProperty(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::Handle<JS::PropertyKey> aId,
+ JS::Handle<JS::PropertyDescriptor> aDesc,
+ JS::ObjectOpResult& aResult) const override;
+
+ bool delete_(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::Handle<JS::PropertyKey> aId,
+ JS::ObjectOpResult& aResult) const override;
+
+ bool get(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::Handle<JS::Value> aReceiver, JS::Handle<JS::PropertyKey> aId,
+ JS::MutableHandle<JS::Value> aVp) const override;
+
+ bool getOwnPropertyDescriptor(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::Handle<JS::PropertyKey> aId,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> aDesc) const override;
+
+ bool has(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::Handle<JS::PropertyKey> aId, bool* aBp) const override;
+
+ bool ownPropertyKeys(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::MutableHandleVector<jsid> aProps) const override;
+
+ bool preventExtensions(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::ObjectOpResult& aResult) const override;
+
+ bool set(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::Handle<JS::PropertyKey> aId, JS::Handle<JS::Value> aV,
+ JS::Handle<JS::Value> aReceiver,
+ JS::ObjectOpResult& aResult) const override;
+
+ bool SetLength(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ uint32_t aLength) const;
+
+ static const char family;
+
+ protected:
+ bool GetBackingListObject(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::MutableHandle<JSObject*> aBackingListObject) const;
+
+ bool GetBackingListLength(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ uint32_t* aLength) const;
+
+ bool SetLength(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::Handle<JSObject*> aBackingList, uint32_t aLength,
+ JS::ObjectOpResult& aResult) const;
+
+ bool SetLength(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::Handle<JSObject*> aBackingList,
+ JS::Handle<JS::Value> aValue,
+ JS::ObjectOpResult& aResult) const;
+
+ // Hook for subclasses to invoke the setting the indexed value steps which
+ // would invoke DeleteAlgorithm/SetAlgorithm defined and implemented per
+ // interface. Returns false and throw exception on failure.
+ virtual bool SetIndexedValue(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::Handle<JSObject*> aBackingList,
+ uint32_t aIndex, JS::Handle<JS::Value> aValue,
+ JS::ObjectOpResult& aResult) const = 0;
+
+ // Hook for subclasses to invoke the DeleteAlgorithm defined and implemented
+ // per interface. Returns false and throw exception on failure.
+ virtual bool OnDeleteItem(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::Handle<JS::Value> aValue,
+ uint32_t aIndex) const = 0;
+};
+
+inline bool IsObservableArrayProxy(JSObject* obj) {
+ return js::IsProxy(obj) && js::GetProxyHandler(obj)->family() ==
+ &ObservableArrayProxyHandler::family;
+}
+
+inline const ObservableArrayProxyHandler* GetObservableArrayProxyHandler(
+ JSObject* obj) {
+ MOZ_ASSERT(IsObservableArrayProxy(obj));
+ return static_cast<const ObservableArrayProxyHandler*>(
+ js::GetProxyHandler(obj));
+}
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_ObservableArrayProxyHandler_h */
diff --git a/dom/bindings/PinnedStringId.h b/dom/bindings/PinnedStringId.h
new file mode 100644
index 0000000000..3d18c1b07c
--- /dev/null
+++ b/dom/bindings/PinnedStringId.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 DOM_BINDINGS_PINNEDSTRINGID_H_
+#define DOM_BINDINGS_PINNEDSTRINGID_H_
+
+#include "js/GCAnnotations.h"
+#include "js/Id.h"
+#include "js/RootingAPI.h"
+#include "js/String.h"
+#include "jsapi.h"
+
+class JSString;
+struct JSContext;
+
+namespace mozilla::dom {
+/*
+ * Holds a jsid that is initialized to a pinned string, with automatic
+ * conversion to Handle<jsid>, as it is held live forever by pinning.
+ */
+class PinnedStringId {
+ jsid id;
+
+ public:
+ constexpr PinnedStringId() : id(JS::PropertyKey::Void()) {}
+
+ bool init(JSContext* cx, const char* string) {
+ JSString* str = JS_AtomizeAndPinString(cx, string);
+ if (!str) {
+ return false;
+ }
+ id = JS::PropertyKey::fromPinnedString(str);
+ return true;
+ }
+
+ operator const jsid&() const { return id; }
+
+ operator JS::Handle<jsid>() const {
+ /* This is safe because we have pinned the string. */
+ return JS::Handle<jsid>::fromMarkedLocation(&id);
+ }
+} JS_HAZ_ROOTED;
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/bindings/PrimitiveConversions.h b/dom/bindings/PrimitiveConversions.h
new file mode 100644
index 0000000000..1c5b62ec8f
--- /dev/null
+++ b/dom/bindings/PrimitiveConversions.h
@@ -0,0 +1,330 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/**
+ * Conversions from jsval to primitive values
+ */
+
+#ifndef mozilla_dom_PrimitiveConversions_h
+#define mozilla_dom_PrimitiveConversions_h
+
+#include <limits>
+#include <math.h>
+#include <stdint.h>
+
+#include "js/Conversions.h"
+#include "js/RootingAPI.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/dom/BindingCallContext.h"
+
+namespace mozilla::dom {
+
+template <typename T>
+struct TypeName {};
+
+template <>
+struct TypeName<int8_t> {
+ static const char* value() { return "byte"; }
+};
+template <>
+struct TypeName<uint8_t> {
+ static const char* value() { return "octet"; }
+};
+template <>
+struct TypeName<int16_t> {
+ static const char* value() { return "short"; }
+};
+template <>
+struct TypeName<uint16_t> {
+ static const char* value() { return "unsigned short"; }
+};
+template <>
+struct TypeName<int32_t> {
+ static const char* value() { return "long"; }
+};
+template <>
+struct TypeName<uint32_t> {
+ static const char* value() { return "unsigned long"; }
+};
+template <>
+struct TypeName<int64_t> {
+ static const char* value() { return "long long"; }
+};
+template <>
+struct TypeName<uint64_t> {
+ static const char* value() { return "unsigned long long"; }
+};
+
+enum ConversionBehavior { eDefault, eEnforceRange, eClamp };
+
+template <typename T, ConversionBehavior B>
+struct PrimitiveConversionTraits {};
+
+template <typename T>
+struct DisallowedConversion {
+ typedef int jstype;
+ typedef int intermediateType;
+
+ private:
+ static inline bool converter(JSContext* cx, JS::Handle<JS::Value> v,
+ const char* sourceDescription, jstype* retval) {
+ MOZ_CRASH("This should never be instantiated!");
+ }
+};
+
+struct PrimitiveConversionTraits_smallInt {
+ // The output of JS::ToInt32 is determined as follows:
+ // 1) The value is converted to a double
+ // 2) Anything that's not a finite double returns 0
+ // 3) The double is rounded towards zero to the nearest integer
+ // 4) The resulting integer is reduced mod 2^32. The output of this
+ // operation is an integer in the range [0, 2^32).
+ // 5) If the resulting number is >= 2^31, 2^32 is subtracted from it.
+ //
+ // The result of all this is a number in the range [-2^31, 2^31)
+ //
+ // WebIDL conversions for the 8-bit, 16-bit, and 32-bit integer types
+ // are defined in the same way, except that step 4 uses reduction mod
+ // 2^8 and 2^16 for the 8-bit and 16-bit types respectively, and step 5
+ // is only done for the signed types.
+ //
+ // C/C++ define integer conversion semantics to unsigned types as taking
+ // your input integer mod (1 + largest value representable in the
+ // unsigned type). Since 2^32 is zero mod 2^8, 2^16, and 2^32,
+ // converting to the unsigned int of the relevant width will correctly
+ // perform step 4; in particular, the 2^32 possibly subtracted in step 5
+ // will become 0.
+ //
+ // Once we have step 4 done, we're just going to assume 2s-complement
+ // representation and cast directly to the type we really want.
+ //
+ // So we can cast directly for all unsigned types and for int32_t; for
+ // the smaller-width signed types we need to cast through the
+ // corresponding unsigned type.
+ typedef int32_t jstype;
+ typedef int32_t intermediateType;
+ static inline bool converter(JSContext* cx, JS::Handle<JS::Value> v,
+ const char* sourceDescription, jstype* retval) {
+ return JS::ToInt32(cx, v, retval);
+ }
+};
+template <>
+struct PrimitiveConversionTraits<int8_t, eDefault>
+ : PrimitiveConversionTraits_smallInt {
+ typedef uint8_t intermediateType;
+};
+template <>
+struct PrimitiveConversionTraits<uint8_t, eDefault>
+ : PrimitiveConversionTraits_smallInt {};
+template <>
+struct PrimitiveConversionTraits<int16_t, eDefault>
+ : PrimitiveConversionTraits_smallInt {
+ typedef uint16_t intermediateType;
+};
+template <>
+struct PrimitiveConversionTraits<uint16_t, eDefault>
+ : PrimitiveConversionTraits_smallInt {};
+template <>
+struct PrimitiveConversionTraits<int32_t, eDefault>
+ : PrimitiveConversionTraits_smallInt {};
+template <>
+struct PrimitiveConversionTraits<uint32_t, eDefault>
+ : PrimitiveConversionTraits_smallInt {};
+
+template <>
+struct PrimitiveConversionTraits<int64_t, eDefault> {
+ typedef int64_t jstype;
+ typedef int64_t intermediateType;
+ static inline bool converter(JSContext* cx, JS::Handle<JS::Value> v,
+ const char* sourceDescription, jstype* retval) {
+ return JS::ToInt64(cx, v, retval);
+ }
+};
+
+template <>
+struct PrimitiveConversionTraits<uint64_t, eDefault> {
+ typedef uint64_t jstype;
+ typedef uint64_t intermediateType;
+ static inline bool converter(JSContext* cx, JS::Handle<JS::Value> v,
+ const char* sourceDescription, jstype* retval) {
+ return JS::ToUint64(cx, v, retval);
+ }
+};
+
+template <typename T>
+struct PrimitiveConversionTraits_Limits {
+ static inline T min() { return std::numeric_limits<T>::min(); }
+ static inline T max() { return std::numeric_limits<T>::max(); }
+};
+
+template <>
+struct PrimitiveConversionTraits_Limits<int64_t> {
+ static inline int64_t min() { return -(1LL << 53) + 1; }
+ static inline int64_t max() { return (1LL << 53) - 1; }
+};
+
+template <>
+struct PrimitiveConversionTraits_Limits<uint64_t> {
+ static inline uint64_t min() { return 0; }
+ static inline uint64_t max() { return (1LL << 53) - 1; }
+};
+
+template <typename T, typename U,
+ bool (*Enforce)(U cx, const char* sourceDescription, const double& d,
+ T* retval)>
+struct PrimitiveConversionTraits_ToCheckedIntHelper {
+ typedef T jstype;
+ typedef T intermediateType;
+
+ static inline bool converter(U cx, JS::Handle<JS::Value> v,
+ const char* sourceDescription, jstype* retval) {
+ double intermediate;
+ if (!JS::ToNumber(cx, v, &intermediate)) {
+ return false;
+ }
+
+ return Enforce(cx, sourceDescription, intermediate, retval);
+ }
+};
+
+template <typename T>
+inline bool PrimitiveConversionTraits_EnforceRange(
+ BindingCallContext& cx, const char* sourceDescription, const double& d,
+ T* retval) {
+ static_assert(std::numeric_limits<T>::is_integer,
+ "This can only be applied to integers!");
+
+ if (!std::isfinite(d)) {
+ return cx.ThrowErrorMessage<MSG_ENFORCE_RANGE_NON_FINITE>(
+ sourceDescription, TypeName<T>::value());
+ }
+
+ bool neg = (d < 0);
+ double rounded = floor(neg ? -d : d);
+ rounded = neg ? -rounded : rounded;
+ if (rounded < PrimitiveConversionTraits_Limits<T>::min() ||
+ rounded > PrimitiveConversionTraits_Limits<T>::max()) {
+ return cx.ThrowErrorMessage<MSG_ENFORCE_RANGE_OUT_OF_RANGE>(
+ sourceDescription, TypeName<T>::value());
+ }
+
+ *retval = static_cast<T>(rounded);
+ return true;
+}
+
+template <typename T>
+struct PrimitiveConversionTraits<T, eEnforceRange>
+ : public PrimitiveConversionTraits_ToCheckedIntHelper<
+ T, BindingCallContext&, PrimitiveConversionTraits_EnforceRange<T> > {
+};
+
+template <typename T>
+inline bool PrimitiveConversionTraits_Clamp(JSContext* cx,
+ const char* sourceDescription,
+ const double& d, T* retval) {
+ static_assert(std::numeric_limits<T>::is_integer,
+ "This can only be applied to integers!");
+
+ if (std::isnan(d)) {
+ *retval = 0;
+ return true;
+ }
+ if (d >= PrimitiveConversionTraits_Limits<T>::max()) {
+ *retval = PrimitiveConversionTraits_Limits<T>::max();
+ return true;
+ }
+ if (d <= PrimitiveConversionTraits_Limits<T>::min()) {
+ *retval = PrimitiveConversionTraits_Limits<T>::min();
+ return true;
+ }
+
+ MOZ_ASSERT(std::isfinite(d));
+
+ // Banker's rounding (round ties towards even).
+ // We move away from 0 by 0.5f and then truncate. That gets us the right
+ // answer for any starting value except plus or minus N.5. With a starting
+ // value of that form, we now have plus or minus N+1. If N is odd, this is
+ // the correct result. If N is even, plus or minus N is the correct result.
+ double toTruncate = (d < 0) ? d - 0.5 : d + 0.5;
+
+ T truncated = static_cast<T>(toTruncate);
+
+ if (truncated == toTruncate) {
+ /*
+ * It was a tie (since moving away from 0 by 0.5 gave us the exact integer
+ * we want). Since we rounded away from 0, we either already have an even
+ * number or we have an odd number but the number we want is one closer to
+ * 0. So just unconditionally masking out the ones bit should do the trick
+ * to get us the value we want.
+ */
+ truncated &= ~1;
+ }
+
+ *retval = truncated;
+ return true;
+}
+
+template <typename T>
+struct PrimitiveConversionTraits<T, eClamp>
+ : public PrimitiveConversionTraits_ToCheckedIntHelper<
+ T, JSContext*, PrimitiveConversionTraits_Clamp<T> > {};
+
+template <ConversionBehavior B>
+struct PrimitiveConversionTraits<bool, B> : public DisallowedConversion<bool> {
+};
+
+template <>
+struct PrimitiveConversionTraits<bool, eDefault> {
+ typedef bool jstype;
+ typedef bool intermediateType;
+ static inline bool converter(JSContext* /* unused */, JS::Handle<JS::Value> v,
+ const char* sourceDescription, jstype* retval) {
+ *retval = JS::ToBoolean(v);
+ return true;
+ }
+};
+
+template <ConversionBehavior B>
+struct PrimitiveConversionTraits<float, B>
+ : public DisallowedConversion<float> {};
+
+template <ConversionBehavior B>
+struct PrimitiveConversionTraits<double, B>
+ : public DisallowedConversion<double> {};
+
+struct PrimitiveConversionTraits_float {
+ typedef double jstype;
+ typedef double intermediateType;
+ static inline bool converter(JSContext* cx, JS::Handle<JS::Value> v,
+ const char* sourceDescription, jstype* retval) {
+ return JS::ToNumber(cx, v, retval);
+ }
+};
+
+template <>
+struct PrimitiveConversionTraits<float, eDefault>
+ : PrimitiveConversionTraits_float {};
+template <>
+struct PrimitiveConversionTraits<double, eDefault>
+ : PrimitiveConversionTraits_float {};
+
+template <typename T, ConversionBehavior B, typename U>
+bool ValueToPrimitive(U& cx, JS::Handle<JS::Value> v,
+ const char* sourceDescription, T* retval) {
+ typename PrimitiveConversionTraits<T, B>::jstype t;
+ if (!PrimitiveConversionTraits<T, B>::converter(cx, v, sourceDescription, &t))
+ return false;
+
+ *retval = static_cast<T>(
+ static_cast<typename PrimitiveConversionTraits<T, B>::intermediateType>(
+ t));
+ return true;
+}
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_PrimitiveConversions_h */
diff --git a/dom/bindings/ProxyHandlerUtils.h b/dom/bindings/ProxyHandlerUtils.h
new file mode 100644
index 0000000000..b9a9e4a579
--- /dev/null
+++ b/dom/bindings/ProxyHandlerUtils.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_ProxyHandlerUtils_h
+#define mozilla_dom_ProxyHandlerUtils_h
+
+#include "mozilla/Likely.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/TextUtils.h"
+
+#include "js/Id.h"
+#include "js/Object.h" // JS::GetClass
+#include "js/PropertyDescriptor.h"
+#include "js/String.h" // JS::AtomToLinearString, JS::GetLinearString{CharAt,Length}
+#include "js/TypeDecls.h"
+
+#include "jsfriendapi.h" // js::StringIsArrayIndex
+
+namespace mozilla::dom {
+
+extern jsid s_length_id;
+
+// A return value of UINT32_MAX indicates "not an array index". Note, in
+// particular, that UINT32_MAX itself is not a valid array index in general.
+inline uint32_t GetArrayIndexFromId(JS::Handle<jsid> id) {
+ // Much like js::IdIsIndex, except with a fast path for "length" and another
+ // fast path for starting with a lowercase ascii char. Is that second one
+ // really needed? I guess it is because StringIsArrayIndex is out of line...
+ // as of now, use id.get() instead of id otherwise operands mismatch error
+ // occurs.
+ if (MOZ_LIKELY(id.isInt())) {
+ return id.toInt();
+ }
+ if (MOZ_LIKELY(id.get() == s_length_id)) {
+ return UINT32_MAX;
+ }
+ if (MOZ_UNLIKELY(!id.isAtom())) {
+ return UINT32_MAX;
+ }
+
+ JSLinearString* str = JS::AtomToLinearString(id.toAtom());
+ if (MOZ_UNLIKELY(JS::GetLinearStringLength(str) == 0)) {
+ return UINT32_MAX;
+ }
+
+ char16_t firstChar = JS::GetLinearStringCharAt(str, 0);
+ if (MOZ_LIKELY(IsAsciiLowercaseAlpha(firstChar))) {
+ return UINT32_MAX;
+ }
+
+ uint32_t i;
+ return js::StringIsArrayIndex(str, &i) ? i : UINT32_MAX;
+}
+
+inline bool IsArrayIndex(uint32_t index) { return index < UINT32_MAX; }
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_ProxyHandlerUtils_h */
diff --git a/dom/bindings/Record.h b/dom/bindings/Record.h
new file mode 100644
index 0000000000..1c9145a40d
--- /dev/null
+++ b/dom/bindings/Record.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/**
+ * Class for representing record arguments. Basically an array under the hood.
+ */
+
+#ifndef mozilla_dom_Record_h
+#define mozilla_dom_Record_h
+
+#include <utility>
+
+#include "mozilla/Attributes.h"
+#include "nsHashKeys.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+
+namespace mozilla::dom {
+
+namespace binding_detail {
+template <typename KeyType, typename ValueType>
+class RecordEntry {
+ public:
+ RecordEntry() = default;
+
+ // Move constructor so we can do Records of Records.
+ RecordEntry(RecordEntry<KeyType, ValueType>&& aOther)
+ : mKey(std::move(aOther.mKey)), mValue(std::move(aOther.mValue)) {}
+
+ KeyType mKey;
+ ValueType mValue;
+};
+
+// Specialize for a JSObject* ValueType and initialize it on construction, so we
+// don't need to worry about un-initialized JSObject* floating around.
+template <typename KeyType>
+class RecordEntry<KeyType, JSObject*> {
+ public:
+ RecordEntry() : mValue(nullptr) {}
+
+ // Move constructor so we can do Records of Records.
+ RecordEntry(RecordEntry<KeyType, JSObject*>&& aOther)
+ : mKey(std::move(aOther.mKey)), mValue(std::move(aOther.mValue)) {}
+
+ KeyType mKey;
+ JSObject* mValue;
+};
+
+} // namespace binding_detail
+
+template <typename KeyType, typename ValueType>
+class Record {
+ public:
+ typedef typename binding_detail::RecordEntry<KeyType, ValueType> EntryType;
+ typedef Record<KeyType, ValueType> SelfType;
+
+ Record() = default;
+
+ // Move constructor so we can do Record of Record.
+ Record(SelfType&& aOther) : mEntries(std::move(aOther.mEntries)) {}
+
+ const nsTArray<EntryType>& Entries() const { return mEntries; }
+
+ nsTArray<EntryType>& Entries() { return mEntries; }
+
+ private:
+ nsTArray<EntryType> mEntries;
+};
+
+} // namespace mozilla::dom
+
+template <typename K, typename V>
+class nsDefaultComparator<mozilla::dom::binding_detail::RecordEntry<K, V>, K> {
+ public:
+ bool Equals(const mozilla::dom::binding_detail::RecordEntry<K, V>& aEntry,
+ const K& aKey) const {
+ return aEntry.mKey == aKey;
+ }
+};
+
+#endif // mozilla_dom_Record_h
diff --git a/dom/bindings/RemoteObjectProxy.cpp b/dom/bindings/RemoteObjectProxy.cpp
new file mode 100644
index 0000000000..c17686627f
--- /dev/null
+++ b/dom/bindings/RemoteObjectProxy.cpp
@@ -0,0 +1,182 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "RemoteObjectProxy.h"
+#include "AccessCheck.h"
+#include "jsfriendapi.h"
+#include "js/Object.h" // JS::GetClass
+#include "xpcprivate.h"
+
+namespace mozilla::dom {
+
+bool RemoteObjectProxyBase::getOwnPropertyDescriptor(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> aDesc) const {
+ bool ok = CrossOriginGetOwnPropertyHelper(aCx, aProxy, aId, aDesc);
+ if (!ok || aDesc.isSome()) {
+ return ok;
+ }
+
+ return CrossOriginPropertyFallback(aCx, aProxy, aId, aDesc);
+}
+
+bool RemoteObjectProxyBase::defineProperty(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
+ JS::Handle<JS::PropertyDescriptor> aDesc,
+ JS::ObjectOpResult& aResult) const {
+ // https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-defineownproperty
+ // step 3 and
+ // https://html.spec.whatwg.org/multipage/browsers.html#location-defineownproperty
+ // step 2
+ return ReportCrossOriginDenial(aCx, aId, "define"_ns);
+}
+
+bool RemoteObjectProxyBase::ownPropertyKeys(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::MutableHandleVector<jsid> aProps) const {
+ // https://html.spec.whatwg.org/multipage/browsers.html#crossoriginownpropertykeys-(-o-)
+ // step 2 and
+ // https://html.spec.whatwg.org/multipage/browsers.html#crossoriginproperties-(-o-)
+ JS::Rooted<JSObject*> holder(aCx);
+ if (!EnsureHolder(aCx, aProxy, &holder) ||
+ !js::GetPropertyKeys(aCx, holder,
+ JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS,
+ aProps)) {
+ return false;
+ }
+
+ // https://html.spec.whatwg.org/multipage/browsers.html#crossoriginownpropertykeys-(-o-)
+ // step 3 and 4
+ return xpc::AppendCrossOriginWhitelistedPropNames(aCx, aProps);
+}
+
+bool RemoteObjectProxyBase::delete_(JSContext* aCx,
+ JS::Handle<JSObject*> aProxy,
+ JS::Handle<jsid> aId,
+ JS::ObjectOpResult& aResult) const {
+ // https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-delete
+ // step 3 and
+ // https://html.spec.whatwg.org/multipage/browsers.html#location-delete step 2
+ return ReportCrossOriginDenial(aCx, aId, "delete"_ns);
+}
+
+bool RemoteObjectProxyBase::getPrototypeIfOrdinary(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy, bool* aIsOrdinary,
+ JS::MutableHandle<JSObject*> aProtop) const {
+ // WindowProxy's and Location's [[GetPrototypeOf]] traps aren't the ordinary
+ // definition:
+ //
+ // https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-getprototypeof
+ // https://html.spec.whatwg.org/multipage/browsers.html#location-getprototypeof
+ //
+ // We nonetheless can implement it with a static [[Prototype]], because the
+ // [[GetPrototypeOf]] trap should always return null.
+ *aIsOrdinary = true;
+ aProtop.set(nullptr);
+ return true;
+}
+
+bool RemoteObjectProxyBase::preventExtensions(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::ObjectOpResult& aResult) const {
+ // https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-preventextensions
+ // and
+ // https://html.spec.whatwg.org/multipage/browsers.html#location-preventextensions
+ return aResult.failCantPreventExtensions();
+}
+
+bool RemoteObjectProxyBase::isExtensible(JSContext* aCx,
+ JS::Handle<JSObject*> aProxy,
+ bool* aExtensible) const {
+ // https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-isextensible
+ // and
+ // https://html.spec.whatwg.org/multipage/browsers.html#location-isextensible
+ *aExtensible = true;
+ return true;
+}
+
+bool RemoteObjectProxyBase::get(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::Handle<JS::Value> aReceiver,
+ JS::Handle<jsid> aId,
+ JS::MutableHandle<JS::Value> aVp) const {
+ return CrossOriginGet(aCx, aProxy, aReceiver, aId, aVp);
+}
+
+bool RemoteObjectProxyBase::set(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::Handle<jsid> aId,
+ JS::Handle<JS::Value> aValue,
+ JS::Handle<JS::Value> aReceiver,
+ JS::ObjectOpResult& aResult) const {
+ return CrossOriginSet(aCx, aProxy, aId, aValue, aReceiver, aResult);
+}
+
+bool RemoteObjectProxyBase::getOwnEnumerablePropertyKeys(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::MutableHandleVector<jsid> aProps) const {
+ return true;
+}
+
+const char* RemoteObjectProxyBase::className(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy) const {
+ MOZ_ASSERT(js::IsProxy(aProxy));
+
+ return "Object";
+}
+
+void RemoteObjectProxyBase::GetOrCreateProxyObject(
+ JSContext* aCx, void* aNative, const JSClass* aClasp,
+ JS::Handle<JSObject*> aTransplantTo, JS::MutableHandle<JSObject*> aProxy,
+ bool& aNewObjectCreated) const {
+ xpc::CompartmentPrivate* priv =
+ xpc::CompartmentPrivate::Get(JS::CurrentGlobalOrNull(aCx));
+ xpc::CompartmentPrivate::RemoteProxyMap& map = priv->GetRemoteProxyMap();
+ if (auto result = map.lookup(aNative)) {
+ MOZ_ASSERT(!aTransplantTo,
+ "No existing value allowed if we're doing a transplant");
+
+ aProxy.set(result->value());
+
+ // During a transplant, we put an object that is temporarily not a
+ // proxy object into the map. Make sure that we don't return one of
+ // these objects in the middle of a transplant.
+ MOZ_RELEASE_ASSERT(JS::GetClass(aProxy) == aClasp);
+
+ return;
+ }
+
+ js::ProxyOptions options;
+ options.setClass(aClasp);
+ JS::Rooted<JS::Value> native(aCx, JS::PrivateValue(aNative));
+ JS::Rooted<JSObject*> obj(
+ aCx, js::NewProxyObject(aCx, this, native, nullptr, options));
+ if (!obj) {
+ return;
+ }
+
+ bool success;
+ if (!JS_SetImmutablePrototype(aCx, obj, &success)) {
+ return;
+ }
+ MOZ_ASSERT(success);
+
+ aNewObjectCreated = true;
+
+ // If we're transplanting onto an object, we want to make sure that it does
+ // not have the same class as aClasp to ensure that the release assert earlier
+ // in this function will actually fire if we try to return a proxy object in
+ // the middle of a transplant.
+ MOZ_ASSERT_IF(aTransplantTo, JS::GetClass(aTransplantTo) != aClasp);
+
+ if (!map.put(aNative, aTransplantTo ? aTransplantTo : obj)) {
+ return;
+ }
+
+ aProxy.set(obj);
+}
+
+const char RemoteObjectProxyBase::sCrossOriginProxyFamily = 0;
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/RemoteObjectProxy.h b/dom/bindings/RemoteObjectProxy.h
new file mode 100644
index 0000000000..8a8ef7270c
--- /dev/null
+++ b/dom/bindings/RemoteObjectProxy.h
@@ -0,0 +1,204 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_RemoteObjectProxy_h
+#define mozilla_dom_RemoteObjectProxy_h
+
+#include "js/Proxy.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/MaybeCrossOriginObject.h"
+#include "mozilla/dom/PrototypeList.h"
+#include "xpcpublic.h"
+
+namespace mozilla::dom {
+
+class BrowsingContext;
+
+/**
+ * Base class for RemoteObjectProxy. Implements the pieces of the handler that
+ * don't depend on properties/methods of the specific WebIDL interface that this
+ * proxy implements.
+ */
+class RemoteObjectProxyBase : public js::BaseProxyHandler,
+ public MaybeCrossOriginObjectMixins {
+ protected:
+ explicit constexpr RemoteObjectProxyBase(prototypes::ID aPrototypeID)
+ : BaseProxyHandler(&sCrossOriginProxyFamily, false),
+ mPrototypeID(aPrototypeID) {}
+
+ public:
+ bool finalizeInBackground(const JS::Value& priv) const final { return false; }
+
+ // Standard internal methods
+ bool getOwnPropertyDescriptor(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> aDesc) const override;
+ bool ownPropertyKeys(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::MutableHandleVector<jsid> aProps) const override;
+ bool defineProperty(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::Handle<jsid> aId,
+ JS::Handle<JS::PropertyDescriptor> aDesc,
+ JS::ObjectOpResult& result) const final;
+ bool delete_(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::Handle<jsid> aId, JS::ObjectOpResult& aResult) const final;
+
+ bool getPrototypeIfOrdinary(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ bool* aIsOrdinary,
+ JS::MutableHandle<JSObject*> aProtop) const final;
+
+ bool preventExtensions(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::ObjectOpResult& aResult) const final;
+ bool isExtensible(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ bool* aExtensible) const final;
+
+ bool get(JSContext* cx, JS::Handle<JSObject*> aProxy,
+ JS::Handle<JS::Value> aReceiver, JS::Handle<jsid> aId,
+ JS::MutableHandle<JS::Value> aVp) const final;
+ bool set(JSContext* cx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
+ JS::Handle<JS::Value> aValue, JS::Handle<JS::Value> aReceiver,
+ JS::ObjectOpResult& aResult) const final;
+
+ // SpiderMonkey extensions
+ bool getOwnEnumerablePropertyKeys(
+ JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::MutableHandleVector<jsid> aProps) const override;
+ const char* className(JSContext* aCx,
+ JS::Handle<JSObject*> aProxy) const final;
+
+ // Cross origin objects like RemoteWindowProxy should not participate in
+ // private fields.
+ virtual bool throwOnPrivateField() const override { return true; }
+
+ bool isCallable(JSObject* aObj) const final { return false; }
+ bool isConstructor(JSObject* aObj) const final { return false; }
+
+ virtual void NoteChildren(JSObject* aProxy,
+ nsCycleCollectionTraversalCallback& aCb) const = 0;
+
+ static void* GetNative(JSObject* aProxy) {
+ return js::GetProxyPrivate(aProxy).toPrivate();
+ }
+
+ /**
+ * Returns true if aProxy is a cross-process proxy that represents
+ * an object implementing the WebIDL interface for aProtoID. aProxy
+ * should be a proxy object.
+ */
+ static inline bool IsRemoteObjectProxy(JSObject* aProxy,
+ prototypes::ID aProtoID) {
+ const js::BaseProxyHandler* handler = js::GetProxyHandler(aProxy);
+ return handler->family() == &sCrossOriginProxyFamily &&
+ static_cast<const RemoteObjectProxyBase*>(handler)->mPrototypeID ==
+ aProtoID;
+ }
+
+ /**
+ * Returns true if aProxy is a cross-process proxy, no matter which
+ * interface it represents. aProxy should be a proxy object.
+ */
+ static inline bool IsRemoteObjectProxy(JSObject* aProxy) {
+ const js::BaseProxyHandler* handler = js::GetProxyHandler(aProxy);
+ return handler->family() == &sCrossOriginProxyFamily;
+ }
+
+ protected:
+ /**
+ * Gets an existing cached proxy object, or creates a new one and caches it.
+ * aProxy will be null on failure. aNewObjectCreated is set to true if a new
+ * object was created, callers probably need to addref the native in that
+ * case. aNewObjectCreated can be true even if aProxy is null, if something
+ * failed after creating the object.
+ */
+ void GetOrCreateProxyObject(JSContext* aCx, void* aNative,
+ const JSClass* aClasp,
+ JS::Handle<JSObject*> aTransplantTo,
+ JS::MutableHandle<JSObject*> aProxy,
+ bool& aNewObjectCreated) const;
+
+ const prototypes::ID mPrototypeID;
+
+ friend struct SetDOMProxyInformation;
+ static const char sCrossOriginProxyFamily;
+};
+
+/**
+ * Proxy handler for proxy objects that represent an object implementing a
+ * WebIDL interface that has cross-origin accessible properties/methods, and
+ * which lives in a different process. The WebIDL code generator will create
+ * arrays of cross-origin accessible properties/methods that can be used as
+ * arguments to this template.
+ *
+ * The properties and methods can be cached on a holder JSObject, stored in a
+ * reserved slot on the proxy object.
+ *
+ * The proxy objects that use a handler derived from this one are stored in a
+ * hash map in the JS compartment's private (@see
+ * xpc::CompartmentPrivate::GetRemoteProxyMap).
+ */
+template <class Native, const CrossOriginProperties& P>
+class RemoteObjectProxy : public RemoteObjectProxyBase {
+ public:
+ void finalize(JS::GCContext* aGcx, JSObject* aProxy) const final {
+ auto native = static_cast<Native*>(GetNative(aProxy));
+ RefPtr<Native> self(dont_AddRef(native));
+ }
+
+ void GetProxyObject(JSContext* aCx, Native* aNative,
+ JS::Handle<JSObject*> aTransplantTo,
+ JS::MutableHandle<JSObject*> aProxy) const {
+ bool objectCreated = false;
+ GetOrCreateProxyObject(aCx, aNative, &sClass, aTransplantTo, aProxy,
+ objectCreated);
+ if (objectCreated) {
+ NS_ADDREF(aNative);
+ }
+ }
+
+ protected:
+ using RemoteObjectProxyBase::RemoteObjectProxyBase;
+
+ private:
+ bool EnsureHolder(JSContext* aCx, JS::Handle<JSObject*> aProxy,
+ JS::MutableHandle<JSObject*> aHolder) const final {
+ return MaybeCrossOriginObjectMixins::EnsureHolder(
+ aCx, aProxy, /* slot = */ 0, P, aHolder);
+ }
+
+ static const JSClass sClass;
+};
+
+/**
+ * Returns true if aObj is a cross-process proxy object that
+ * represents an object implementing the WebIDL interface for
+ * aProtoID.
+ */
+inline bool IsRemoteObjectProxy(JSObject* aObj, prototypes::ID aProtoID) {
+ if (!js::IsProxy(aObj)) {
+ return false;
+ }
+ return RemoteObjectProxyBase::IsRemoteObjectProxy(aObj, aProtoID);
+}
+
+/**
+ * Returns true if aObj is a cross-process proxy object, no matter
+ * which WebIDL interface it corresponds to.
+ */
+inline bool IsRemoteObjectProxy(JSObject* aObj) {
+ if (!js::IsProxy(aObj)) {
+ return false;
+ }
+ return RemoteObjectProxyBase::IsRemoteObjectProxy(aObj);
+}
+
+/**
+ * Return the browsing context for this remote outer window proxy.
+ * Only call this function on remote outer window proxies.
+ */
+BrowsingContext* GetBrowsingContext(JSObject* aProxy);
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_RemoteObjectProxy_h */
diff --git a/dom/bindings/RootedDictionary.h b/dom/bindings/RootedDictionary.h
new file mode 100644
index 0000000000..24fb859ae3
--- /dev/null
+++ b/dom/bindings/RootedDictionary.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_RootedDictionary_h__
+#define mozilla_dom_RootedDictionary_h__
+
+#include "mozilla/dom/Nullable.h"
+#include "jsapi.h"
+
+namespace mozilla::dom {
+
+template <typename T>
+class MOZ_RAII RootedDictionary final : public T, private JS::CustomAutoRooter {
+ public:
+ template <typename CX>
+ explicit RootedDictionary(const CX& cx) : T(), JS::CustomAutoRooter(cx) {}
+
+ virtual void trace(JSTracer* trc) override { this->TraceDictionary(trc); }
+};
+
+template <typename T>
+class MOZ_RAII NullableRootedDictionary final : public Nullable<T>,
+ private JS::CustomAutoRooter {
+ public:
+ template <typename CX>
+ explicit NullableRootedDictionary(const CX& cx)
+ : Nullable<T>(), JS::CustomAutoRooter(cx) {}
+
+ virtual void trace(JSTracer* trc) override {
+ if (!this->IsNull()) {
+ this->Value().TraceDictionary(trc);
+ }
+ }
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_RootedDictionary_h__ */
diff --git a/dom/bindings/RootedOwningNonNull.h b/dom/bindings/RootedOwningNonNull.h
new file mode 100644
index 0000000000..cbcb5c4ecd
--- /dev/null
+++ b/dom/bindings/RootedOwningNonNull.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/**
+ * An implementation of Rooted for OwningNonNull<T>. This works by assuming
+ * that T has a Trace() method defined on it which will trace whatever things
+ * inside the T instance need tracing.
+ *
+ * This implementation has one serious drawback: operator= doesn't work right
+ * because it's declared on Rooted directly and expects the type Rooted is
+ * templated over.
+ */
+
+#ifndef mozilla_RootedOwningNonNull_h__
+#define mozilla_RootedOwningNonNull_h__
+
+#include "mozilla/OwningNonNull.h"
+#include "js/GCPolicyAPI.h"
+#include "js/TypeDecls.h"
+
+namespace JS {
+template <typename T>
+struct GCPolicy<mozilla::OwningNonNull<T>> {
+ typedef mozilla::OwningNonNull<T> SmartPtrType;
+
+ static SmartPtrType initial() { return SmartPtrType(); }
+
+ static void trace(JSTracer* trc, SmartPtrType* tp, const char* name) {
+ // We have to be very careful here. Normally, OwningNonNull can't be null.
+ // But binding code can end up in a situation where it sets up a
+ // Rooted<OwningNonNull> and then before it gets a chance to assign to it
+ // (e.g. from the constructor of the thing being assigned) a GC happens. So
+ // we can land here when *tp stores a null pointer because it's not
+ // initialized.
+ //
+ // So we need to check for that before jumping.
+ if ((*tp).isInitialized()) {
+ (*tp)->Trace(trc);
+ }
+ }
+
+ static bool isValid(const SmartPtrType& v) {
+ return !v.isInitialized() || GCPolicy<T>::isValid(v);
+ }
+};
+} // namespace JS
+
+namespace js {
+template <typename T, typename Wrapper>
+struct WrappedPtrOperations<mozilla::OwningNonNull<T>, Wrapper> {
+ operator T&() const { return static_cast<const Wrapper*>(this)->get(); }
+};
+} // namespace js
+
+#endif /* mozilla_RootedOwningNonNull_h__ */
diff --git a/dom/bindings/RootedRecord.h b/dom/bindings/RootedRecord.h
new file mode 100644
index 0000000000..a474b290e5
--- /dev/null
+++ b/dom/bindings/RootedRecord.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_RootedRecord_h__
+#define mozilla_dom_RootedRecord_h__
+
+#include "mozilla/dom/Record.h"
+#include "js/RootingAPI.h"
+
+namespace mozilla::dom {
+
+template <typename K, typename V>
+class MOZ_RAII RootedRecord final : public Record<K, V>,
+ private JS::CustomAutoRooter {
+ public:
+ template <typename CX>
+ explicit RootedRecord(const CX& cx)
+ : Record<K, V>(), JS::CustomAutoRooter(cx) {}
+
+ virtual void trace(JSTracer* trc) override { TraceRecord(trc, *this); }
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_RootedRecord_h__ */
diff --git a/dom/bindings/RootedRefPtr.h b/dom/bindings/RootedRefPtr.h
new file mode 100644
index 0000000000..f5b2323c0f
--- /dev/null
+++ b/dom/bindings/RootedRefPtr.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/**
+ * An implementation of Rooted for RefPtr<T>. This works by assuming that T has
+ * a Trace() method defined on it which will trace whatever things inside the T
+ * instance need tracing.
+ *
+ * This implementation has one serious drawback: operator= doesn't work right
+ * because it's declared on Rooted directly and expects the type Rooted is
+ * templated over.
+ */
+
+#ifndef mozilla_RootedRefPtr_h__
+#define mozilla_RootedRefPtr_h__
+
+#include "mozilla/RefPtr.h"
+#include "js/GCPolicyAPI.h"
+#include "js/TypeDecls.h"
+
+namespace JS {
+template <typename T>
+struct GCPolicy<RefPtr<T>> {
+ static RefPtr<T> initial() { return RefPtr<T>(); }
+
+ static void trace(JSTracer* trc, RefPtr<T>* tp, const char* name) {
+ if (*tp) {
+ (*tp)->Trace(trc);
+ }
+ }
+
+ static bool isValid(const RefPtr<T>& v) {
+ return !v || GCPolicy<T>::isValid(*v.get());
+ }
+};
+} // namespace JS
+
+namespace js {
+template <typename T, typename Wrapper>
+struct WrappedPtrOperations<RefPtr<T>, Wrapper> {
+ operator T*() const { return static_cast<const Wrapper*>(this)->get(); }
+};
+} // namespace js
+
+#endif /* mozilla_RootedRefPtr_h__ */
diff --git a/dom/bindings/RootedSequence.h b/dom/bindings/RootedSequence.h
new file mode 100644
index 0000000000..c41a48f8e1
--- /dev/null
+++ b/dom/bindings/RootedSequence.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_RootedSequence_h__
+#define mozilla_dom_RootedSequence_h__
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "js/RootingAPI.h"
+
+namespace mozilla::dom::binding_detail {
+
+template <typename T>
+class MOZ_RAII RootedAutoSequence final : public AutoSequence<T>,
+ private JS::CustomAutoRooter {
+ public:
+ template <typename CX>
+ explicit RootedAutoSequence(const CX& cx)
+ : AutoSequence<T>(), JS::CustomAutoRooter(cx) {}
+
+ virtual void trace(JSTracer* trc) override { DoTraceSequence(trc, *this); }
+};
+
+} // namespace mozilla::dom::binding_detail
+
+#endif /* mozilla_dom_RootedSequence_h__ */
diff --git a/dom/bindings/SimpleGlobalObject.cpp b/dom/bindings/SimpleGlobalObject.cpp
new file mode 100644
index 0000000000..798efc94c1
--- /dev/null
+++ b/dom/bindings/SimpleGlobalObject.cpp
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/SimpleGlobalObject.h"
+
+#include "jsapi.h"
+#include "js/Class.h"
+#include "js/Object.h" // JS::GetClass, JS::GetObjectISupports, JS::SetObjectISupports
+
+#include "nsJSPrincipals.h"
+#include "nsThreadUtils.h"
+#include "nsContentUtils.h"
+
+#include "xpcprivate.h"
+
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/NullPrincipal.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(SimpleGlobalObject)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SimpleGlobalObject)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ tmp->UnlinkObjectsInGlobal();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SimpleGlobalObject)
+ tmp->TraverseObjectsInGlobal(cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(SimpleGlobalObject)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(SimpleGlobalObject)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SimpleGlobalObject)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
+NS_INTERFACE_MAP_END
+
+static SimpleGlobalObject* GetSimpleGlobal(JSObject* global);
+
+static void SimpleGlobal_finalize(JS::GCContext* gcx, JSObject* obj) {
+ SimpleGlobalObject* globalObject = GetSimpleGlobal(obj);
+ if (globalObject) {
+ globalObject->ClearWrapper(obj);
+ NS_RELEASE(globalObject);
+ }
+}
+
+static size_t SimpleGlobal_moved(JSObject* obj, JSObject* old) {
+ SimpleGlobalObject* globalObject = GetSimpleGlobal(obj);
+ if (globalObject) {
+ globalObject->UpdateWrapper(obj, old);
+ }
+ return 0;
+}
+
+static const JSClassOps SimpleGlobalClassOps = {
+ nullptr,
+ nullptr,
+ nullptr,
+ JS_NewEnumerateStandardClasses,
+ JS_ResolveStandardClass,
+ JS_MayResolveStandardClass,
+ SimpleGlobal_finalize,
+ nullptr,
+ nullptr,
+ JS_GlobalObjectTraceHook,
+};
+
+static const js::ClassExtension SimpleGlobalClassExtension = {
+ SimpleGlobal_moved};
+
+static_assert(JSCLASS_GLOBAL_APPLICATION_SLOTS > 0,
+ "Need at least one slot for JSCLASS_SLOT0_IS_NSISUPPORTS");
+
+const JSClass SimpleGlobalClass = {"",
+ JSCLASS_GLOBAL_FLAGS |
+ JSCLASS_SLOT0_IS_NSISUPPORTS |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &SimpleGlobalClassOps,
+ JS_NULL_CLASS_SPEC,
+ &SimpleGlobalClassExtension,
+ JS_NULL_OBJECT_OPS};
+
+static SimpleGlobalObject* GetSimpleGlobal(JSObject* global) {
+ MOZ_ASSERT(JS::GetClass(global) == &SimpleGlobalClass);
+
+ return JS::GetObjectISupports<SimpleGlobalObject>(global);
+}
+
+// static
+JSObject* SimpleGlobalObject::Create(GlobalType globalType,
+ JS::Handle<JS::Value> proto) {
+ // We can't root our return value with our AutoJSAPI because the rooting
+ // analysis thinks ~AutoJSAPI can GC. So we need to root in a scope outside
+ // the lifetime of the AutoJSAPI.
+ JS::Rooted<JSObject*> global(RootingCx());
+
+ { // Scope to ensure the AutoJSAPI destructor runs before we end up returning
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+
+ JS::RealmOptions options;
+ options.creationOptions()
+ .setInvisibleToDebugger(true)
+ // Put our SimpleGlobalObjects in the system zone, so we won't create
+ // lots of zones for what are probably very short-lived
+ // compartments. This should help them be GCed quicker and take up
+ // less memory before they're GCed.
+ .setNewCompartmentInSystemZone();
+
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsIPrincipal> principal =
+ NullPrincipal::CreateWithoutOriginAttributes();
+ options.creationOptions().setTrace(xpc::TraceXPCGlobal);
+ global = xpc::CreateGlobalObject(cx, &SimpleGlobalClass,
+ nsJSPrincipals::get(principal), options);
+ } else {
+ global = JS_NewGlobalObject(cx, &SimpleGlobalClass, nullptr,
+ JS::DontFireOnNewGlobalHook, options);
+ }
+
+ if (!global) {
+ jsapi.ClearException();
+ return nullptr;
+ }
+
+ JSAutoRealm ar(cx, global);
+
+ // It's important to create the nsIGlobalObject for our new global before we
+ // start trying to wrap things like the prototype into its compartment,
+ // because the wrap operation relies on the global having its
+ // nsIGlobalObject already.
+ RefPtr<SimpleGlobalObject> globalObject =
+ new SimpleGlobalObject(global, globalType);
+
+ // Pass on ownership of globalObject to |global|.
+ JS::SetObjectISupports(global, globalObject.forget().take());
+
+ if (proto.isObjectOrNull()) {
+ JS::Rooted<JSObject*> protoObj(cx, proto.toObjectOrNull());
+ if (!JS_WrapObject(cx, &protoObj)) {
+ jsapi.ClearException();
+ return nullptr;
+ }
+
+ if (!JS_SetPrototype(cx, global, protoObj)) {
+ jsapi.ClearException();
+ return nullptr;
+ }
+ } else if (!proto.isUndefined()) {
+ // Bogus proto.
+ return nullptr;
+ }
+
+ JS_FireOnNewGlobalObject(cx, global);
+ }
+
+ return global;
+}
+
+// static
+SimpleGlobalObject::GlobalType SimpleGlobalObject::SimpleGlobalType(
+ JSObject* obj) {
+ if (JS::GetClass(obj) != &SimpleGlobalClass) {
+ return SimpleGlobalObject::GlobalType::NotSimpleGlobal;
+ }
+
+ SimpleGlobalObject* globalObject = GetSimpleGlobal(obj);
+ return globalObject->Type();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/SimpleGlobalObject.h b/dom/bindings/SimpleGlobalObject.h
new file mode 100644
index 0000000000..baf914af0f
--- /dev/null
+++ b/dom/bindings/SimpleGlobalObject.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/**
+ * A simplere nsIGlobalObject implementation that can be used to set up a new
+ * global without anything interesting in it other than the JS builtins. This
+ * is safe to use on both mainthread and worker threads.
+ */
+
+#ifndef mozilla_dom_SimpleGlobalObject_h__
+#define mozilla_dom_SimpleGlobalObject_h__
+
+#include "nsContentUtils.h"
+#include "nsIGlobalObject.h"
+#include "nsWrapperCache.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "nsISupportsImpl.h"
+#include "nsCycleCollectionParticipant.h"
+
+namespace mozilla::dom {
+
+class SimpleGlobalObject : public nsIGlobalObject, public nsWrapperCache {
+ public:
+ enum class GlobalType {
+ BindingDetail, // Should only be used by DOM bindings code.
+ WorkerDebuggerSandbox,
+ NotSimpleGlobal // Sentinel to be used by BasicGlobalType.
+ };
+
+ // Create a new JS global object that can be used to do some work. This
+ // global will NOT have any DOM APIs exposed in it, will not be visible to the
+ // debugger, and will not have a useful concept of principals, so don't try to
+ // use it with any DOM objects. Apart from that, running code with
+ // side-effects is safe in this global. Importantly, when you are first
+ // handed this global it's guaranteed to have pristine built-ins. The
+ // corresponding nsIGlobalObject* for this global object will be a
+ // SimpleGlobalObject of the type provided; JS::GetPrivate on the returned
+ // JSObject* will return the SimpleGlobalObject*.
+ //
+ // If the provided prototype value is undefined, it is ignored. If it's an
+ // object or null, it's set as the prototype of the created global. If it's
+ // anything else, this function returns null.
+ //
+ // Note that creating new globals is not cheap and should not be done
+ // gratuitously. Please think carefully before you use this function.
+ static JSObject* Create(GlobalType globalType, JS::Handle<JS::Value> proto =
+ JS::UndefinedHandleValue);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(SimpleGlobalObject)
+
+ // Gets the GlobalType of this SimpleGlobalObject.
+ GlobalType Type() const { return mType; }
+
+ // Gets the GlobalType of the SimpleGlobalObject for the given JSObject*, if
+ // the given JSObject* is the global corresponding to a SimpleGlobalObject.
+ // Oherwise, returns GlobalType::NotSimpleGlobal.
+ static GlobalType SimpleGlobalType(JSObject* obj);
+
+ JSObject* GetGlobalJSObject() override { return GetWrapper(); }
+ JSObject* GetGlobalJSObjectPreserveColor() const override {
+ return GetWrapperPreserveColor();
+ }
+
+ OriginTrials Trials() const override { return {}; }
+
+ JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ MOZ_CRASH("SimpleGlobalObject doesn't use DOM bindings!");
+ }
+
+ bool ShouldResistFingerprinting(
+ RFPTarget aTarget = RFPTarget::Unknown) const override {
+ return nsContentUtils::ShouldResistFingerprinting(
+ "Presently we don't have enough context to make an informed decision"
+ "on JS Sandboxes. See 1782853",
+ aTarget);
+ }
+
+ private:
+ SimpleGlobalObject(JSObject* global, GlobalType type) : mType(type) {
+ SetWrapper(global);
+ }
+
+ virtual ~SimpleGlobalObject() { MOZ_ASSERT(!GetWrapperMaybeDead()); }
+
+ const GlobalType mType;
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_SimpleGlobalObject_h__ */
diff --git a/dom/bindings/SpiderMonkeyInterface.h b/dom/bindings/SpiderMonkeyInterface.h
new file mode 100644
index 0000000000..92cdd3090b
--- /dev/null
+++ b/dom/bindings/SpiderMonkeyInterface.h
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_SpiderMonkeyInterface_h
+#define mozilla_dom_SpiderMonkeyInterface_h
+
+#include "jsapi.h"
+#include "js/RootingAPI.h"
+#include "js/TracingAPI.h"
+
+namespace mozilla::dom {
+
+/*
+ * Class that just handles the JSObject storage and tracing for spidermonkey
+ * interfaces
+ */
+struct SpiderMonkeyInterfaceObjectStorage {
+ protected:
+ JSObject* mImplObj;
+ JSObject* mWrappedObj;
+
+ SpiderMonkeyInterfaceObjectStorage()
+ : mImplObj(nullptr), mWrappedObj(nullptr) {}
+
+ SpiderMonkeyInterfaceObjectStorage(
+ SpiderMonkeyInterfaceObjectStorage&& aOther)
+ : mImplObj(aOther.mImplObj), mWrappedObj(aOther.mWrappedObj) {
+ aOther.mImplObj = nullptr;
+ aOther.mWrappedObj = nullptr;
+ }
+
+ public:
+ inline void TraceSelf(JSTracer* trc) {
+ JS::TraceRoot(trc, &mImplObj,
+ "SpiderMonkeyInterfaceObjectStorage.mImplObj");
+ JS::TraceRoot(trc, &mWrappedObj,
+ "SpiderMonkeyInterfaceObjectStorage.mWrappedObj");
+ }
+
+ inline bool inited() const { return !!mImplObj; }
+
+ inline bool WrapIntoNewCompartment(JSContext* cx) {
+ return JS_WrapObject(
+ cx, JS::MutableHandle<JSObject*>::fromMarkedLocation(&mWrappedObj));
+ }
+
+ inline JSObject* Obj() const {
+ MOZ_ASSERT(inited());
+ return mWrappedObj;
+ }
+
+ private:
+ SpiderMonkeyInterfaceObjectStorage(
+ const SpiderMonkeyInterfaceObjectStorage&) = delete;
+};
+
+// A class for rooting an existing SpiderMonkey Interface struct
+template <typename InterfaceType>
+class MOZ_RAII SpiderMonkeyInterfaceRooter : private JS::CustomAutoRooter {
+ public:
+ template <typename CX>
+ SpiderMonkeyInterfaceRooter(const CX& cx, InterfaceType* aInterface)
+ : JS::CustomAutoRooter(cx), mInterface(aInterface) {}
+
+ virtual void trace(JSTracer* trc) override { mInterface->TraceSelf(trc); }
+
+ private:
+ SpiderMonkeyInterfaceObjectStorage* const mInterface;
+};
+
+// And a specialization for dealing with nullable SpiderMonkey interfaces
+template <typename Inner>
+struct Nullable;
+template <typename InterfaceType>
+class MOZ_RAII SpiderMonkeyInterfaceRooter<Nullable<InterfaceType>>
+ : private JS::CustomAutoRooter {
+ public:
+ template <typename CX>
+ SpiderMonkeyInterfaceRooter(const CX& cx, Nullable<InterfaceType>* aInterface)
+ : JS::CustomAutoRooter(cx), mInterface(aInterface) {}
+
+ virtual void trace(JSTracer* trc) override {
+ if (!mInterface->IsNull()) {
+ mInterface->Value().TraceSelf(trc);
+ }
+ }
+
+ private:
+ Nullable<InterfaceType>* const mInterface;
+};
+
+// Class for easily setting up a rooted SpiderMonkey interface object on the
+// stack
+template <typename InterfaceType>
+class MOZ_RAII RootedSpiderMonkeyInterface final
+ : public InterfaceType,
+ private SpiderMonkeyInterfaceRooter<InterfaceType> {
+ public:
+ template <typename CX>
+ explicit RootedSpiderMonkeyInterface(const CX& cx)
+ : InterfaceType(), SpiderMonkeyInterfaceRooter<InterfaceType>(cx, this) {}
+
+ template <typename CX>
+ RootedSpiderMonkeyInterface(const CX& cx, JSObject* obj)
+ : InterfaceType(obj),
+ SpiderMonkeyInterfaceRooter<InterfaceType>(cx, this) {}
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_SpiderMonkeyInterface_h */
diff --git a/dom/bindings/ToJSValue.cpp b/dom/bindings/ToJSValue.cpp
new file mode 100644
index 0000000000..73fc7b1e33
--- /dev/null
+++ b/dom/bindings/ToJSValue.cpp
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/ToJSValue.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/WindowProxyHolder.h"
+#include "nsAString.h"
+#include "nsContentUtils.h"
+#include "nsStringBuffer.h"
+#include "xpcpublic.h"
+
+namespace mozilla::dom {
+
+bool ToJSValue(JSContext* aCx, const nsAString& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ // Make sure we're called in a compartment
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx));
+
+ // XXXkhuey I'd love to use xpc::NonVoidStringToJsval here, but it requires
+ // a non-const nsAString for silly reasons.
+ nsStringBuffer* sharedBuffer;
+ if (!XPCStringConvert::ReadableToJSVal(aCx, aArgument, &sharedBuffer,
+ aValue)) {
+ return false;
+ }
+
+ if (sharedBuffer) {
+ NS_ADDREF(sharedBuffer);
+ }
+
+ return true;
+}
+
+bool ToJSValue(JSContext* aCx, const nsACString& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ return UTF8StringToJsval(aCx, aArgument, aValue);
+}
+
+bool ToJSValue(JSContext* aCx, nsresult aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ RefPtr<Exception> exception = CreateException(aArgument);
+ return ToJSValue(aCx, exception, aValue);
+}
+
+bool ToJSValue(JSContext* aCx, ErrorResult&& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ MOZ_ASSERT(aArgument.Failed());
+ MOZ_ASSERT(
+ !aArgument.IsUncatchableException(),
+ "Doesn't make sense to convert uncatchable exception to a JS value!");
+ MOZ_ALWAYS_TRUE(aArgument.MaybeSetPendingException(aCx));
+ MOZ_ALWAYS_TRUE(JS_GetPendingException(aCx, aValue));
+ JS_ClearPendingException(aCx);
+ return true;
+}
+
+bool ToJSValue(JSContext* aCx, Promise& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ aValue.setObject(*aArgument.PromiseObj());
+ return MaybeWrapObjectValue(aCx, aValue);
+}
+
+bool ToJSValue(JSContext* aCx, const WindowProxyHolder& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ BrowsingContext* bc = aArgument.get();
+ if (!bc) {
+ aValue.setNull();
+ return true;
+ }
+ JS::Rooted<JSObject*> windowProxy(aCx);
+ if (bc->IsInProcess()) {
+ windowProxy = bc->GetWindowProxy();
+ if (!windowProxy) {
+ nsPIDOMWindowOuter* window = bc->GetDOMWindow();
+ if (!window) {
+ // Torn down enough that we should just return null.
+ aValue.setNull();
+ return true;
+ }
+ if (!window->EnsureInnerWindow()) {
+ return Throw(aCx, NS_ERROR_UNEXPECTED);
+ }
+ windowProxy = bc->GetWindowProxy();
+ }
+ return ToJSValue(aCx, windowProxy, aValue);
+ }
+
+ if (!GetRemoteOuterWindowProxy(aCx, bc, /* aTransplantTo = */ nullptr,
+ &windowProxy)) {
+ return false;
+ }
+ aValue.setObjectOrNull(windowProxy);
+ return true;
+}
+
+// Static assertion tests for the `binding_detail::ScriptableInterfaceType`
+// helper template, used by `ToJSValue`.
+namespace binding_detail {
+static_assert(std::is_same_v<ScriptableInterfaceType<nsISupports>, nsISupports>,
+ "nsISupports works with ScriptableInterfaceType");
+static_assert(
+ std::is_same_v<ScriptableInterfaceType<nsIGlobalObject>, nsISupports>,
+ "non-scriptable interfaces get a fallback");
+static_assert(std::is_same_v<ScriptableInterfaceType<nsIObserver>, nsIObserver>,
+ "scriptable interfaces should get the correct type");
+static_assert(std::is_same_v<ScriptableInterfaceType<nsIRunnable>, nsIRunnable>,
+ "scriptable interfaces should get the correct type");
+class SingleScriptableInterface : public nsIObserver {};
+static_assert(
+ std::is_same_v<ScriptableInterfaceType<SingleScriptableInterface>,
+ nsIObserver>,
+ "Concrete type with one scriptable interface picks the correct interface");
+class MultiScriptableInterface : public nsIObserver, public nsIRunnable {};
+static_assert(std::is_same_v<ScriptableInterfaceType<MultiScriptableInterface>,
+ nsISupports>,
+ "Concrete type with multiple scriptable interfaces falls back");
+} // namespace binding_detail
+} // namespace mozilla::dom
diff --git a/dom/bindings/ToJSValue.h b/dom/bindings/ToJSValue.h
new file mode 100644
index 0000000000..e81ea41e39
--- /dev/null
+++ b/dom/bindings/ToJSValue.h
@@ -0,0 +1,486 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_ToJSValue_h
+#define mozilla_dom_ToJSValue_h
+
+#include <cstddef> // for size_t
+#include <cstdint> // for int32_t, int64_t, uint32_t, uint64_t
+#include <type_traits> // for is_base_of, enable_if_t, enable_if, is_pointer, is_same, void_t
+#include <utility> // for forward
+#include "ErrorList.h" // for nsresult
+#include "js/Array.h" // for NewArrayObject
+#include "js/GCVector.h" // for RootedVector, MutableWrappedPtrOperations
+#include "js/PropertyAndElement.h" // JS_DefineUCProperty
+#include "js/RootingAPI.h" // for MutableHandle, Rooted, Handle, Heap
+#include "js/Value.h" // for Value
+#include "js/ValueArray.h" // for HandleValueArray
+#include "jsapi.h" // for CurrentGlobalOrNull
+#include "mozilla/Assertions.h" // for AssertionConditionType, MOZ_ASSERT, MOZ_ASSERT_HELPER1
+#include "mozilla/UniquePtr.h" // for UniquePtr
+#include "mozilla/Unused.h" // for Unused
+#include "mozilla/dom/BindingUtils.h" // for MaybeWrapValue, MaybeWrapObjectOrNullValue, XPCOMObjectToJsval, GetOrCreateDOMReflector
+#include "mozilla/dom/CallbackObject.h" // for CallbackObject
+#include "mozilla/dom/Record.h"
+#include "nsID.h" // for NS_GET_TEMPLATE_IID, nsIID
+#include "nsISupports.h" // for nsISupports
+#include "nsStringFwd.h" // for nsAString
+#include "nsTArrayForwardDeclare.h"
+#include "xpcObjectHelper.h" // for xpcObjectHelper
+
+namespace mozilla::dom {
+
+class CallbackObject;
+class Promise;
+class WindowProxyHolder;
+template <typename TypedArrayType>
+class TypedArrayCreator;
+
+// If ToJSValue returns false, it must set an exception on the
+// JSContext.
+
+// Accept strings.
+[[nodiscard]] bool ToJSValue(JSContext* aCx, const nsAString& aArgument,
+ JS::MutableHandle<JS::Value> aValue);
+
+// Treats the input as UTF-8, and throws otherwise.
+[[nodiscard]] bool ToJSValue(JSContext* aCx, const nsACString& aArgument,
+ JS::MutableHandle<JS::Value> aValue);
+
+// Accept booleans. But be careful here: if we just have a function that takes
+// a boolean argument, then any pointer that doesn't match one of our other
+// signatures/templates will get treated as a boolean, which is clearly not
+// desirable. So make this a template that only gets used if the argument type
+// is actually boolean
+template <typename T>
+[[nodiscard]] std::enable_if_t<std::is_same<T, bool>::value, bool> ToJSValue(
+ JSContext* aCx, T aArgument, JS::MutableHandle<JS::Value> aValue) {
+ // Make sure we're called in a compartment
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx));
+
+ aValue.setBoolean(aArgument);
+ return true;
+}
+
+// Accept integer types
+inline bool ToJSValue(JSContext* aCx, int32_t aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ // Make sure we're called in a compartment
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx));
+
+ aValue.setInt32(aArgument);
+ return true;
+}
+
+inline bool ToJSValue(JSContext* aCx, uint32_t aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ // Make sure we're called in a compartment
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx));
+
+ aValue.setNumber(aArgument);
+ return true;
+}
+
+inline bool ToJSValue(JSContext* aCx, int64_t aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ // Make sure we're called in a compartment
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx));
+
+ aValue.setNumber(double(aArgument));
+ return true;
+}
+
+inline bool ToJSValue(JSContext* aCx, uint64_t aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ // Make sure we're called in a compartment
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx));
+
+ aValue.setNumber(double(aArgument));
+ return true;
+}
+
+// accept floating point types
+inline bool ToJSValue(JSContext* aCx, float aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ // Make sure we're called in a compartment
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx));
+
+ aValue.setNumber(aArgument);
+ return true;
+}
+
+inline bool ToJSValue(JSContext* aCx, double aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ // Make sure we're called in a compartment
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx));
+
+ aValue.set(JS_NumberValue(aArgument));
+ return true;
+}
+
+// Accept CallbackObjects
+[[nodiscard]] inline bool ToJSValue(JSContext* aCx, CallbackObject& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ // Make sure we're called in a compartment
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx));
+
+ aValue.setObjectOrNull(aArgument.Callback(aCx));
+
+ return MaybeWrapValue(aCx, aValue);
+}
+
+// Accept objects that inherit from nsWrapperCache (e.g. most
+// DOM objects).
+template <class T>
+[[nodiscard]] std::enable_if_t<std::is_base_of<nsWrapperCache, T>::value, bool>
+ToJSValue(JSContext* aCx, T& aArgument, JS::MutableHandle<JS::Value> aValue) {
+ // Make sure we're called in a compartment
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx));
+
+ return GetOrCreateDOMReflector(aCx, aArgument, aValue);
+}
+
+// Accept non-refcounted DOM objects that do not inherit from
+// nsWrapperCache. Refcounted ones would be too much of a footgun:
+// you could convert them to JS twice and get two different objects.
+namespace binding_detail {
+template <class T>
+[[nodiscard]] std::enable_if_t<
+ std::is_base_of<NonRefcountedDOMObject, T>::value, bool>
+ToJSValueFromPointerHelper(JSContext* aCx, T* aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ // Make sure we're called in a compartment
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx));
+
+ // This is a cut-down version of
+ // WrapNewBindingNonWrapperCachedObject that doesn't need to deal
+ // with nearly as many cases.
+ if (!aArgument) {
+ aValue.setNull();
+ return true;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx);
+ if (!aArgument->WrapObject(aCx, nullptr, &obj)) {
+ return false;
+ }
+
+ aValue.setObject(*obj);
+ return true;
+}
+} // namespace binding_detail
+
+// We can take a non-refcounted non-wrapper-cached DOM object that lives in a
+// UniquePtr.
+template <class T>
+[[nodiscard]] std::enable_if_t<
+ std::is_base_of<NonRefcountedDOMObject, T>::value, bool>
+ToJSValue(JSContext* aCx, UniquePtr<T>&& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ if (!binding_detail::ToJSValueFromPointerHelper(aCx, aArgument.get(),
+ aValue)) {
+ return false;
+ }
+
+ // JS object took ownership
+ Unused << aArgument.release();
+ return true;
+}
+
+// Accept typed arrays built from appropriate nsTArray values
+template <typename T>
+[[nodiscard]]
+typename std::enable_if<std::is_base_of<AllTypedArraysBase, T>::value,
+ bool>::type
+ToJSValue(JSContext* aCx, const TypedArrayCreator<T>& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ // Make sure we're called in a compartment
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx));
+
+ JSObject* obj = aArgument.Create(aCx);
+ if (!obj) {
+ return false;
+ }
+ aValue.setObject(*obj);
+ return true;
+}
+
+namespace binding_detail {
+// Helper type alias for picking a script-exposable non-wrappercached XPIDL
+// interface to expose to JS code. Falls back to `nsISupports` if the specific
+// interface type is ambiguous.
+template <typename T, typename = void>
+struct GetScriptableInterfaceType {
+ using Type = nsISupports;
+
+ static_assert(std::is_base_of_v<nsISupports, T>,
+ "T must inherit from nsISupports");
+};
+template <typename T>
+struct GetScriptableInterfaceType<
+ T, std::void_t<typename T::ScriptableInterfaceType>> {
+ using Type = typename T::ScriptableInterfaceType;
+
+ static_assert(std::is_base_of_v<Type, T>,
+ "T must inherit from ScriptableInterfaceType");
+ static_assert(std::is_base_of_v<nsISupports, Type>,
+ "ScriptableInterfaceType must inherit from nsISupports");
+};
+
+template <typename T>
+using ScriptableInterfaceType = typename GetScriptableInterfaceType<T>::Type;
+} // namespace binding_detail
+
+// Accept objects that inherit from nsISupports but not nsWrapperCache (e.g.
+// DOM File).
+template <class T>
+[[nodiscard]] std::enable_if_t<!std::is_base_of<nsWrapperCache, T>::value &&
+ !std::is_base_of<CallbackObject, T>::value &&
+ std::is_base_of<nsISupports, T>::value,
+ bool>
+ToJSValue(JSContext* aCx, T& aArgument, JS::MutableHandle<JS::Value> aValue) {
+ // Make sure we're called in a compartment
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx));
+
+ xpcObjectHelper helper(ToSupports(&aArgument));
+ JS::Rooted<JSObject*> scope(aCx, JS::CurrentGlobalOrNull(aCx));
+ const nsIID& iid =
+ NS_GET_TEMPLATE_IID(binding_detail::ScriptableInterfaceType<T>);
+ return XPCOMObjectToJsval(aCx, scope, helper, &iid, true, aValue);
+}
+
+[[nodiscard]] bool ToJSValue(JSContext* aCx, const WindowProxyHolder& aArgument,
+ JS::MutableHandle<JS::Value> aValue);
+
+// Accept nsRefPtr/nsCOMPtr
+template <typename T>
+[[nodiscard]] bool ToJSValue(JSContext* aCx, const nsCOMPtr<T>& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ return ToJSValue(aCx, *aArgument.get(), aValue);
+}
+
+template <typename T>
+[[nodiscard]] bool ToJSValue(JSContext* aCx, const RefPtr<T>& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ return ToJSValue(aCx, *aArgument.get(), aValue);
+}
+
+template <typename T>
+[[nodiscard]] bool ToJSValue(JSContext* aCx, const NonNull<T>& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ return ToJSValue(aCx, *aArgument.get(), aValue);
+}
+
+template <typename T>
+[[nodiscard]] bool ToJSValue(JSContext* aCx, const OwningNonNull<T>& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ return ToJSValue(aCx, *aArgument.get(), aValue);
+}
+
+// Accept WebIDL dictionaries
+template <class T>
+[[nodiscard]] std::enable_if_t<std::is_base_of<DictionaryBase, T>::value, bool>
+ToJSValue(JSContext* aCx, const T& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ return aArgument.ToObjectInternal(aCx, aValue);
+}
+
+// Accept existing JS values (which may not be same-compartment with us
+[[nodiscard]] inline bool ToJSValue(JSContext* aCx, const JS::Value& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ aValue.set(aArgument);
+ return MaybeWrapValue(aCx, aValue);
+}
+[[nodiscard]] inline bool ToJSValue(JSContext* aCx,
+ JS::Handle<JS::Value> aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ aValue.set(aArgument);
+ return MaybeWrapValue(aCx, aValue);
+}
+
+// Accept existing JS values on the Heap (which may not be same-compartment with
+// us
+[[nodiscard]] inline bool ToJSValue(JSContext* aCx,
+ const JS::Heap<JS::Value>& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ aValue.set(aArgument);
+ return MaybeWrapValue(aCx, aValue);
+}
+
+// Accept existing rooted JS values (which may not be same-compartment with us
+[[nodiscard]] inline bool ToJSValue(JSContext* aCx,
+ const JS::Rooted<JS::Value>& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ aValue.set(aArgument);
+ return MaybeWrapValue(aCx, aValue);
+}
+
+// Accept existing rooted JS objects (which may not be same-compartment with
+// us).
+[[nodiscard]] inline bool ToJSValue(JSContext* aCx,
+ const JS::Rooted<JSObject*>& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ aValue.setObjectOrNull(aArgument);
+ return MaybeWrapObjectOrNullValue(aCx, aValue);
+}
+
+// Accept nsresult, for use in rejections, and create an XPCOM
+// exception object representing that nsresult.
+[[nodiscard]] bool ToJSValue(JSContext* aCx, nsresult aArgument,
+ JS::MutableHandle<JS::Value> aValue);
+
+// Accept ErrorResult, for use in rejections, and create an exception
+// representing the failure. Note, the ErrorResult must indicate a failure
+// with aArgument.Failure() returning true.
+[[nodiscard]] bool ToJSValue(JSContext* aCx, ErrorResult&& aArgument,
+ JS::MutableHandle<JS::Value> aValue);
+
+// Accept owning WebIDL unions.
+template <typename T>
+[[nodiscard]] std::enable_if_t<std::is_base_of<AllOwningUnionBase, T>::value,
+ bool>
+ToJSValue(JSContext* aCx, const T& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
+ return aArgument.ToJSVal(aCx, global, aValue);
+}
+
+// Accept pointers to other things we accept
+template <typename T>
+[[nodiscard]] std::enable_if_t<std::is_pointer<T>::value, bool> ToJSValue(
+ JSContext* aCx, T aArgument, JS::MutableHandle<JS::Value> aValue) {
+ return ToJSValue(aCx, *aArgument, aValue);
+}
+
+// Accept Promise objects, which need special handling.
+[[nodiscard]] bool ToJSValue(JSContext* aCx, Promise& aArgument,
+ JS::MutableHandle<JS::Value> aValue);
+
+// Accept arrays (and nested arrays) of other things we accept
+template <typename T>
+[[nodiscard]] bool ToJSValue(JSContext* aCx, T* aArguments, size_t aLength,
+ JS::MutableHandle<JS::Value> aValue);
+
+template <typename T>
+[[nodiscard]] bool ToJSValue(JSContext* aCx, const nsTArray<T>& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ return ToJSValue(aCx, aArgument.Elements(), aArgument.Length(), aValue);
+}
+
+template <typename T>
+[[nodiscard]] bool ToJSValue(JSContext* aCx, const FallibleTArray<T>& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ return ToJSValue(aCx, aArgument.Elements(), aArgument.Length(), aValue);
+}
+
+template <typename T, int N>
+[[nodiscard]] bool ToJSValue(JSContext* aCx, const T (&aArgument)[N],
+ JS::MutableHandle<JS::Value> aValue) {
+ return ToJSValue(aCx, aArgument, N, aValue);
+}
+
+// Accept arrays of other things we accept
+template <typename T>
+[[nodiscard]] bool ToJSValue(JSContext* aCx, T* aArguments, size_t aLength,
+ JS::MutableHandle<JS::Value> aValue) {
+ // Make sure we're called in a compartment
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx));
+
+ JS::RootedVector<JS::Value> v(aCx);
+ if (!v.resize(aLength)) {
+ return false;
+ }
+ for (size_t i = 0; i < aLength; ++i) {
+ if (!ToJSValue(aCx, aArguments[i], v[i])) {
+ return false;
+ }
+ }
+ JSObject* arrayObj = JS::NewArrayObject(aCx, v);
+ if (!arrayObj) {
+ return false;
+ }
+ aValue.setObject(*arrayObj);
+ return true;
+}
+
+// Accept tuple of other things we accept. The result will be a JS array object.
+template <typename... Elements>
+[[nodiscard]] bool ToJSValue(JSContext* aCx,
+ const std::tuple<Elements...>& aArguments,
+ JS::MutableHandle<JS::Value> aValue) {
+ // Make sure we're called in a compartment
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx));
+
+ JS::RootedVector<JS::Value> v(aCx);
+ if (!v.resize(sizeof...(Elements))) {
+ return false;
+ }
+ bool ok = true;
+ size_t i = 0;
+ auto Callable = [aCx, &ok, &v, &i](auto& aElem) {
+ ok = ok && ToJSValue(aCx, aElem, v[i++]);
+ };
+ std::apply([Callable](auto&&... args) { (Callable(args), ...); }, aArguments);
+
+ if (!ok) {
+ return false;
+ }
+ JSObject* arrayObj = JS::NewArrayObject(aCx, v);
+ if (!arrayObj) {
+ return false;
+ }
+ aValue.setObject(*arrayObj);
+ return true;
+}
+
+// Accept records of other things we accept. N.B. This assumes that
+// keys are either UTF-8 or UTF-16-ish. See Bug 1706058.
+template <typename K, typename V>
+[[nodiscard]] bool ToJSValue(JSContext* aCx, const Record<K, V>& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ JS::Rooted<JSObject*> recordObj(aCx, JS_NewPlainObject(aCx));
+ if (!recordObj) {
+ return false;
+ }
+
+ for (auto& entry : aArgument.Entries()) {
+ JS::Rooted<JS::Value> value(aCx);
+ if (!ToJSValue(aCx, entry.mValue, &value)) {
+ return false;
+ }
+
+ if constexpr (std::is_same_v<nsCString, decltype(entry.mKey)>) {
+ NS_ConvertUTF8toUTF16 expandedKey(entry.mKey);
+ if (!JS_DefineUCProperty(aCx, recordObj, expandedKey.BeginReading(),
+ expandedKey.Length(), value, JSPROP_ENUMERATE)) {
+ return false;
+ }
+ } else {
+ if (!JS_DefineUCProperty(aCx, recordObj, entry.mKey.BeginReading(),
+ entry.mKey.Length(), value, JSPROP_ENUMERATE)) {
+ return false;
+ }
+ }
+ }
+
+ aValue.setObject(*recordObj);
+ return true;
+}
+
+template <typename T>
+[[nodiscard]] bool ToJSValue(JSContext* aCx, const Nullable<T>& aArgument,
+ JS::MutableHandle<JS::Value> aValue) {
+ if (aArgument.IsNull()) {
+ aValue.setNull();
+ return true;
+ }
+
+ return ToJSValue(aCx, aArgument.Value(), aValue);
+}
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_ToJSValue_h */
diff --git a/dom/bindings/TypedArray.h b/dom/bindings/TypedArray.h
new file mode 100644
index 0000000000..b419575b87
--- /dev/null
+++ b/dom/bindings/TypedArray.h
@@ -0,0 +1,290 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_TypedArray_h
+#define mozilla_dom_TypedArray_h
+
+#include <utility>
+
+#include "js/ArrayBuffer.h"
+#include "js/ArrayBufferMaybeShared.h"
+#include "js/experimental/TypedData.h" // js::Unwrap(Ui|I)nt(8|16|32)Array, js::Get(Ui|I)nt(8|16|32)ArrayLengthAndData, js::UnwrapUint8ClampedArray, js::GetUint8ClampedArrayLengthAndData, js::UnwrapFloat(32|64)Array, js::GetFloat(32|64)ArrayLengthAndData, JS_GetArrayBufferViewType
+#include "js/GCAPI.h" // JS::AutoCheckCannotGC
+#include "js/RootingAPI.h" // JS::Rooted
+#include "js/ScalarType.h" // JS::Scalar::Type
+#include "js/SharedArrayBuffer.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/SpiderMonkeyInterface.h"
+#include "nsWrapperCache.h"
+#include "nsWrapperCacheInlines.h"
+
+namespace mozilla::dom {
+
+/*
+ * Various typed array classes for argument conversion. We have a base class
+ * that has a way of initializing a TypedArray from an existing typed array, and
+ * a subclass of the base class that supports creation of a relevant typed array
+ * or array buffer object.
+ */
+template <class ArrayT>
+struct TypedArray_base : public SpiderMonkeyInterfaceObjectStorage,
+ AllTypedArraysBase {
+ using element_type = typename ArrayT::DataType;
+
+ TypedArray_base()
+ : mData(nullptr), mLength(0), mShared(false), mComputed(false) {}
+
+ TypedArray_base(TypedArray_base&& aOther)
+ : SpiderMonkeyInterfaceObjectStorage(std::move(aOther)),
+ mData(aOther.mData),
+ mLength(aOther.mLength),
+ mShared(aOther.mShared),
+ mComputed(aOther.mComputed) {
+ aOther.Reset();
+ }
+
+ private:
+ mutable element_type* mData;
+ mutable uint32_t mLength;
+ mutable bool mShared;
+ mutable bool mComputed;
+
+ public:
+ inline bool Init(JSObject* obj) {
+ MOZ_ASSERT(!inited());
+ mImplObj = mWrappedObj = ArrayT::unwrap(obj).asObject();
+ return inited();
+ }
+
+ // About shared memory:
+ //
+ // Any DOM TypedArray as well as any DOM ArrayBufferView can map the
+ // memory of either a JS ArrayBuffer or a JS SharedArrayBuffer. If
+ // the TypedArray maps a SharedArrayBuffer the Length() and Data()
+ // accessors on the DOM view will return zero and nullptr; to get
+ // the actual length and data, call the LengthAllowShared() and
+ // DataAllowShared() accessors instead.
+ //
+ // Two methods are available for determining if a DOM view maps
+ // shared memory. The IsShared() method is cheap and can be called
+ // if the view has been computed; the JS_GetTypedArraySharedness()
+ // method is slightly more expensive and can be called on the Obj()
+ // value if the view may not have been computed and if the value is
+ // known to represent a JS TypedArray.
+ //
+ // (Just use JS::IsSharedArrayBuffer() to test if any object is of
+ // that type.)
+ //
+ // Code that elects to allow views that map shared memory to be used
+ // -- ie, code that "opts in to shared memory" -- should generally
+ // not access the raw data buffer with standard C++ mechanisms as
+ // that creates the possibility of C++ data races, which is
+ // undefined behavior. The JS engine will eventually export (bug
+ // 1225033) a suite of methods that avoid undefined behavior.
+ //
+ // Callers of Obj() that do not opt in to shared memory can produce
+ // better diagnostics by checking whether the JSObject in fact maps
+ // shared memory and throwing an error if it does. However, it is
+ // safe to use the value of Obj() without such checks.
+ //
+ // The DOM TypedArray abstraction prevents the underlying buffer object
+ // from being accessed directly, but JS_GetArrayBufferViewBuffer(Obj())
+ // will obtain the buffer object. Code that calls that function must
+ // not assume the returned buffer is an ArrayBuffer. That is guarded
+ // against by an out parameter on that call that communicates the
+ // sharedness of the buffer.
+ //
+ // Finally, note that the buffer memory of a SharedArrayBuffer is
+ // not detachable.
+
+ inline bool IsShared() const {
+ MOZ_ASSERT(mComputed);
+ return mShared;
+ }
+
+ inline element_type* Data() const {
+ MOZ_ASSERT(mComputed);
+ return mData;
+ }
+
+ // Return a pointer to data that will not move during a GC.
+ //
+ // For some smaller views, this will copy the data into the provided buffer
+ // and return that buffer as the pointer. Otherwise, this will return a
+ // direct pointer to the actual data with no copying. If the provided buffer
+ // is not large enough, nullptr will be returned. If bufSize is at least
+ // JS_MaxMovableTypedArraySize(), the data is guaranteed to fit.
+ inline element_type* FixedData(uint8_t* buffer, size_t bufSize) const {
+ MOZ_ASSERT(mComputed);
+ return JS_GetArrayBufferViewFixedData(mImplObj, buffer, bufSize);
+ }
+
+ inline uint32_t Length() const {
+ MOZ_ASSERT(mComputed);
+ return mLength;
+ }
+
+ inline void ComputeState() const {
+ MOZ_ASSERT(inited());
+ MOZ_ASSERT(!mComputed);
+ size_t length;
+ JS::AutoCheckCannotGC nogc;
+ mData =
+ ArrayT::fromObject(mImplObj).getLengthAndData(&length, &mShared, nogc);
+ MOZ_RELEASE_ASSERT(length <= INT32_MAX,
+ "Bindings must have checked ArrayBuffer{View} length");
+ mLength = length;
+ mComputed = true;
+ }
+
+ inline void Reset() {
+ // This method mostly exists to inform the GC rooting hazard analysis that
+ // the variable can be considered dead, at least until you do anything else
+ // with it.
+ mData = nullptr;
+ mLength = 0;
+ mShared = false;
+ mComputed = false;
+ }
+
+ private:
+ TypedArray_base(const TypedArray_base&) = delete;
+};
+
+template <class ArrayT>
+struct TypedArray : public TypedArray_base<ArrayT> {
+ using Base = TypedArray_base<ArrayT>;
+ using element_type = typename Base::element_type;
+
+ TypedArray() = default;
+
+ TypedArray(TypedArray&& aOther) = default;
+
+ static inline JSObject* Create(JSContext* cx, nsWrapperCache* creator,
+ uint32_t length,
+ const element_type* data = nullptr) {
+ JS::Rooted<JSObject*> creatorWrapper(cx);
+ Maybe<JSAutoRealm> ar;
+ if (creator && (creatorWrapper = creator->GetWrapperPreserveColor())) {
+ ar.emplace(cx, creatorWrapper);
+ }
+
+ return CreateCommon(cx, length, data);
+ }
+
+ static inline JSObject* Create(JSContext* cx, uint32_t length,
+ const element_type* data = nullptr) {
+ return CreateCommon(cx, length, data);
+ }
+
+ static inline JSObject* Create(JSContext* cx, nsWrapperCache* creator,
+ Span<const element_type> data) {
+ // Span<> uses size_t as a length, and we use uint32_t instead.
+ if (MOZ_UNLIKELY(data.Length() > UINT32_MAX)) {
+ JS_ReportOutOfMemory(cx);
+ return nullptr;
+ }
+ return Create(cx, creator, data.Length(), data.Elements());
+ }
+
+ static inline JSObject* Create(JSContext* cx, Span<const element_type> data) {
+ // Span<> uses size_t as a length, and we use uint32_t instead.
+ if (MOZ_UNLIKELY(data.Length() > UINT32_MAX)) {
+ JS_ReportOutOfMemory(cx);
+ return nullptr;
+ }
+ return CreateCommon(cx, data.Length(), data.Elements());
+ }
+
+ private:
+ static inline JSObject* CreateCommon(JSContext* cx, uint32_t length,
+ const element_type* data) {
+ auto array = ArrayT::create(cx, length);
+ if (!array) {
+ return nullptr;
+ }
+ if (data) {
+ JS::AutoCheckCannotGC nogc;
+ bool isShared;
+ element_type* buf = array.getData(&isShared, nogc);
+ // Data will not be shared, until a construction protocol exists
+ // for constructing shared data.
+ MOZ_ASSERT(!isShared);
+ memcpy(buf, data, length * sizeof(element_type));
+ }
+ return array.asObject();
+ }
+
+ TypedArray(const TypedArray&) = delete;
+};
+
+template <JS::Scalar::Type GetViewType(JSObject*)>
+struct ArrayBufferView_base : public TypedArray_base<JS::ArrayBufferView> {
+ private:
+ using Base = TypedArray_base<JS::ArrayBufferView>;
+
+ public:
+ ArrayBufferView_base() : Base(), mType(JS::Scalar::MaxTypedArrayViewType) {}
+
+ ArrayBufferView_base(ArrayBufferView_base&& aOther)
+ : Base(std::move(aOther)), mType(aOther.mType) {
+ aOther.mType = JS::Scalar::MaxTypedArrayViewType;
+ }
+
+ private:
+ JS::Scalar::Type mType;
+
+ public:
+ inline bool Init(JSObject* obj) {
+ if (!Base::Init(obj)) {
+ return false;
+ }
+
+ mType = GetViewType(this->Obj());
+ return true;
+ }
+
+ inline JS::Scalar::Type Type() const {
+ MOZ_ASSERT(this->inited());
+ return mType;
+ }
+};
+
+using Int8Array = TypedArray<JS::Int8Array>;
+using Uint8Array = TypedArray<JS::Uint8Array>;
+using Uint8ClampedArray = TypedArray<JS::Uint8ClampedArray>;
+using Int16Array = TypedArray<JS::Int16Array>;
+using Uint16Array = TypedArray<JS::Uint16Array>;
+using Int32Array = TypedArray<JS::Int32Array>;
+using Uint32Array = TypedArray<JS::Uint32Array>;
+using Float32Array = TypedArray<JS::Float32Array>;
+using Float64Array = TypedArray<JS::Float64Array>;
+using ArrayBufferView = ArrayBufferView_base<JS_GetArrayBufferViewType>;
+using ArrayBuffer = TypedArray<JS::ArrayBuffer>;
+
+// A class for converting an nsTArray to a TypedArray
+// Note: A TypedArrayCreator must not outlive the nsTArray it was created from.
+// So this is best used to pass from things that understand nsTArray to
+// things that understand TypedArray, as with ToJSValue.
+template <typename TypedArrayType>
+class TypedArrayCreator {
+ typedef nsTArray<typename TypedArrayType::element_type> ArrayType;
+
+ public:
+ explicit TypedArrayCreator(const ArrayType& aArray) : mArray(aArray) {}
+
+ JSObject* Create(JSContext* aCx) const {
+ return TypedArrayType::Create(aCx, mArray.Length(), mArray.Elements());
+ }
+
+ private:
+ const ArrayType& mArray;
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_TypedArray_h */
diff --git a/dom/bindings/UnionMember.h b/dom/bindings/UnionMember.h
new file mode 100644
index 0000000000..2fba7910fa
--- /dev/null
+++ b/dom/bindings/UnionMember.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/* A class for holding the members of a union. */
+
+#ifndef mozilla_dom_UnionMember_h
+#define mozilla_dom_UnionMember_h
+
+#include "mozilla/Alignment.h"
+#include "mozilla/Attributes.h"
+#include <utility>
+
+namespace mozilla::dom {
+
+// The union type has an enum to keep track of which of its UnionMembers has
+// been constructed.
+template <class T>
+class UnionMember {
+ AlignedStorage2<T> mStorage;
+
+ // Copy construction can't be supported because C++ requires that any enclosed
+ // T be initialized in a way C++ knows about -- that is, by |new| or similar.
+ UnionMember(const UnionMember&) = delete;
+
+ public:
+ UnionMember() = default;
+ ~UnionMember() = default;
+
+ template <typename... Args>
+ T& SetValue(Args&&... args) {
+ new (mStorage.addr()) T(std::forward<Args>(args)...);
+ return *mStorage.addr();
+ }
+
+ T& Value() { return *mStorage.addr(); }
+ const T& Value() const { return *mStorage.addr(); }
+ void Destroy() { mStorage.addr()->~T(); }
+} MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS;
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_UnionMember_h
diff --git a/dom/bindings/WebIDLGlobalNameHash.cpp b/dom/bindings/WebIDLGlobalNameHash.cpp
new file mode 100644
index 0000000000..4ed6b96706
--- /dev/null
+++ b/dom/bindings/WebIDLGlobalNameHash.cpp
@@ -0,0 +1,274 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "WebIDLGlobalNameHash.h"
+#include "js/Class.h"
+#include "js/GCAPI.h"
+#include "js/Id.h"
+#include "js/Object.h" // JS::GetClass, JS::GetReservedSlot
+#include "js/Wrapper.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/BindingNames.h"
+#include "mozilla/dom/DOMJSClass.h"
+#include "mozilla/dom/Exceptions.h"
+#include "mozilla/dom/JSSlots.h"
+#include "mozilla/dom/PrototypeList.h"
+#include "mozilla/dom/ProxyHandlerUtils.h"
+#include "mozilla/dom/RegisterBindings.h"
+#include "nsGlobalWindow.h"
+#include "nsTHashtable.h"
+#include "WrapperFactory.h"
+
+namespace mozilla::dom {
+
+static JSObject* FindNamedConstructorForXray(
+ JSContext* aCx, JS::Handle<jsid> aId, const WebIDLNameTableEntry* aEntry) {
+ JSObject* interfaceObject =
+ GetPerInterfaceObjectHandle(aCx, aEntry->mConstructorId, aEntry->mCreate,
+ /* aDefineOnGlobal = */ false);
+ if (!interfaceObject) {
+ return nullptr;
+ }
+
+ // This is a call over Xrays, so we will actually use the return value
+ // (instead of just having it defined on the global now). Check for named
+ // constructors with this id, in case that's what the caller is asking for.
+ for (unsigned slot = DOM_INTERFACE_SLOTS_BASE;
+ slot < JSCLASS_RESERVED_SLOTS(JS::GetClass(interfaceObject)); ++slot) {
+ JSObject* constructor =
+ &JS::GetReservedSlot(interfaceObject, slot).toObject();
+ if (JS_GetFunctionId(JS_GetObjectFunction(constructor)) == aId.toString()) {
+ return constructor;
+ }
+ }
+
+ // None of the named constructors match, so the caller must want the
+ // interface object itself.
+ return interfaceObject;
+}
+
+/* static */
+bool WebIDLGlobalNameHash::DefineIfEnabled(
+ JSContext* aCx, JS::Handle<JSObject*> aObj, JS::Handle<jsid> aId,
+ JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> aDesc,
+ bool* aFound) {
+ MOZ_ASSERT(aId.isString(), "Check for string id before calling this!");
+
+ const WebIDLNameTableEntry* entry = GetEntry(aId.toLinearString());
+ if (!entry) {
+ *aFound = false;
+ return true;
+ }
+
+ *aFound = true;
+
+ ConstructorEnabled checkEnabledForScope = entry->mEnabled;
+ // We do the enabled check on the current Realm of aCx, but for the
+ // actual object we pass in the underlying object in the Xray case. That
+ // way the callee can decide whether to allow access based on the caller
+ // or the window being touched.
+ //
+ // Using aCx to represent the current Realm for CheckedUnwrapDynamic
+ // purposes is OK here, because that's the Realm where we plan to do
+ // our property-defining.
+ JS::Rooted<JSObject*> global(
+ aCx,
+ js::CheckedUnwrapDynamic(aObj, aCx, /* stopAtWindowProxy = */ false));
+ if (!global) {
+ return Throw(aCx, NS_ERROR_DOM_SECURITY_ERR);
+ }
+
+ {
+ // It's safe to pass "&global" here, because we've already unwrapped it, but
+ // for general sanity better to not have debug code even having the
+ // appearance of mutating things that opt code uses.
+#ifdef DEBUG
+ JS::Rooted<JSObject*> temp(aCx, global);
+ DebugOnly<nsGlobalWindowInner*> win;
+ MOZ_ASSERT(NS_SUCCEEDED(
+ UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT(Window, &temp, win, aCx)));
+#endif
+ }
+
+ if (checkEnabledForScope && !checkEnabledForScope(aCx, global)) {
+ return true;
+ }
+
+ // The DOM constructor resolve machinery interacts with Xrays in tricky
+ // ways, and there are some asymmetries that are important to understand.
+ //
+ // In the regular (non-Xray) case, we only want to resolve constructors
+ // once (so that if they're deleted, they don't reappear). We do this by
+ // stashing the constructor in a slot on the global, such that we can see
+ // during resolve whether we've created it already. This is rather
+ // memory-intensive, so we don't try to maintain these semantics when
+ // manipulating a global over Xray (so the properties just re-resolve if
+ // they've been deleted).
+ //
+ // Unfortunately, there's a bit of an impedance-mismatch between the Xray
+ // and non-Xray machinery. The Xray machinery wants an API that returns a
+ // JS::PropertyDescriptor, so that the resolve hook doesn't have to get
+ // snared up with trying to define a property on the Xray holder. At the
+ // same time, the DefineInterface callbacks are set up to define things
+ // directly on the global. And re-jiggering them to return property
+ // descriptors is tricky, because some DefineInterface callbacks define
+ // multiple things (like the Image() alias for HTMLImageElement).
+ //
+ // So the setup is as-follows:
+ //
+ // * The resolve function takes a JS::PropertyDescriptor, but in the
+ // non-Xray case, callees may define things directly on the global, and
+ // set the value on the property descriptor to |undefined| to indicate
+ // that there's nothing more for the caller to do. We assert against
+ // this behavior in the Xray case.
+ //
+ // * We make sure that we do a non-Xray resolve first, so that all the
+ // slots are set up. In the Xray case, this means unwrapping and doing
+ // a non-Xray resolve before doing the Xray resolve.
+ //
+ // This all could use some grand refactoring, but for now we just limp
+ // along.
+ if (xpc::WrapperFactory::IsXrayWrapper(aObj)) {
+ JS::Rooted<JSObject*> constructor(aCx);
+ {
+ JSAutoRealm ar(aCx, global);
+ constructor = FindNamedConstructorForXray(aCx, aId, entry);
+ }
+ if (NS_WARN_IF(!constructor)) {
+ return Throw(aCx, NS_ERROR_FAILURE);
+ }
+ if (!JS_WrapObject(aCx, &constructor)) {
+ return Throw(aCx, NS_ERROR_FAILURE);
+ }
+
+ aDesc.set(mozilla::Some(JS::PropertyDescriptor::Data(
+ JS::ObjectValue(*constructor), {JS::PropertyAttribute::Configurable,
+ JS::PropertyAttribute::Writable})));
+ return true;
+ }
+
+ JS::Rooted<JSObject*> interfaceObject(
+ aCx,
+ GetPerInterfaceObjectHandle(aCx, entry->mConstructorId, entry->mCreate,
+ /* aDefineOnGlobal = */ true));
+ if (NS_WARN_IF(!interfaceObject)) {
+ return Throw(aCx, NS_ERROR_FAILURE);
+ }
+
+ // We've already defined the property. We indicate this to the caller
+ // by filling a property descriptor with JS::UndefinedValue() as the
+ // value. We still have to fill in a property descriptor, though, so
+ // that the caller knows the property is in fact on this object.
+ aDesc.set(
+ mozilla::Some(JS::PropertyDescriptor::Data(JS::UndefinedValue(), {})));
+ return true;
+}
+
+/* static */
+bool WebIDLGlobalNameHash::MayResolve(jsid aId) {
+ return GetEntry(aId.toLinearString()) != nullptr;
+}
+
+/* static */
+bool WebIDLGlobalNameHash::GetNames(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ NameType aNameType,
+ JS::MutableHandleVector<jsid> aNames) {
+ // aObj is always a Window here, so GetProtoAndIfaceCache on it is safe.
+ ProtoAndIfaceCache* cache = GetProtoAndIfaceCache(aObj);
+ for (size_t i = 0; i < sCount; ++i) {
+ const WebIDLNameTableEntry& entry = sEntries[i];
+ // If aNameType is not AllNames, only include things whose entry slot in the
+ // ProtoAndIfaceCache is null.
+ if ((aNameType == AllNames ||
+ !cache->HasEntryInSlot(entry.mConstructorId)) &&
+ (!entry.mEnabled || entry.mEnabled(aCx, aObj))) {
+ JSString* str = JS_AtomizeStringN(aCx, BindingName(entry.mNameOffset),
+ entry.mNameLength);
+ if (!str || !aNames.append(JS::PropertyKey::NonIntAtom(str))) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/* static */
+bool WebIDLGlobalNameHash::ResolveForSystemGlobal(JSContext* aCx,
+ JS::Handle<JSObject*> aObj,
+ JS::Handle<jsid> aId,
+ bool* aResolvedp) {
+ MOZ_ASSERT(JS_IsGlobalObject(aObj));
+
+ // First we try to resolve standard classes.
+ if (!JS_ResolveStandardClass(aCx, aObj, aId, aResolvedp)) {
+ return false;
+ }
+ if (*aResolvedp) {
+ return true;
+ }
+
+ // We don't resolve any non-string entries.
+ if (!aId.isString()) {
+ return true;
+ }
+
+ // XXX(nika): In the Window case, we unwrap our global object here to handle
+ // XRays. I don't think we ever create xrays to system globals, so I believe
+ // we can skip this step.
+ MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(aObj), "Xrays not supported!");
+
+ // Look up the corresponding entry in the name table, and resolve if enabled.
+ const WebIDLNameTableEntry* entry = GetEntry(aId.toLinearString());
+ if (entry && (!entry->mEnabled || entry->mEnabled(aCx, aObj))) {
+ if (NS_WARN_IF(!GetPerInterfaceObjectHandle(
+ aCx, entry->mConstructorId, entry->mCreate,
+ /* aDefineOnGlobal = */ true))) {
+ return Throw(aCx, NS_ERROR_FAILURE);
+ }
+
+ *aResolvedp = true;
+ }
+ return true;
+}
+
+/* static */
+bool WebIDLGlobalNameHash::NewEnumerateSystemGlobal(
+ JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::MutableHandleVector<jsid> aProperties, bool aEnumerableOnly) {
+ MOZ_ASSERT(JS_IsGlobalObject(aObj));
+
+ if (!JS_NewEnumerateStandardClasses(aCx, aObj, aProperties,
+ aEnumerableOnly)) {
+ return false;
+ }
+
+ // All properties defined on our global are non-enumerable, so we can skip
+ // remaining properties.
+ if (aEnumerableOnly) {
+ return true;
+ }
+
+ // Enumerate all entries & add enabled ones.
+ for (size_t i = 0; i < sCount; ++i) {
+ const WebIDLNameTableEntry& entry = sEntries[i];
+ if (!entry.mEnabled || entry.mEnabled(aCx, aObj)) {
+ JSString* str = JS_AtomizeStringN(aCx, BindingName(entry.mNameOffset),
+ entry.mNameLength);
+ if (!str || !aProperties.append(JS::PropertyKey::NonIntAtom(str))) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/WebIDLGlobalNameHash.h b/dom/bindings/WebIDLGlobalNameHash.h
new file mode 100644
index 0000000000..bbe047d4d3
--- /dev/null
+++ b/dom/bindings/WebIDLGlobalNameHash.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_WebIDLGlobalNameHash_h__
+#define mozilla_dom_WebIDLGlobalNameHash_h__
+
+#include "js/RootingAPI.h"
+#include "nsTArray.h"
+#include "mozilla/dom/BindingDeclarations.h"
+
+class JSLinearString;
+
+namespace mozilla::dom {
+
+enum class BindingNamesOffset : uint16_t;
+
+namespace constructors::id {
+enum ID : uint16_t;
+} // namespace constructors::id
+
+struct WebIDLNameTableEntry {
+ // Check whether a constructor should be enabled for the given object.
+ // Note that the object should NOT be an Xray, since Xrays will end up
+ // defining constructors on the underlying object.
+ using ConstructorEnabled = bool (*)(JSContext* cx, JS::Handle<JSObject*> obj);
+
+ BindingNamesOffset mNameOffset;
+ uint16_t mNameLength;
+ constructors::id::ID mConstructorId;
+ CreateInterfaceObjectsMethod mCreate;
+ // May be null if enabled unconditionally
+ ConstructorEnabled mEnabled;
+};
+
+class WebIDLGlobalNameHash {
+ public:
+ using ConstructorEnabled = WebIDLNameTableEntry::ConstructorEnabled;
+
+ // Returns false if something failed. aFound is set to true if the name is in
+ // the hash, whether it's enabled or not.
+ static bool DefineIfEnabled(
+ JSContext* aCx, JS::Handle<JSObject*> aObj, JS::Handle<jsid> aId,
+ JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> aDesc,
+ bool* aFound);
+
+ static bool MayResolve(jsid aId);
+
+ // The type of names we're asking for.
+ enum NameType {
+ // All WebIDL names enabled for aObj.
+ AllNames,
+ // Only the names that are enabled for aObj and have not been resolved for
+ // aObj in the past (and therefore can't have been deleted).
+ UnresolvedNamesOnly
+ };
+ // Returns false if an exception has been thrown on aCx.
+ static bool GetNames(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ NameType aNameType,
+ JS::MutableHandleVector<jsid> aNames);
+
+ // Helpers for resolving & enumerating names on the system global.
+ // NOTE: These are distinct as it currently lacks a ProtoAndIfaceCache, and is
+ // an XPCOM global.
+ static bool ResolveForSystemGlobal(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::Handle<jsid> aId, bool* aResolvedp);
+
+ static bool NewEnumerateSystemGlobal(
+ JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::MutableHandleVector<jsid> aProperties, bool aEnumerableOnly);
+
+ private:
+ friend struct WebIDLNameTableEntry;
+
+ // Look up an entry by key name. `nullptr` if the entry was not found.
+ // The impl of GetEntry is generated by Codegen.py in RegisterBindings.cpp
+ static const WebIDLNameTableEntry* GetEntry(JSLinearString* aKey);
+
+ // The total number of names in the hash.
+ // The value of sCount is generated by Codegen.py in RegisterBindings.cpp.
+ static const uint32_t sCount;
+
+ // The name table entries in the hash.
+ // The value of sEntries is generated by Codegen.py in RegisterBindings.cpp.
+ static const WebIDLNameTableEntry sEntries[];
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_WebIDLGlobalNameHash_h__
diff --git a/dom/bindings/XrayExpandoClass.h b/dom/bindings/XrayExpandoClass.h
new file mode 100644
index 0000000000..a18a7125ca
--- /dev/null
+++ b/dom/bindings/XrayExpandoClass.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file declares a macro for defining Xray expando classes and declares the
+ * default Xray expando class. The actual definition of that default class
+ * lives elsewhere.
+ */
+#ifndef mozilla_dom_XrayExpandoClass_h
+#define mozilla_dom_XrayExpandoClass_h
+
+/*
+ * maybeStatic_ Should be either `static` or nothing (because some Xray expando
+ * classes are not static).
+ *
+ * name_ should be the name of the variable.
+ *
+ * extraSlots_ should be how many extra slots to give the class, in addition to
+ * the ones Xray expandos want.
+ */
+#define DEFINE_XRAY_EXPANDO_CLASS(maybeStatic_, name_, extraSlots_) \
+ maybeStatic_ const JSClass name_ = { \
+ "XrayExpandoObject", \
+ JSCLASS_HAS_RESERVED_SLOTS(xpc::JSSLOT_EXPANDO_COUNT + (extraSlots_)) | \
+ JSCLASS_FOREGROUND_FINALIZE, \
+ &xpc::XrayExpandoObjectClassOps}
+
+namespace mozilla::dom {
+
+extern const JSClass DefaultXrayExpandoObjectClass;
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_XrayExpandoClass_h */
diff --git a/dom/bindings/crashtests/1010658-1.html b/dom/bindings/crashtests/1010658-1.html
new file mode 100644
index 0000000000..6b341f4ed9
--- /dev/null
+++ b/dom/bindings/crashtests/1010658-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+function boom()
+{
+ window.__proto__ = null;
+ for (var i = 0; i < 10000; ++i) {
+ self.document;
+ }
+}
+
+</script></head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/dom/bindings/crashtests/1010658-2.html b/dom/bindings/crashtests/1010658-2.html
new file mode 100644
index 0000000000..cf473c3dd9
--- /dev/null
+++ b/dom/bindings/crashtests/1010658-2.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+function boom()
+{
+ window.__proto__ = function(){};
+ for (var i = 0; i < 10000; ++i) {
+ self.document;
+ }
+}
+
+</script></head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/dom/bindings/crashtests/769464.html b/dom/bindings/crashtests/769464.html
new file mode 100644
index 0000000000..d075ee66a0
--- /dev/null
+++ b/dom/bindings/crashtests/769464.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<script>
+
+function boom()
+{
+ window.getComputedStyle(new Worker("404.js"));
+}
+
+window.addEventListener("load", boom);
+
+</script>
diff --git a/dom/bindings/crashtests/822340-1.html b/dom/bindings/crashtests/822340-1.html
new file mode 100644
index 0000000000..4c8f6ae460
--- /dev/null
+++ b/dom/bindings/crashtests/822340-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<script>
+ var xhr = new XMLHttpRequest;
+ function f() {
+ var x = xhr.getResponseHeader;
+ x("abc");
+ }
+ for (var i = 0; i < 20000; ++i) {
+ try { f(); } catch (e) {}
+ }
+</script>
diff --git a/dom/bindings/crashtests/822340-2.html b/dom/bindings/crashtests/822340-2.html
new file mode 100644
index 0000000000..e938c91aac
--- /dev/null
+++ b/dom/bindings/crashtests/822340-2.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<script>
+ var l = document.getElementsByTagName("*");
+ var count = 20000;
+ for (var i = 0; i < count; ++i) {
+ l.item(0);
+ }
+</script>
diff --git a/dom/bindings/crashtests/832899.html b/dom/bindings/crashtests/832899.html
new file mode 100644
index 0000000000..c565ad00f4
--- /dev/null
+++ b/dom/bindings/crashtests/832899.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<script>
+ var ev = document.createEvent("Events");
+ EventTarget.prototype.dispatchEvent.call(navigator.connection, ev);
+</script>
diff --git a/dom/bindings/crashtests/860551.html b/dom/bindings/crashtests/860551.html
new file mode 100644
index 0000000000..5008e57396
--- /dev/null
+++ b/dom/bindings/crashtests/860551.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+SVGZoomAndPan instanceof SVGZoomAndPan
+</script>
diff --git a/dom/bindings/crashtests/860591.html b/dom/bindings/crashtests/860591.html
new file mode 100644
index 0000000000..565a729c4d
--- /dev/null
+++ b/dom/bindings/crashtests/860591.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+ var timeEvent = document.createEvent('TimeEvent');
+ var mutationEvent = document.createEvent('MutationEvents');
+ mutationEvent.__proto__ = timeEvent;
+ mutationEvent.target;
+
+ var mouseScrollEvent = document.createEvent("MouseScrollEvents");
+ var mouseEvent = document.createEvent("MouseEvents");
+ mouseEvent.__proto__ = mouseScrollEvent;
+ mouseEvent.relatedTarget;
+
+ var uiEvent = document.createEvent("UIEvents");
+ uiEvent.__proto__ = mouseScrollEvent;
+ uiEvent.rangeParent;
+</script>
+</head>
+</html>
diff --git a/dom/bindings/crashtests/862092.html b/dom/bindings/crashtests/862092.html
new file mode 100644
index 0000000000..1b31775a97
--- /dev/null
+++ b/dom/bindings/crashtests/862092.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ var frameDoc = document.getElementById("f").contentDocument;
+ frameDoc.adoptNode(document.createElement("select"));
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+<iframe id="f"></iframe>
+</body>
+</html>
diff --git a/dom/bindings/crashtests/862610.html b/dom/bindings/crashtests/862610.html
new file mode 100644
index 0000000000..768871ad96
--- /dev/null
+++ b/dom/bindings/crashtests/862610.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+ HTMLElement.prototype.__proto__ = new Proxy({}, {});
+ try {
+ window.Image;
+ } finally {
+ // Restore our prototype so the test harnesses can deal with us
+ // We can't just assign to __proto__ because it lives on our proto chain
+ // and we messed that up.
+ var desc = Object.getOwnPropertyDescriptor(Object.prototype, "__proto__");
+ desc.set.call(HTMLElement.prototype, Element.prototype);
+ }
+</script>
+</head>
+
+<body></body>
+</html>
diff --git a/dom/bindings/crashtests/869038.html b/dom/bindings/crashtests/869038.html
new file mode 100644
index 0000000000..dedb4dd4d7
--- /dev/null
+++ b/dom/bindings/crashtests/869038.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ var frame = document.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
+ document.body.appendChild(frame);
+ var frameDoc = frame.contentDocument;
+ frameDoc.contentEditable = "true";
+ document.body.removeChild(frame);
+ SpecialPowers.gc();
+ frameDoc.focus();
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/dom/bindings/crashtests/949940.html b/dom/bindings/crashtests/949940.html
new file mode 100644
index 0000000000..7f20085fea
--- /dev/null
+++ b/dom/bindings/crashtests/949940.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+function boom()
+{
+ var frameWin = document.getElementById("f").contentWindow;
+ Object.create(frameWin).self;
+}
+</script>
+</head>
+<body onload="boom()">
+<iframe id="f" src="data:text/html,3"></iframe>
+</body>
+</html>
diff --git a/dom/bindings/crashtests/crashtests.list b/dom/bindings/crashtests/crashtests.list
new file mode 100644
index 0000000000..50126788cd
--- /dev/null
+++ b/dom/bindings/crashtests/crashtests.list
@@ -0,0 +1,13 @@
+skip load 769464.html # bug 823822 - assert often leaks into other tests
+load 822340-1.html
+load 822340-2.html
+load 832899.html
+load 860551.html
+load 860591.html
+load 862092.html
+load 862610.html
+load 869038.html
+load 949940.html
+load 1010658-1.html
+load 1010658-2.html
+load stringbuffer-USVString.html
diff --git a/dom/bindings/crashtests/stringbuffer-USVString.html b/dom/bindings/crashtests/stringbuffer-USVString.html
new file mode 100644
index 0000000000..a193e732b9
--- /dev/null
+++ b/dom/bindings/crashtests/stringbuffer-USVString.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<script>
+ var div = document.createElement("div");
+ // Need a long-enough string that when we get it from the DOM it will not get
+ // inlined and will be an external stringbuffer string.
+ var str = "http://" + (new Array(200).join("a"));
+ div.setAttribute("x", str);
+ str = div.getAttribute("x");
+ // Now pass it as a USVString
+ new URL(str);
+</script>
diff --git a/dom/bindings/docs/index.rst b/dom/bindings/docs/index.rst
new file mode 100644
index 0000000000..d47d4c9c68
--- /dev/null
+++ b/dom/bindings/docs/index.rst
@@ -0,0 +1,124 @@
+.. _webidl:
+
+======
+WebIDL
+======
+
+WebIDL describes interfaces web browsers are supposed to implement.
+
+The interaction between WebIDL and the build system is somewhat complex.
+This document will attempt to explain how it all works.
+
+Overview
+========
+
+``.webidl`` files throughout the tree define interfaces the browser
+implements. Since Gecko/Firefox is implemented in C++, there is a
+mechanism to convert these interfaces and associated metadata to
+C++ code. That's where the build system comes into play.
+
+All the code for interacting with ``.webidl`` files lives under
+``dom/bindings``. There is code in the build system to deal with
+WebIDLs explicitly.
+
+WebIDL source file flavors
+==========================
+
+Not all ``.webidl`` files are created equal! There are several flavors,
+each represented by a separate symbol from :ref:`mozbuild_symbols`.
+
+WEBIDL_FILES
+ Refers to regular/static ``.webidl`` files. Most WebIDL interfaces
+ are defined this way.
+
+GENERATED_EVENTS_WEBIDL_FILES
+ In addition to generating a binding, these ``.webidl`` files also
+ generate a source file implementing the event object in C++
+
+PREPROCESSED_WEBIDL_FILES
+ The ``.webidl`` files are generated by preprocessing an input file.
+ They otherwise behave like *WEBIDL_FILES*.
+
+TEST_WEBIDL_FILES
+ Like *WEBIDL_FILES* but the interfaces are for testing only and
+ aren't shipped with the browser.
+
+PREPROCESSED_TEST_WEBIDL_FILES
+ Like *TEST_WEBIDL_FILES* except the ``.webidl`` is obtained via
+ preprocessing, much like *PREPROCESSED_WEBIDL_FILES*.
+
+GENERATED_WEBIDL_FILES
+ The ``.webidl`` for these is obtained through an *external*
+ mechanism. Typically there are custom build rules for producing these
+ files.
+
+Producing C++ code
+==================
+
+The most complicated part about WebIDLs is the process by which
+``.webidl`` files are converted into C++.
+
+This process is handled by code in the :py:mod:`mozwebidlcodegen`
+package. :py:class:`mozwebidlcodegen.WebIDLCodegenManager` is
+specifically where you want to look for how code generation is
+performed. This includes complex dependency management.
+
+Requirements
+============
+
+This section aims to document the build and developer workflow requirements
+for WebIDL.
+
+Parser unit tests
+ There are parser tests provided by ``dom/bindings/parser/runtests.py``
+ that should run as part of ``make check``. There must be a mechanism
+ to run the tests in *human* mode so they output friendly error
+ messages.
+
+ The current mechanism for this is ``mach webidl-parser-test``.
+
+Mochitests
+ There are various mochitests under ``dom/bindings/test``. They should
+ be runnable through the standard mechanisms.
+
+Working with test interfaces
+ ``TestExampleGenBinding.cpp`` calls into methods from the
+ ``TestExampleInterface``, ``TestExampleProxyInterface``,
+ ``TestExampleThrowingConstructorInterface``,
+ and ``TestExampleWorkerInterface`` interfaces.
+ These interfaces need to be generated as part of the build. These
+ interfaces should not be exported or packaged.
+
+ There is a ``compiletests`` make target in ``dom/bindings`` that
+ isn't part of the build that facilitates turnkey code generation
+ and test file compilation.
+
+Minimal rebuilds
+ Reprocessing every output for every change is expensive. So we don't
+ inconvenience people changing ``.webidl`` files, the build system
+ should only perform a minimal rebuild when sources change.
+
+ This logic is mostly all handled in
+ :py:class:`mozwebidlcodegen.WebIDLCodegenManager`. The unit tests for
+ that Python code should adequately test typical rebuild scenarios.
+
+ Bug 940469 tracks making the existing implementation better.
+
+Explicit method for performing codegen
+ There needs to be an explicit method for invoking code generation.
+ It needs to cover regular and test files.
+
+ This is implemented via ``make export`` in ``dom/bindings``.
+
+No-op binding generation should be fast
+ So developers touching ``.webidl`` files are not inconvenienced,
+ no-op binding generation should be fast. Watch out for the build system
+ processing large dependency files it doesn't need in order to perform
+ code generation.
+
+Ability to generate example files
+ *Any* interface can have example ``.h``/``.cpp`` files generated.
+ There must be a mechanism to facilitate this.
+
+ This is currently facilitated through ``mach webidl-example``. e.g.
+ ``mach webidl-example HTMLStyleElement``.
diff --git a/dom/bindings/mach_commands.py b/dom/bindings/mach_commands.py
new file mode 100644
index 0000000000..6f6b29bcbf
--- /dev/null
+++ b/dom/bindings/mach_commands.py
@@ -0,0 +1,62 @@
+# 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/.
+
+import os
+import sys
+
+from mach.decorators import Command, CommandArgument
+from mozbuild.util import mkdir
+
+
+def get_test_parser():
+ import runtests
+
+ return runtests.get_parser
+
+
+@Command(
+ "webidl-example",
+ category="misc",
+ description="Generate example files for a WebIDL interface.",
+)
+@CommandArgument(
+ "interface", nargs="+", help="Interface(s) whose examples to generate."
+)
+def webidl_example(command_context, interface):
+ from mozwebidlcodegen import create_build_system_manager
+
+ manager = create_build_system_manager()
+ for i in interface:
+ manager.generate_example_files(i)
+
+
+@Command(
+ "webidl-parser-test",
+ category="testing",
+ parser=get_test_parser,
+ description="Run WebIDL tests (Interface Browser parser).",
+)
+def webidl_test(command_context, **kwargs):
+ sys.path.insert(0, os.path.join(command_context.topsrcdir, "other-licenses", "ply"))
+
+ # Ensure the topobjdir exists. On a Taskcluster test run there won't be
+ # an objdir yet.
+ mkdir(command_context.topobjdir)
+
+ # Make sure we drop our cached grammar bits in the objdir, not
+ # wherever we happen to be running from.
+ os.chdir(command_context.topobjdir)
+
+ if kwargs["verbose"] is None:
+ kwargs["verbose"] = False
+
+ # Now we're going to create the cached grammar file in the
+ # objdir. But we're going to try loading it as a python
+ # module, so we need to make sure the objdir is in our search
+ # path.
+ sys.path.insert(0, command_context.topobjdir)
+
+ import runtests
+
+ return runtests.run_tests(kwargs["tests"], verbose=kwargs["verbose"])
diff --git a/dom/bindings/moz.build b/dom/bindings/moz.build
new file mode 100644
index 0000000000..c9746d2330
--- /dev/null
+++ b/dom/bindings/moz.build
@@ -0,0 +1,204 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Bindings (WebIDL)")
+
+TEST_DIRS += ["test"]
+
+XPIDL_SOURCES += ["nsIScriptError.idl"]
+
+XPIDL_MODULE = "dom_bindings"
+
+EXPORTS.ipc += [
+ "ErrorIPCUtils.h",
+]
+
+EXPORTS.mozilla += [
+ "ErrorResult.h",
+ "RootedOwningNonNull.h",
+ "RootedRefPtr.h",
+]
+
+EXPORTS.mozilla.dom += [
+ "AtomList.h",
+ "BindingCallContext.h",
+ "BindingDeclarations.h",
+ "BindingIPCUtils.h",
+ "BindingUtils.h",
+ "CallbackFunction.h",
+ "CallbackInterface.h",
+ "CallbackObject.h",
+ "DOMExceptionNames.h",
+ "DOMJSClass.h",
+ "DOMJSProxyHandler.h",
+ "DOMString.h",
+ "Errors.msg",
+ "Exceptions.h",
+ "FakeString.h",
+ "IterableIterator.h",
+ "JSSlots.h",
+ "NonRefcountedDOMObject.h",
+ "Nullable.h",
+ "ObservableArrayProxyHandler.h",
+ "PinnedStringId.h",
+ "PrimitiveConversions.h",
+ "ProxyHandlerUtils.h",
+ "Record.h",
+ "RemoteObjectProxy.h",
+ "RootedDictionary.h",
+ "RootedRecord.h",
+ "RootedSequence.h",
+ "SimpleGlobalObject.h",
+ "SpiderMonkeyInterface.h",
+ "ToJSValue.h",
+ "TypedArray.h",
+ "UnionMember.h",
+ "WebIDLGlobalNameHash.h",
+ "XrayExpandoClass.h",
+]
+
+
+# Generated bindings reference *Binding.h, not mozilla/dom/*Binding.h. And,
+# since we generate exported bindings directly to $(DIST)/include, we need
+# to add that path to the search list.
+#
+# Ideally, binding generation uses the prefixed header file names.
+# Bug 932082 tracks.
+LOCAL_INCLUDES += [
+ "!/dist/include/mozilla/dom",
+]
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+ "/dom/battery",
+ "/dom/canvas",
+ "/dom/geolocation",
+ "/dom/html",
+ "/dom/indexedDB",
+ "/dom/media/webaudio",
+ "/dom/media/webrtc",
+ "/dom/media/webrtc/common/time_profiling",
+ "/dom/media/webrtc/jsapi",
+ "/dom/media/webrtc/libwebrtcglue",
+ "/dom/media/webrtc/transport",
+ "/dom/media/webspeech/recognition",
+ "/dom/svg",
+ "/dom/xml",
+ "/dom/xslt/base",
+ "/dom/xslt/xpath",
+ "/dom/xul",
+ "/js/xpconnect/src",
+ "/js/xpconnect/wrappers",
+ "/layout/generic",
+ "/layout/style",
+ "/layout/xul/tree",
+ "/media/webrtc/",
+ "/netwerk/base/",
+ "/third_party/libwebrtc",
+ "/third_party/libwebrtc/third_party/abseil-cpp",
+]
+
+LOCAL_INCLUDES += ["/third_party/msgpack/include"]
+
+DEFINES["GOOGLE_PROTOBUF_NO_RTTI"] = True
+DEFINES["GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER"] = True
+
+UNIFIED_SOURCES += [
+ "BindingUtils.cpp",
+ "CallbackInterface.cpp",
+ "CallbackObject.cpp",
+ "DOMJSProxyHandler.cpp",
+ "Exceptions.cpp",
+ "IterableIterator.cpp",
+ "nsScriptError.cpp",
+ "nsScriptErrorWithStack.cpp",
+ "ObservableArrayProxyHandler.cpp",
+ "RemoteObjectProxy.cpp",
+ "SimpleGlobalObject.cpp",
+ "ToJSValue.cpp",
+ "WebIDLGlobalNameHash.cpp",
+]
+
+# Some tests, including those for 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"] and CONFIG["ENABLE_TESTS"]:
+ EXPORTS.mozilla.dom += [
+ "test/TestFunctions.h",
+ "test/TestInterfaceAsyncIterableDouble.h",
+ "test/TestInterfaceAsyncIterableDoubleUnion.h",
+ "test/TestInterfaceAsyncIterableSingle.h",
+ "test/TestInterfaceAsyncIterableSingleWithArgs.h",
+ "test/TestInterfaceIterableDouble.h",
+ "test/TestInterfaceIterableDoubleUnion.h",
+ "test/TestInterfaceIterableSingle.h",
+ "test/TestInterfaceLength.h",
+ "test/TestInterfaceMaplike.h",
+ "test/TestInterfaceMaplikeJSObject.h",
+ "test/TestInterfaceMaplikeObject.h",
+ "test/TestInterfaceObservableArray.h",
+ "test/TestInterfaceSetlike.h",
+ "test/TestInterfaceSetlikeNode.h",
+ "test/TestTrialInterface.h",
+ "test/WrapperCachedNonISupportsTestInterface.h",
+ ]
+ UNIFIED_SOURCES += [
+ "test/TestFunctions.cpp",
+ "test/TestInterfaceAsyncIterableDouble.cpp",
+ "test/TestInterfaceAsyncIterableDoubleUnion.cpp",
+ "test/TestInterfaceAsyncIterableSingle.cpp",
+ "test/TestInterfaceAsyncIterableSingleWithArgs.cpp",
+ "test/TestInterfaceIterableDouble.cpp",
+ "test/TestInterfaceIterableDoubleUnion.cpp",
+ "test/TestInterfaceIterableSingle.cpp",
+ "test/TestInterfaceLength.cpp",
+ "test/TestInterfaceMaplike.cpp",
+ "test/TestInterfaceMaplikeJSObject.cpp",
+ "test/TestInterfaceMaplikeObject.cpp",
+ "test/TestInterfaceObservableArray.cpp",
+ "test/TestInterfaceSetlike.cpp",
+ "test/TestInterfaceSetlikeNode.cpp",
+ "test/TestTrialInterface.cpp",
+ "test/WrapperCachedNonISupportsTestInterface.cpp",
+ ]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+SPHINX_TREES["webidl"] = "docs"
+
+with Files("docs/**"):
+ SCHEDULES.exclusive = ["docs"]
+
+SPHINX_PYTHON_PACKAGE_DIRS += ["mozwebidlcodegen"]
+
+with Files("mozwebidlcodegen/**.py"):
+ SCHEDULES.inclusive += ["docs"]
+
+
+PYTHON_UNITTEST_MANIFESTS += [
+ "mozwebidlcodegen/test/python.ini",
+]
+
+if CONFIG["CC_TYPE"] == "gcc":
+ CXXFLAGS += [
+ "-Wno-maybe-uninitialized",
+ ]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ GeneratedFile(
+ "CSS2Properties.webidl",
+ script="GenerateCSS2PropertiesWebIDL.py",
+ entry_point="generate",
+ inputs=[
+ "/dom/webidl/CSS2Properties.webidl.in",
+ "!/layout/style/ServoCSSPropList.py",
+ ],
+ )
diff --git a/dom/bindings/mozwebidlcodegen/__init__.py b/dom/bindings/mozwebidlcodegen/__init__.py
new file mode 100644
index 0000000000..8b6e62f345
--- /dev/null
+++ b/dom/bindings/mozwebidlcodegen/__init__.py
@@ -0,0 +1,681 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This module contains code for managing WebIDL files and bindings for
+# the build system.
+
+import errno
+import hashlib
+import io
+import json
+import logging
+import os
+from copy import deepcopy
+
+import mozpack.path as mozpath
+import six
+from mach.mixin.logging import LoggingMixin
+from mozbuild.makeutil import Makefile
+from mozbuild.pythonutil import iter_modules_in_path
+from mozbuild.util import FileAvoidWrite
+
+# There are various imports in this file in functions to avoid adding
+# dependencies to config.status. See bug 949875.
+
+
+class BuildResult(object):
+ """Represents the result of processing WebIDL files.
+
+ This holds a summary of output file generation during code generation.
+ """
+
+ def __init__(self):
+ # The .webidl files that had their outputs regenerated.
+ self.inputs = set()
+
+ # The output files that were created.
+ self.created = set()
+
+ # The output files that changed.
+ self.updated = set()
+
+ # The output files that didn't change.
+ self.unchanged = set()
+
+
+class WebIDLCodegenManagerState(dict):
+ """Holds state for the WebIDL code generation manager.
+
+ State is currently just an extended dict. The internal implementation of
+ state should be considered a black box to everyone except
+ WebIDLCodegenManager. But we'll still document it.
+
+ Fields:
+
+ version
+ The integer version of the format. This is to detect incompatible
+ changes between state. It should be bumped whenever the format
+ changes or semantics change.
+
+ webidls
+ A dictionary holding information about every known WebIDL input.
+ Keys are the basenames of input WebIDL files. Values are dicts of
+ metadata. Keys in those dicts are:
+
+ * filename - The full path to the input filename.
+ * inputs - A set of full paths to other webidl files this webidl
+ depends on.
+ * outputs - Set of full output paths that are created/derived from
+ this file.
+ * sha1 - The hexidecimal SHA-1 of the input filename from the last
+ processing time.
+
+ global_inputs
+ A dictionary defining files that influence all processing. Keys
+ are full filenames. Values are hexidecimal SHA-1 from the last
+ processing time.
+
+ dictionaries_convertible_to_js
+ A set of names of dictionaries that are convertible to JS.
+
+ dictionaries_convertible_from_js
+ A set of names of dictionaries that are convertible from JS.
+ """
+
+ VERSION = 3
+
+ def __init__(self, fh=None):
+ self["version"] = self.VERSION
+ self["webidls"] = {}
+ self["global_depends"] = {}
+
+ if not fh:
+ return
+
+ state = json.load(fh)
+ if state["version"] != self.VERSION:
+ raise Exception("Unknown state version: %s" % state["version"])
+
+ self["version"] = state["version"]
+ self["global_depends"] = state["global_depends"]
+
+ for k, v in state["webidls"].items():
+ self["webidls"][k] = v
+
+ # Sets are converted to lists for serialization because JSON
+ # doesn't support sets.
+ self["webidls"][k]["inputs"] = set(v["inputs"])
+ self["webidls"][k]["outputs"] = set(v["outputs"])
+
+ self["dictionaries_convertible_to_js"] = set(
+ state["dictionaries_convertible_to_js"]
+ )
+
+ self["dictionaries_convertible_from_js"] = set(
+ state["dictionaries_convertible_from_js"]
+ )
+
+ def dump(self, fh):
+ """Dump serialized state to a file handle."""
+ normalized = deepcopy(self)
+
+ for k, v in self["webidls"].items():
+ # Convert sets to lists because JSON doesn't support sets.
+ normalized["webidls"][k]["outputs"] = sorted(v["outputs"])
+ normalized["webidls"][k]["inputs"] = sorted(v["inputs"])
+
+ normalized["dictionaries_convertible_to_js"] = sorted(
+ self["dictionaries_convertible_to_js"]
+ )
+
+ normalized["dictionaries_convertible_from_js"] = sorted(
+ self["dictionaries_convertible_from_js"]
+ )
+
+ json.dump(normalized, fh, sort_keys=True)
+
+
+class WebIDLCodegenManager(LoggingMixin):
+ """Manages all code generation around WebIDL.
+
+ To facilitate testing, this object is meant to be generic and reusable.
+ Paths, etc should be parameters and not hardcoded.
+ """
+
+ # Global parser derived declaration files.
+ GLOBAL_DECLARE_FILES = {
+ "BindingNames.h",
+ "GeneratedAtomList.h",
+ "GeneratedEventList.h",
+ "PrototypeList.h",
+ "RegisterBindings.h",
+ "RegisterShadowRealmBindings.h",
+ "RegisterWorkerBindings.h",
+ "RegisterWorkerDebuggerBindings.h",
+ "RegisterWorkletBindings.h",
+ "UnionTypes.h",
+ "WebIDLPrefs.h",
+ "WebIDLSerializable.h",
+ }
+
+ # Global parser derived definition files.
+ GLOBAL_DEFINE_FILES = {
+ "BindingNames.cpp",
+ "RegisterBindings.cpp",
+ "RegisterShadowRealmBindings.cpp",
+ "RegisterWorkerBindings.cpp",
+ "RegisterWorkerDebuggerBindings.cpp",
+ "RegisterWorkletBindings.cpp",
+ "UnionTypes.cpp",
+ "PrototypeList.cpp",
+ "WebIDLPrefs.cpp",
+ "WebIDLSerializable.cpp",
+ }
+
+ def __init__(
+ self,
+ config_path,
+ webidl_root,
+ inputs,
+ exported_header_dir,
+ codegen_dir,
+ state_path,
+ cache_dir=None,
+ make_deps_path=None,
+ make_deps_target=None,
+ ):
+ """Create an instance that manages WebIDLs in the build system.
+
+ config_path refers to a WebIDL config file (e.g. Bindings.conf).
+ inputs is a 4-tuple describing the input .webidl files and how to
+ process them. Members are:
+ (set(.webidl files), set(basenames of exported files),
+ set(basenames of generated events files),
+ set(example interface names))
+
+ exported_header_dir and codegen_dir are directories where generated
+ files will be written to.
+ state_path is the path to a file that will receive JSON state from our
+ actions.
+ make_deps_path is the path to a make dependency file that we can
+ optionally write.
+ make_deps_target is the target that receives the make dependencies. It
+ must be defined if using make_deps_path.
+ """
+ self.populate_logger()
+
+ input_paths, exported_stems, generated_events_stems, example_interfaces = inputs
+
+ self._config_path = config_path
+ self._webidl_root = webidl_root
+ self._input_paths = set(input_paths)
+ self._exported_stems = set(exported_stems)
+ self._generated_events_stems = set(generated_events_stems)
+ self._generated_events_stems_as_array = generated_events_stems
+ self._example_interfaces = set(example_interfaces)
+ self._exported_header_dir = exported_header_dir
+ self._codegen_dir = codegen_dir
+ self._state_path = state_path
+ self._cache_dir = cache_dir
+ self._make_deps_path = make_deps_path
+ self._make_deps_target = make_deps_target
+
+ if (make_deps_path and not make_deps_target) or (
+ not make_deps_path and make_deps_target
+ ):
+ raise Exception(
+ "Must define both make_deps_path and make_deps_target "
+ "if one is defined."
+ )
+
+ self._parser_results = None
+ self._config = None
+ self._state = WebIDLCodegenManagerState()
+
+ if os.path.exists(state_path):
+ with io.open(state_path, "r") as fh:
+ try:
+ self._state = WebIDLCodegenManagerState(fh=fh)
+ except Exception as e:
+ self.log(
+ logging.WARN,
+ "webidl_bad_state",
+ {"msg": str(e)},
+ "Bad WebIDL state: {msg}",
+ )
+
+ @property
+ def config(self):
+ if not self._config:
+ self._parse_webidl()
+
+ return self._config
+
+ def generate_build_files(self):
+ """Generate files required for the build.
+
+ This function is in charge of generating all the .h/.cpp files derived
+ from input .webidl files. Please note that there are build actions
+ required to produce .webidl files and these build actions are
+ explicitly not captured here: this function assumes all .webidl files
+ are present and up to date.
+
+ This routine is called as part of the build to ensure files that need
+ to exist are present and up to date. This routine may not be called if
+ the build dependencies (generated as a result of calling this the first
+ time) say everything is up to date.
+
+ Because reprocessing outputs for every .webidl on every invocation
+ is expensive, we only regenerate the minimal set of files on every
+ invocation. The rules for deciding what needs done are roughly as
+ follows:
+
+ 1. If any .webidl changes, reparse all .webidl files and regenerate
+ the global derived files. Only regenerate output files (.h/.cpp)
+ impacted by the modified .webidl files.
+ 2. If an non-.webidl dependency (Python files, config file) changes,
+ assume everything is out of date and regenerate the world. This
+ is because changes in those could globally impact every output
+ file.
+ 3. If an output file is missing, ensure it is present by performing
+ necessary regeneration.
+ """
+ # Despite #1 above, we assume the build system is smart enough to not
+ # invoke us if nothing has changed. Therefore, any invocation means
+ # something has changed. And, if anything has changed, we need to
+ # parse the WebIDL.
+ self._parse_webidl()
+
+ result = BuildResult()
+
+ # If we parse, we always update globals - they are cheap and it is
+ # easier that way.
+ created, updated, unchanged = self._write_global_derived()
+ result.created |= created
+ result.updated |= updated
+ result.unchanged |= unchanged
+
+ # If any of the extra dependencies changed, regenerate the world.
+ global_changed, global_hashes = self._global_dependencies_changed()
+ if global_changed:
+ # Make a copy because we may modify.
+ changed_inputs = set(self._input_paths)
+ else:
+ changed_inputs = self._compute_changed_inputs()
+
+ self._state["global_depends"] = global_hashes
+ self._state["dictionaries_convertible_to_js"] = set(
+ d.identifier.name for d in self._config.getDictionariesConvertibleToJS()
+ )
+ self._state["dictionaries_convertible_from_js"] = set(
+ d.identifier.name for d in self._config.getDictionariesConvertibleFromJS()
+ )
+
+ # Generate bindings from .webidl files.
+ for filename in sorted(changed_inputs):
+ basename = mozpath.basename(filename)
+ result.inputs.add(filename)
+ written, deps = self._generate_build_files_for_webidl(filename)
+ result.created |= written[0]
+ result.updated |= written[1]
+ result.unchanged |= written[2]
+
+ self._state["webidls"][basename] = dict(
+ filename=filename,
+ outputs=written[0] | written[1] | written[2],
+ inputs=set(deps),
+ sha1=self._input_hashes[filename],
+ )
+
+ # Process some special interfaces required for testing.
+ for interface in self._example_interfaces:
+ written = self.generate_example_files(interface)
+ result.created |= written[0]
+ result.updated |= written[1]
+ result.unchanged |= written[2]
+
+ # Generate a make dependency file.
+ if self._make_deps_path:
+ mk = Makefile()
+ codegen_rule = mk.create_rule([self._make_deps_target])
+ codegen_rule.add_dependencies(
+ six.ensure_text(s) for s in global_hashes.keys()
+ )
+ codegen_rule.add_dependencies(six.ensure_text(p) for p in self._input_paths)
+
+ with FileAvoidWrite(self._make_deps_path) as fh:
+ mk.dump(fh)
+
+ self._save_state()
+
+ return result
+
+ def generate_example_files(self, interface):
+ """Generates example files for a given interface."""
+ from Codegen import CGExampleRoot
+
+ root = CGExampleRoot(self.config, interface)
+
+ example_paths = self._example_paths(interface)
+ for path in example_paths:
+ print("Generating {}".format(path))
+
+ return self._maybe_write_codegen(root, *example_paths)
+
+ def _parse_webidl(self):
+ import WebIDL
+ from Configuration import Configuration
+
+ self.log(
+ logging.INFO,
+ "webidl_parse",
+ {"count": len(self._input_paths)},
+ "Parsing {count} WebIDL files.",
+ )
+
+ hashes = {}
+ parser = WebIDL.Parser(self._cache_dir, lexer=None)
+
+ for path in sorted(self._input_paths):
+ with io.open(path, "r", encoding="utf-8") as fh:
+ data = fh.read()
+ hashes[path] = hashlib.sha1(six.ensure_binary(data)).hexdigest()
+ parser.parse(data, path)
+
+ # Only these directories may contain WebIDL files with interfaces
+ # which are exposed to the web. WebIDL files in these roots may not
+ # be changed without DOM peer review.
+ #
+ # Other directories may contain WebIDL files as long as they only
+ # contain ChromeOnly interfaces. These are not subject to mandatory
+ # DOM peer review.
+ web_roots = (
+ # The main WebIDL root.
+ self._webidl_root,
+ # The binding config root, which contains some test-only
+ # interfaces.
+ os.path.dirname(self._config_path),
+ # The objdir sub-directory which contains generated WebIDL files.
+ self._codegen_dir,
+ )
+
+ self._parser_results = parser.finish()
+ self._config = Configuration(
+ self._config_path,
+ web_roots,
+ self._parser_results,
+ self._generated_events_stems_as_array,
+ )
+ self._input_hashes = hashes
+
+ def _write_global_derived(self):
+ from Codegen import GlobalGenRoots
+
+ things = [("declare", f) for f in self.GLOBAL_DECLARE_FILES]
+ things.extend(("define", f) for f in self.GLOBAL_DEFINE_FILES)
+
+ result = (set(), set(), set())
+
+ for what, filename in things:
+ stem = mozpath.splitext(filename)[0]
+ root = getattr(GlobalGenRoots, stem)(self._config)
+
+ if what == "declare":
+ code = root.declare()
+ output_root = self._exported_header_dir
+ elif what == "define":
+ code = root.define()
+ output_root = self._codegen_dir
+ else:
+ raise Exception("Unknown global gen type: %s" % what)
+
+ output_path = mozpath.join(output_root, filename)
+ self._maybe_write_file(output_path, code, result)
+
+ return result
+
+ def _compute_changed_inputs(self):
+ """Compute the set of input files that need to be regenerated."""
+ changed_inputs = set()
+ expected_outputs = self.expected_build_output_files()
+
+ # Look for missing output files.
+ if any(not os.path.exists(f) for f in expected_outputs):
+ # FUTURE Bug 940469 Only regenerate minimum set.
+ changed_inputs |= self._input_paths
+
+ # That's it for examining output files. We /could/ examine SHA-1's of
+ # output files from a previous run to detect modifications. But that's
+ # a lot of extra work and most build systems don't do that anyway.
+
+ # Now we move on to the input files.
+ old_hashes = {v["filename"]: v["sha1"] for v in self._state["webidls"].values()}
+
+ old_filenames = set(old_hashes.keys())
+ new_filenames = self._input_paths
+
+ # If an old file has disappeared or a new file has arrived, mark
+ # it.
+ changed_inputs |= old_filenames ^ new_filenames
+
+ # For the files in common between runs, compare content. If the file
+ # has changed, mark it. We don't need to perform mtime comparisons
+ # because content is a stronger validator.
+ for filename in old_filenames & new_filenames:
+ if old_hashes[filename] != self._input_hashes[filename]:
+ changed_inputs.add(filename)
+
+ # We've now populated the base set of inputs that have changed.
+
+ # Inherit dependencies from previous run. The full set of dependencies
+ # is associated with each record, so we don't need to perform any fancy
+ # graph traversal.
+ for v in self._state["webidls"].values():
+ if any(dep for dep in v["inputs"] if dep in changed_inputs):
+ changed_inputs.add(v["filename"])
+
+ # Now check for changes to the set of dictionaries that are convertible to JS
+ oldDictionariesConvertibleToJS = self._state["dictionaries_convertible_to_js"]
+ newDictionariesConvertibleToJS = self._config.getDictionariesConvertibleToJS()
+ newNames = set(d.identifier.name for d in newDictionariesConvertibleToJS)
+ changedDictionaryNames = oldDictionariesConvertibleToJS ^ newNames
+
+ # Now check for changes to the set of dictionaries that are convertible from JS
+ oldDictionariesConvertibleFromJS = self._state[
+ "dictionaries_convertible_from_js"
+ ]
+ newDictionariesConvertibleFromJS = (
+ self._config.getDictionariesConvertibleFromJS()
+ )
+ newNames = set(d.identifier.name for d in newDictionariesConvertibleFromJS)
+ changedDictionaryNames |= oldDictionariesConvertibleFromJS ^ newNames
+
+ for name in changedDictionaryNames:
+ d = self._config.getDictionaryIfExists(name)
+ if d:
+ changed_inputs.add(d.filename())
+
+ # Only use paths that are known to our current state.
+ # This filters out files that were deleted or changed type (e.g. from
+ # static to preprocessed).
+ return changed_inputs & self._input_paths
+
+ def _binding_info(self, p):
+ """Compute binding metadata for an input path.
+
+ Returns a tuple of:
+
+ (stem, binding_stem, is_event, output_files)
+
+ output_files is itself a tuple. The first two items are the binding
+ header and C++ paths, respectively. The 2nd pair are the event header
+ and C++ paths or None if this isn't an event binding.
+ """
+ basename = mozpath.basename(p)
+ stem = mozpath.splitext(basename)[0]
+ binding_stem = "%sBinding" % stem
+
+ if stem in self._exported_stems:
+ header_dir = self._exported_header_dir
+ else:
+ header_dir = self._codegen_dir
+
+ is_event = stem in self._generated_events_stems
+
+ files = (
+ mozpath.join(header_dir, "%s.h" % binding_stem),
+ mozpath.join(self._codegen_dir, "%s.cpp" % binding_stem),
+ mozpath.join(header_dir, "%s.h" % stem) if is_event else None,
+ mozpath.join(self._codegen_dir, "%s.cpp" % stem) if is_event else None,
+ )
+
+ return stem, binding_stem, is_event, header_dir, files
+
+ def _example_paths(self, interface):
+ return (
+ mozpath.join(self._codegen_dir, "%s-example.h" % interface),
+ mozpath.join(self._codegen_dir, "%s-example.cpp" % interface),
+ )
+
+ def expected_build_output_files(self):
+ """Obtain the set of files generate_build_files() should write."""
+ paths = set()
+
+ # Account for global generation.
+ for p in self.GLOBAL_DECLARE_FILES:
+ paths.add(mozpath.join(self._exported_header_dir, p))
+ for p in self.GLOBAL_DEFINE_FILES:
+ paths.add(mozpath.join(self._codegen_dir, p))
+
+ for p in self._input_paths:
+ stem, binding_stem, is_event, header_dir, files = self._binding_info(p)
+ paths |= {f for f in files if f}
+
+ for interface in self._example_interfaces:
+ for p in self._example_paths(interface):
+ paths.add(p)
+
+ return paths
+
+ def _generate_build_files_for_webidl(self, filename):
+ from Codegen import CGBindingRoot, CGEventRoot
+
+ self.log(
+ logging.INFO,
+ "webidl_generate_build_for_input",
+ {"filename": filename},
+ "Generating WebIDL files derived from {filename}",
+ )
+
+ stem, binding_stem, is_event, header_dir, files = self._binding_info(filename)
+ root = CGBindingRoot(self._config, binding_stem, filename)
+
+ result = self._maybe_write_codegen(root, files[0], files[1])
+
+ if is_event:
+ generated_event = CGEventRoot(self._config, stem)
+ result = self._maybe_write_codegen(
+ generated_event, files[2], files[3], result
+ )
+
+ return result, root.deps()
+
+ def _global_dependencies_changed(self):
+ """Determine whether the global dependencies have changed."""
+ current_files = set(iter_modules_in_path(mozpath.dirname(__file__)))
+
+ # We need to catch other .py files from /dom/bindings. We assume these
+ # are in the same directory as the config file.
+ current_files |= set(iter_modules_in_path(mozpath.dirname(self._config_path)))
+
+ current_files.add(self._config_path)
+
+ current_hashes = {}
+ for f in current_files:
+ # This will fail if the file doesn't exist. If a current global
+ # dependency doesn't exist, something else is wrong.
+ with io.open(f, "rb") as fh:
+ current_hashes[f] = hashlib.sha1(fh.read()).hexdigest()
+
+ # The set of files has changed.
+ if current_files ^ set(self._state["global_depends"].keys()):
+ return True, current_hashes
+
+ # Compare hashes.
+ for f, sha1 in current_hashes.items():
+ if sha1 != self._state["global_depends"][f]:
+ return True, current_hashes
+
+ return False, current_hashes
+
+ def _save_state(self):
+ with io.open(self._state_path, "w", newline="\n") as fh:
+ self._state.dump(fh)
+
+ def _maybe_write_codegen(self, obj, declare_path, define_path, result=None):
+ assert declare_path and define_path
+ if not result:
+ result = (set(), set(), set())
+
+ self._maybe_write_file(declare_path, obj.declare(), result)
+ self._maybe_write_file(define_path, obj.define(), result)
+
+ return result
+
+ def _maybe_write_file(self, path, content, result):
+ fh = FileAvoidWrite(path)
+ fh.write(content)
+ existed, updated = fh.close()
+
+ if not existed:
+ result[0].add(path)
+ elif updated:
+ result[1].add(path)
+ else:
+ result[2].add(path)
+
+
+def create_build_system_manager(topsrcdir=None, topobjdir=None, dist_dir=None):
+ """Create a WebIDLCodegenManager for use by the build system."""
+ if topsrcdir is None:
+ assert topobjdir is None and dist_dir is None
+ import buildconfig
+
+ topsrcdir = buildconfig.topsrcdir
+ topobjdir = buildconfig.topobjdir
+ dist_dir = buildconfig.substs["DIST"]
+
+ src_dir = os.path.join(topsrcdir, "dom", "bindings")
+ obj_dir = os.path.join(topobjdir, "dom", "bindings")
+ webidl_root = os.path.join(topsrcdir, "dom", "webidl")
+
+ with io.open(os.path.join(obj_dir, "file-lists.json"), "r") as fh:
+ files = json.load(fh)
+
+ inputs = (
+ files["webidls"],
+ files["exported_stems"],
+ files["generated_events_stems"],
+ files["example_interfaces"],
+ )
+
+ cache_dir = os.path.join(obj_dir, "_cache")
+ try:
+ os.makedirs(cache_dir)
+ except OSError as e:
+ if e.errno != errno.EEXIST:
+ raise
+
+ return WebIDLCodegenManager(
+ os.path.join(src_dir, "Bindings.conf"),
+ webidl_root,
+ inputs,
+ os.path.join(dist_dir, "include", "mozilla", "dom"),
+ obj_dir,
+ os.path.join(obj_dir, "codegen.json"),
+ cache_dir=cache_dir,
+ # The make rules include a codegen.pp file containing dependencies.
+ make_deps_path=os.path.join(obj_dir, "codegen.pp"),
+ make_deps_target="webidl.stub",
+ )
diff --git a/dom/bindings/mozwebidlcodegen/test/Child.webidl b/dom/bindings/mozwebidlcodegen/test/Child.webidl
new file mode 100644
index 0000000000..aa400a52a1
--- /dev/null
+++ b/dom/bindings/mozwebidlcodegen/test/Child.webidl
@@ -0,0 +1,3 @@
+interface Child : Parent {
+ undefined ChildBaz();
+};
diff --git a/dom/bindings/mozwebidlcodegen/test/ExampleBinding.webidl b/dom/bindings/mozwebidlcodegen/test/ExampleBinding.webidl
new file mode 100644
index 0000000000..34794993fe
--- /dev/null
+++ b/dom/bindings/mozwebidlcodegen/test/ExampleBinding.webidl
@@ -0,0 +1,3 @@
+/* These interfaces are hard-coded and need to be defined. */
+interface TestExampleInterface {};
+interface TestExampleProxyInterface {};
diff --git a/dom/bindings/mozwebidlcodegen/test/Parent.webidl b/dom/bindings/mozwebidlcodegen/test/Parent.webidl
new file mode 100644
index 0000000000..9581a6b4e7
--- /dev/null
+++ b/dom/bindings/mozwebidlcodegen/test/Parent.webidl
@@ -0,0 +1,3 @@
+interface Parent {
+ undefined MethodFoo();
+};
diff --git a/dom/bindings/mozwebidlcodegen/test/TestEvent.webidl b/dom/bindings/mozwebidlcodegen/test/TestEvent.webidl
new file mode 100644
index 0000000000..0b795a8024
--- /dev/null
+++ b/dom/bindings/mozwebidlcodegen/test/TestEvent.webidl
@@ -0,0 +1,13 @@
+interface EventTarget {
+ undefined addEventListener();
+};
+
+interface Event {};
+
+callback EventHandlerNonNull = any (Event event);
+typedef EventHandlerNonNull? EventHandler;
+
+[LegacyNoInterfaceObject]
+interface TestEvent : EventTarget {
+ attribute EventHandler onfoo;
+};
diff --git a/dom/bindings/mozwebidlcodegen/test/python.ini b/dom/bindings/mozwebidlcodegen/test/python.ini
new file mode 100644
index 0000000000..3b86feafb3
--- /dev/null
+++ b/dom/bindings/mozwebidlcodegen/test/python.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+subsuite = mozbuild
+
+[test_mozwebidlcodegen.py]
diff --git a/dom/bindings/mozwebidlcodegen/test/test_mozwebidlcodegen.py b/dom/bindings/mozwebidlcodegen/test/test_mozwebidlcodegen.py
new file mode 100644
index 0000000000..10129022e6
--- /dev/null
+++ b/dom/bindings/mozwebidlcodegen/test/test_mozwebidlcodegen.py
@@ -0,0 +1,297 @@
+# 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/.
+
+import imp
+import io
+import json
+import os
+import shutil
+import sys
+import tempfile
+import unittest
+
+import mozpack.path as mozpath
+from mozfile import NamedTemporaryFile
+from mozunit import MockedOpen, main
+from mozwebidlcodegen import WebIDLCodegenManager, WebIDLCodegenManagerState
+
+OUR_DIR = mozpath.abspath(mozpath.dirname(__file__))
+TOPSRCDIR = mozpath.normpath(mozpath.join(OUR_DIR, "..", "..", "..", ".."))
+
+
+class TestWebIDLCodegenManager(unittest.TestCase):
+ TEST_STEMS = {
+ "Child",
+ "Parent",
+ "ExampleBinding",
+ "TestEvent",
+ }
+
+ @property
+ def _static_input_paths(self):
+ s = {
+ mozpath.join(OUR_DIR, p)
+ for p in os.listdir(OUR_DIR)
+ if p.endswith(".webidl")
+ }
+
+ return s
+
+ @property
+ def _config_path(self):
+ config = mozpath.join(TOPSRCDIR, "dom", "bindings", "Bindings.conf")
+ self.assertTrue(os.path.exists(config))
+
+ return config
+
+ def _get_manager_args(self):
+ tmp = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, tmp)
+
+ cache_dir = mozpath.join(tmp, "cache")
+ os.mkdir(cache_dir)
+
+ ip = self._static_input_paths
+
+ inputs = (
+ ip,
+ {mozpath.splitext(mozpath.basename(p))[0] for p in ip},
+ set(),
+ set(),
+ )
+
+ return dict(
+ config_path=self._config_path,
+ webidl_root=cache_dir,
+ inputs=inputs,
+ exported_header_dir=mozpath.join(tmp, "exports"),
+ codegen_dir=mozpath.join(tmp, "codegen"),
+ state_path=mozpath.join(tmp, "state.json"),
+ make_deps_path=mozpath.join(tmp, "codegen.pp"),
+ make_deps_target="codegen.pp",
+ cache_dir=cache_dir,
+ )
+
+ def _get_manager(self):
+ return WebIDLCodegenManager(**self._get_manager_args())
+
+ def test_unknown_state_version(self):
+ """Loading a state file with a too new version resets state."""
+ args = self._get_manager_args()
+
+ p = args["state_path"]
+
+ with io.open(p, "w", newline="\n") as fh:
+ json.dump(
+ {
+ "version": WebIDLCodegenManagerState.VERSION + 1,
+ "foobar": "1",
+ },
+ fh,
+ )
+
+ manager = WebIDLCodegenManager(**args)
+
+ self.assertEqual(manager._state["version"], WebIDLCodegenManagerState.VERSION)
+ self.assertNotIn("foobar", manager._state)
+
+ def test_generate_build_files(self):
+ """generate_build_files() does the right thing from empty."""
+ manager = self._get_manager()
+ result = manager.generate_build_files()
+ self.assertEqual(len(result.inputs), 4)
+
+ output = manager.expected_build_output_files()
+ self.assertEqual(result.created, output)
+ self.assertEqual(len(result.updated), 0)
+ self.assertEqual(len(result.unchanged), 0)
+
+ for f in output:
+ self.assertTrue(os.path.isfile(f))
+
+ for f in manager.GLOBAL_DECLARE_FILES:
+ self.assertIn(mozpath.join(manager._exported_header_dir, f), output)
+
+ for f in manager.GLOBAL_DEFINE_FILES:
+ self.assertIn(mozpath.join(manager._codegen_dir, f), output)
+
+ for s in self.TEST_STEMS:
+ self.assertTrue(
+ os.path.isfile(
+ mozpath.join(manager._exported_header_dir, "%sBinding.h" % s)
+ )
+ )
+ self.assertTrue(
+ os.path.isfile(mozpath.join(manager._codegen_dir, "%sBinding.cpp" % s))
+ )
+
+ self.assertTrue(os.path.isfile(manager._state_path))
+
+ with io.open(manager._state_path, "r") as fh:
+ state = json.load(fh)
+ self.assertEqual(state["version"], 3)
+ self.assertIn("webidls", state)
+
+ child = state["webidls"]["Child.webidl"]
+ self.assertEqual(len(child["inputs"]), 2)
+ self.assertEqual(len(child["outputs"]), 2)
+ self.assertEqual(child["sha1"], "c34c40b0fa0ac57c2834ee282efe0681e4dacc35")
+
+ def test_generate_build_files_load_state(self):
+ """State should be equivalent when instantiating a new instance."""
+ args = self._get_manager_args()
+ m1 = WebIDLCodegenManager(**args)
+ self.assertEqual(len(m1._state["webidls"]), 0)
+ m1.generate_build_files()
+
+ m2 = WebIDLCodegenManager(**args)
+ self.assertGreater(len(m2._state["webidls"]), 2)
+ self.assertEqual(m1._state, m2._state)
+
+ def test_no_change_no_writes(self):
+ """If nothing changes, no files should be updated."""
+ args = self._get_manager_args()
+ m1 = WebIDLCodegenManager(**args)
+ m1.generate_build_files()
+
+ m2 = WebIDLCodegenManager(**args)
+ result = m2.generate_build_files()
+
+ self.assertEqual(len(result.inputs), 0)
+ self.assertEqual(len(result.created), 0)
+ self.assertEqual(len(result.updated), 0)
+
+ def test_output_file_regenerated(self):
+ """If an output file disappears, it is regenerated."""
+ args = self._get_manager_args()
+ m1 = WebIDLCodegenManager(**args)
+ m1.generate_build_files()
+
+ rm_count = 0
+ for p in m1._state["webidls"]["Child.webidl"]["outputs"]:
+ rm_count += 1
+ os.unlink(p)
+
+ for p in m1.GLOBAL_DECLARE_FILES:
+ rm_count += 1
+ os.unlink(mozpath.join(m1._exported_header_dir, p))
+
+ m2 = WebIDLCodegenManager(**args)
+ result = m2.generate_build_files()
+ self.assertEqual(len(result.created), rm_count)
+
+ def test_only_rebuild_self(self):
+ """If an input file changes, only rebuild that one file."""
+ args = self._get_manager_args()
+ m1 = WebIDLCodegenManager(**args)
+ m1.generate_build_files()
+
+ child_path = None
+ for p in m1._input_paths:
+ if p.endswith("Child.webidl"):
+ child_path = p
+ break
+
+ self.assertIsNotNone(child_path)
+ child_content = io.open(child_path, "r").read()
+
+ with MockedOpen({child_path: child_content + "\n/* */"}):
+ m2 = WebIDLCodegenManager(**args)
+ result = m2.generate_build_files()
+ self.assertEqual(result.inputs, set([child_path]))
+ self.assertEqual(len(result.updated), 0)
+ self.assertEqual(len(result.created), 0)
+
+ def test_rebuild_dependencies(self):
+ """Ensure an input file used by others results in others rebuilding."""
+ args = self._get_manager_args()
+ m1 = WebIDLCodegenManager(**args)
+ m1.generate_build_files()
+
+ parent_path = None
+ child_path = None
+ for p in m1._input_paths:
+ if p.endswith("Parent.webidl"):
+ parent_path = p
+ elif p.endswith("Child.webidl"):
+ child_path = p
+
+ self.assertIsNotNone(parent_path)
+ parent_content = io.open(parent_path, "r").read()
+
+ with MockedOpen({parent_path: parent_content + "\n/* */"}):
+ m2 = WebIDLCodegenManager(**args)
+ result = m2.generate_build_files()
+ self.assertEqual(result.inputs, {child_path, parent_path})
+ self.assertEqual(len(result.updated), 0)
+ self.assertEqual(len(result.created), 0)
+
+ def test_python_change_regenerate_everything(self):
+ """If a Python file changes, we should attempt to rebuild everything."""
+
+ # We don't want to mutate files in the source directory because we want
+ # to be able to build from a read-only filesystem. So, we install a
+ # dummy module and rewrite the metadata to say it comes from the source
+ # directory.
+ #
+ # Hacking imp to accept a MockedFile doesn't appear possible. So for
+ # the first iteration we read from a temp file. The second iteration
+ # doesn't need to import, so we are fine with a mocked file.
+ fake_path = mozpath.join(OUR_DIR, "fakemodule.py")
+ with NamedTemporaryFile("wt") as fh:
+ fh.write("# Original content")
+ fh.flush()
+ mod = imp.load_source("mozwebidlcodegen.fakemodule", fh.name)
+ mod.__file__ = fake_path
+
+ args = self._get_manager_args()
+ m1 = WebIDLCodegenManager(**args)
+ with MockedOpen({fake_path: "# Original content"}):
+ try:
+ result = m1.generate_build_files()
+ l = len(result.inputs)
+
+ with io.open(fake_path, "wt", newline="\n") as fh:
+ fh.write("# Modified content")
+
+ m2 = WebIDLCodegenManager(**args)
+ result = m2.generate_build_files()
+ self.assertEqual(len(result.inputs), l)
+
+ result = m2.generate_build_files()
+ self.assertEqual(len(result.inputs), 0)
+ finally:
+ del sys.modules["mozwebidlcodegen.fakemodule"]
+
+ def test_copy_input(self):
+ """Ensure a copied .webidl file is handled properly."""
+
+ # This test simulates changing the type of a WebIDL from static to
+ # preprocessed. In that scenario, the original file still exists but
+ # it should no longer be consulted during codegen.
+
+ args = self._get_manager_args()
+ m1 = WebIDLCodegenManager(**args)
+ m1.generate_build_files()
+
+ old_path = None
+ for p in args["inputs"][0]:
+ if p.endswith("Parent.webidl"):
+ old_path = p
+ break
+ self.assertIsNotNone(old_path)
+
+ new_path = mozpath.join(args["cache_dir"], "Parent.webidl")
+ shutil.copy2(old_path, new_path)
+
+ args["inputs"][0].remove(old_path)
+ args["inputs"][0].add(new_path)
+
+ m2 = WebIDLCodegenManager(**args)
+ result = m2.generate_build_files()
+ self.assertEqual(len(result.updated), 0)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/dom/bindings/nsIScriptError.idl b/dom/bindings/nsIScriptError.idl
new file mode 100644
index 0000000000..3d7925a08d
--- /dev/null
+++ b/dom/bindings/nsIScriptError.idl
@@ -0,0 +1,296 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+/*
+ * nsIConsoleMessage subclass for representing JavaScript errors and warnings.
+ */
+
+
+#include "nsISupports.idl"
+#include "nsIArray.idl"
+#include "nsIConsoleMessage.idl"
+interface nsIURI;
+
+%{C++
+#include "nsString.h" // for nsDependentCString
+%}
+
+[scriptable, uuid(e8933fc9-c302-4e12-a55b-4f88611d9c6c)]
+interface nsIScriptErrorNote : nsISupports
+{
+ readonly attribute AString errorMessage;
+ readonly attribute AString sourceName;
+
+ /**
+ * Unique identifier within the process for the script source this note is
+ * associated with, or zero.
+ */
+ readonly attribute uint32_t sourceId;
+
+ readonly attribute uint32_t lineNumber;
+ readonly attribute uint32_t columnNumber;
+
+ AUTF8String toString();
+};
+
+[scriptable, uuid(63eb4d3e-7d99-4150-b4f3-11314f9d82a9)]
+interface nsIScriptError : nsIConsoleMessage
+{
+ /** pseudo-flag for default case */
+ const unsigned long errorFlag = 0x0;
+
+ /** message is warning */
+ const unsigned long warningFlag = 0x1;
+
+ /** just a log message */
+ const unsigned long infoFlag = 0x8;
+
+ /**
+ * The error message without any context/line number information.
+ *
+ * @note nsIConsoleMessage.message will return the error formatted
+ * with file/line information.
+ */
+ readonly attribute AString errorMessage;
+
+ readonly attribute AString sourceName;
+ readonly attribute AString sourceLine;
+
+ /**
+ * Unique identifier within the process for the script source this error is
+ * associated with, or zero.
+ */
+ readonly attribute uint32_t sourceId;
+
+ readonly attribute uint32_t lineNumber;
+ readonly attribute uint32_t columnNumber;
+ readonly attribute uint32_t flags;
+
+ /**
+ * Categories I know about -
+ * XUL javascript
+ * content javascript (both of these from nsDocShell, currently)
+ * system javascript (errors in JS components and other system JS)
+ */
+ readonly attribute string category;
+
+ /* Get the window id this was initialized with. Zero will be
+ returned if init() was used instead of initWithWindowID(). */
+ readonly attribute unsigned long long outerWindowID;
+
+ /* Get the inner window id this was initialized with. Zero will be
+ returned if init() was used instead of initWithWindowID(). */
+ readonly attribute unsigned long long innerWindowID;
+
+ readonly attribute boolean isFromPrivateWindow;
+
+ readonly attribute boolean isFromChromeContext;
+
+ // Error created from a Promise rejection.
+ readonly attribute boolean isPromiseRejection;
+
+ [noscript] void initIsPromiseRejection(in bool isPromiseRejection);
+
+ // The "exception" value generated when an uncaught exception is thrown
+ // by JavaScript. This can be any value, e.g.
+ // - an Error object (`throw new Error("foobar"`)
+ // - some primitive (`throw "hello"`)
+ attribute jsval exception;
+
+ // The hasException attribute is used to differentiate between no thrown
+ // exception and `throw undefined`.
+ readonly attribute boolean hasException;
+
+ attribute jsval stack;
+
+ /**
+ * If |stack| is an object, then stackGlobal must be a global object that's
+ * same-compartment with |stack|. This can be used to enter the correct
+ * realm when working with the stack object. We can't use the object itself
+ * because it might be a cross-compartment wrapper and CCWs are not
+ * associated with a single realm/global.
+ */
+ [noscript] readonly attribute jsval stackGlobal;
+
+ /**
+ * The name of a template string associated with the error message. See
+ * js/public/friend/ErrorNumbers.msg.
+ */
+ attribute AString errorMessageName;
+
+ readonly attribute nsIArray notes;
+
+ /**
+ * If the ScriptError is a CSS parser error, this value will contain the
+ * CSS selectors of the CSS ruleset where the error occured.
+ */
+ attribute AString cssSelectors;
+
+ void init(in AString message,
+ in AString sourceName,
+ in AString sourceLine,
+ in uint32_t lineNumber,
+ in uint32_t columnNumber,
+ in uint32_t flags,
+ in ACString category,
+ [optional] in bool fromPrivateWindow,
+ [optional] in bool fromChromeContext);
+
+ /* This should be called instead of nsIScriptError.init to
+ * initialize with a window id. The window id should be for the
+ * inner window associated with this error.
+ *
+ * This function will check whether sourceName is a uri and sanitize it if
+ * needed. If you know the source name is sanitized already, use
+ * initWithSanitizedSource.
+ * A "sanitized" source name means that passwords are not shown. It will
+ * use the sensitiveInfoHiddenSpec function of nsIURI interface, that is
+ * replacing paswords with ***
+ * (e.g. https://USERNAME:****@example.com/some/path).
+ */
+ void initWithWindowID(in AString message,
+ in AString sourceName,
+ in AString sourceLine,
+ in uint32_t lineNumber,
+ in uint32_t columnNumber,
+ in uint32_t flags,
+ in ACString category,
+ in unsigned long long innerWindowID,
+ [optional] in bool fromChromeContext);
+
+ /* This is the same function as initWithWindowID, but it expects an already
+ * sanitized sourceName.
+ * Please use it only if sourceName string is already sanitized.
+ */
+ void initWithSanitizedSource(in AString message,
+ in AString sourceName,
+ in AString sourceLine,
+ in uint32_t lineNumber,
+ in uint32_t columnNumber,
+ in uint32_t flags,
+ in ACString category,
+ in unsigned long long innerWindowID,
+ [optional] in bool fromChromeContext);
+
+ /* This is the same function as initWithWindowID with an uri as a source parameter.
+ */
+ void initWithSourceURI(in AString message,
+ in nsIURI sourceURI,
+ in AString sourceLine,
+ in uint32_t lineNumber,
+ in uint32_t columnNumber,
+ in uint32_t flags,
+ in ACString category,
+ in unsigned long long innerWindowID,
+ [optional] in bool fromChromeContext);
+
+ /* Initialize the script source ID in a new error. */
+ void initSourceId(in uint32_t sourceId);
+
+%{C++
+ nsresult InitWithWindowID(const nsAString& message,
+ const nsAString& sourceName,
+ const nsAString& sourceLine,
+ uint32_t lineNumber,
+ uint32_t columnNumber,
+ uint32_t flags,
+ const nsACString& category,
+ uint64_t aInnerWindowID)
+ {
+ return InitWithWindowID(message, sourceName, sourceLine, lineNumber,
+ columnNumber, flags, category, aInnerWindowID,
+ false);
+ }
+ // These overloads allow passing a literal string for category.
+ template<uint32_t N>
+ nsresult InitWithWindowID(const nsAString& message,
+ const nsAString& sourceName,
+ const nsAString& sourceLine,
+ uint32_t lineNumber,
+ uint32_t columnNumber,
+ uint32_t flags,
+ const char (&c)[N],
+ uint64_t aInnerWindowID,
+ bool aFromChromeContext = false)
+ {
+ nsDependentCString category(c, N - 1);
+ return InitWithWindowID(message, sourceName, sourceLine, lineNumber,
+ columnNumber, flags, category, aInnerWindowID,
+ aFromChromeContext);
+ }
+
+ nsresult InitWithSanitizedSource(const nsAString& message,
+ const nsAString& sourceName,
+ const nsAString& sourceLine,
+ uint32_t lineNumber,
+ uint32_t columnNumber,
+ uint32_t flags,
+ const nsACString& category,
+ uint64_t aInnerWindowID)
+ {
+ return InitWithSanitizedSource(message, sourceName, sourceLine,
+ lineNumber, columnNumber, flags,
+ category, aInnerWindowID,
+ false);
+ }
+
+ template<uint32_t N>
+ nsresult InitWithSanitizedSource(const nsAString& message,
+ const nsAString& sourceName,
+ const nsAString& sourceLine,
+ uint32_t lineNumber,
+ uint32_t columnNumber,
+ uint32_t flags,
+ const char (&c)[N],
+ uint64_t aInnerWindowID,
+ bool aFromChromeContext = false)
+ {
+ nsDependentCString category(c, N - 1);
+ return InitWithSanitizedSource(message, sourceName, sourceLine,
+ lineNumber, columnNumber, flags,
+ category, aInnerWindowID,
+ aFromChromeContext);
+ }
+
+ nsresult InitWithSourceURI(const nsAString& message,
+ nsIURI* sourceURI,
+ const nsAString& sourceLine,
+ uint32_t lineNumber,
+ uint32_t columnNumber,
+ uint32_t flags,
+ const nsACString& category,
+ uint64_t aInnerWindowID)
+ {
+ return InitWithSourceURI(message, sourceURI, sourceLine,
+ lineNumber, columnNumber, flags,
+ category, aInnerWindowID, false);
+ }
+
+ template<uint32_t N>
+ nsresult InitWithSourceURI(const nsAString& message,
+ nsIURI* sourceURI,
+ const nsAString& sourceLine,
+ uint32_t lineNumber,
+ uint32_t columnNumber,
+ uint32_t flags,
+ const char (&c)[N],
+ uint64_t aInnerWindowID,
+ bool aFromChromeContext = false)
+ {
+ nsDependentCString category(c, N - 1);
+ return InitWithSourceURI(message, sourceURI, sourceLine,
+ lineNumber, columnNumber, flags,
+ category, aInnerWindowID, aFromChromeContext);
+ }
+%}
+
+};
+
+%{ C++
+#define NS_SCRIPTERROR_CID \
+{ 0x1950539a, 0x90f0, 0x4d22, { 0xb5, 0xaf, 0x71, 0x32, 0x9c, 0x68, 0xfa, 0x35 }}
+
+#define NS_SCRIPTERROR_CONTRACTID "@mozilla.org/scripterror;1"
+%}
diff --git a/dom/bindings/nsScriptError.cpp b/dom/bindings/nsScriptError.cpp
new file mode 100644
index 0000000000..0cc71e0bcc
--- /dev/null
+++ b/dom/bindings/nsScriptError.cpp
@@ -0,0 +1,531 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/*
+ * nsIScriptError implementation.
+ */
+
+#include "nsScriptError.h"
+#include "js/Printf.h"
+#include "MainThreadUtils.h"
+#include "mozilla/Assertions.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "nsIMutableArray.h"
+#include "nsIScriptError.h"
+#include "mozilla/BasePrincipal.h"
+
+nsScriptErrorBase::nsScriptErrorBase()
+ : mMessage(),
+ mMessageName(),
+ mSourceName(),
+ mCssSelectors(),
+ mSourceId(0),
+ mLineNumber(0),
+ mSourceLine(),
+ mColumnNumber(0),
+ mFlags(0),
+ mCategory(),
+ mOuterWindowID(0),
+ mInnerWindowID(0),
+ mMicroSecondTimeStamp(0),
+ mInitializedOnMainThread(false),
+ mIsFromPrivateWindow(false),
+ mIsFromChromeContext(false),
+ mIsPromiseRejection(false),
+ mIsForwardedFromContentProcess(false) {}
+
+nsScriptErrorBase::~nsScriptErrorBase() = default;
+
+void nsScriptErrorBase::AddNote(nsIScriptErrorNote* note) {
+ mNotes.AppendObject(note);
+}
+
+void nsScriptErrorBase::InitializeOnMainThread() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mInitializedOnMainThread);
+
+ if (mInnerWindowID) {
+ nsGlobalWindowInner* window =
+ nsGlobalWindowInner::GetInnerWindowWithId(mInnerWindowID);
+ if (window) {
+ nsPIDOMWindowOuter* outer = window->GetOuterWindow();
+ if (outer) mOuterWindowID = outer->WindowID();
+ mIsFromChromeContext = ComputeIsFromChromeContext(window);
+ mIsFromPrivateWindow = ComputeIsFromPrivateWindow(window);
+ }
+ }
+
+ mInitializedOnMainThread = true;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::InitSourceId(uint32_t value) {
+ mSourceId = value;
+ return NS_OK;
+}
+
+// nsIConsoleMessage methods
+NS_IMETHODIMP
+nsScriptErrorBase::GetMessageMoz(nsAString& aMessage) {
+ nsAutoCString message;
+ nsresult rv = ToString(message);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ CopyUTF8toUTF16(message, aMessage);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetLogLevel(uint32_t* aLogLevel) {
+ if (mFlags & (uint32_t)nsIScriptError::infoFlag) {
+ *aLogLevel = nsIConsoleMessage::info;
+ } else if (mFlags & (uint32_t)nsIScriptError::warningFlag) {
+ *aLogLevel = nsIConsoleMessage::warn;
+ } else {
+ *aLogLevel = nsIConsoleMessage::error;
+ }
+ return NS_OK;
+}
+
+// nsIScriptError methods
+NS_IMETHODIMP
+nsScriptErrorBase::GetErrorMessage(nsAString& aResult) {
+ aResult.Assign(mMessage);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetSourceName(nsAString& aResult) {
+ aResult.Assign(mSourceName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetCssSelectors(nsAString& aResult) {
+ aResult.Assign(mCssSelectors);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::SetCssSelectors(const nsAString& aCssSelectors) {
+ mCssSelectors = aCssSelectors;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetSourceId(uint32_t* result) {
+ *result = mSourceId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetSourceLine(nsAString& aResult) {
+ aResult.Assign(mSourceLine);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetLineNumber(uint32_t* result) {
+ *result = mLineNumber;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetColumnNumber(uint32_t* result) {
+ *result = mColumnNumber;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetFlags(uint32_t* result) {
+ *result = mFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetCategory(char** result) {
+ *result = ToNewCString(mCategory);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetHasException(bool* aHasException) {
+ *aHasException = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetException(JS::MutableHandle<JS::Value> aException) {
+ aException.setUndefined();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::SetException(JS::Handle<JS::Value> aStack) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetStack(JS::MutableHandle<JS::Value> aStack) {
+ aStack.setUndefined();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::SetStack(JS::Handle<JS::Value> aStack) { return NS_OK; }
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetStackGlobal(JS::MutableHandle<JS::Value> aStackGlobal) {
+ aStackGlobal.setUndefined();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetErrorMessageName(nsAString& aErrorMessageName) {
+ aErrorMessageName = mMessageName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::SetErrorMessageName(const nsAString& aErrorMessageName) {
+ mMessageName = aErrorMessageName;
+ return NS_OK;
+}
+
+static void AssignSourceNameHelper(nsString& aSourceNameDest,
+ const nsAString& aSourceNameSrc) {
+ if (aSourceNameSrc.IsEmpty()) return;
+
+ aSourceNameDest.Assign(aSourceNameSrc);
+
+ nsCOMPtr<nsIURI> uri;
+ nsAutoCString pass;
+ if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), aSourceNameSrc)) &&
+ NS_SUCCEEDED(uri->GetPassword(pass)) && !pass.IsEmpty()) {
+ NS_GetSanitizedURIStringFromURI(uri, aSourceNameDest);
+ }
+}
+
+static void AssignSourceNameHelper(nsIURI* aSourceURI,
+ nsString& aSourceNameDest) {
+ if (!aSourceURI) return;
+
+ if (NS_FAILED(NS_GetSanitizedURIStringFromURI(aSourceURI, aSourceNameDest))) {
+ aSourceNameDest.AssignLiteral("[nsIURI::GetSpec failed]");
+ }
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::Init(const nsAString& message, const nsAString& sourceName,
+ const nsAString& sourceLine, uint32_t lineNumber,
+ uint32_t columnNumber, uint32_t flags,
+ const nsACString& category, bool fromPrivateWindow,
+ bool fromChromeContext) {
+ InitializationHelper(message, sourceLine, lineNumber, columnNumber, flags,
+ category, 0 /* inner Window ID */, fromChromeContext);
+ AssignSourceNameHelper(mSourceName, sourceName);
+
+ mIsFromPrivateWindow = fromPrivateWindow;
+ mIsFromChromeContext = fromChromeContext;
+ return NS_OK;
+}
+
+void nsScriptErrorBase::InitializationHelper(
+ const nsAString& message, const nsAString& sourceLine, uint32_t lineNumber,
+ uint32_t columnNumber, uint32_t flags, const nsACString& category,
+ uint64_t aInnerWindowID, bool aFromChromeContext) {
+ mMessage.Assign(message);
+ mLineNumber = lineNumber;
+ mSourceLine.Assign(sourceLine);
+ mColumnNumber = columnNumber;
+ mFlags = flags;
+ mCategory = category;
+ mMicroSecondTimeStamp = JS_Now();
+ mInnerWindowID = aInnerWindowID;
+ mIsFromChromeContext = aFromChromeContext;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::InitWithWindowID(const nsAString& message,
+ const nsAString& sourceName,
+ const nsAString& sourceLine,
+ uint32_t lineNumber, uint32_t columnNumber,
+ uint32_t flags, const nsACString& category,
+ uint64_t aInnerWindowID,
+ bool aFromChromeContext) {
+ InitializationHelper(message, sourceLine, lineNumber, columnNumber, flags,
+ category, aInnerWindowID, aFromChromeContext);
+ AssignSourceNameHelper(mSourceName, sourceName);
+
+ if (aInnerWindowID && NS_IsMainThread()) InitializeOnMainThread();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::InitWithSanitizedSource(
+ const nsAString& message, const nsAString& sourceName,
+ const nsAString& sourceLine, uint32_t lineNumber, uint32_t columnNumber,
+ uint32_t flags, const nsACString& category, uint64_t aInnerWindowID,
+ bool aFromChromeContext) {
+ InitializationHelper(message, sourceLine, lineNumber, columnNumber, flags,
+ category, aInnerWindowID, aFromChromeContext);
+ mSourceName = sourceName;
+
+ if (aInnerWindowID && NS_IsMainThread()) InitializeOnMainThread();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::InitWithSourceURI(const nsAString& message,
+ nsIURI* sourceURI,
+ const nsAString& sourceLine,
+ uint32_t lineNumber, uint32_t columnNumber,
+ uint32_t flags, const nsACString& category,
+ uint64_t aInnerWindowID,
+ bool aFromChromeContext) {
+ InitializationHelper(message, sourceLine, lineNumber, columnNumber, flags,
+ category, aInnerWindowID, aFromChromeContext);
+ AssignSourceNameHelper(sourceURI, mSourceName);
+
+ if (aInnerWindowID && NS_IsMainThread()) InitializeOnMainThread();
+
+ return NS_OK;
+}
+
+static nsresult ToStringHelper(const char* aSeverity, const nsString& aMessage,
+ const nsString& aSourceName,
+ const nsString* aSourceLine,
+ uint32_t aLineNumber, uint32_t aColumnNumber,
+ nsACString& /*UTF8*/ aResult) {
+ static const char format0[] =
+ "[%s: \"%s\" {file: \"%s\" line: %d column: %d source: \"%s\"}]";
+ static const char format1[] = "[%s: \"%s\" {file: \"%s\" line: %d}]";
+ static const char format2[] = "[%s: \"%s\"]";
+
+ JS::UniqueChars temp;
+ char* tempMessage = nullptr;
+ char* tempSourceName = nullptr;
+ char* tempSourceLine = nullptr;
+
+ if (!aMessage.IsEmpty()) tempMessage = ToNewUTF8String(aMessage);
+ if (!aSourceName.IsEmpty())
+ // Use at most 512 characters from mSourceName.
+ tempSourceName = ToNewUTF8String(StringHead(aSourceName, 512));
+ if (aSourceLine && !aSourceLine->IsEmpty())
+ // Use at most 512 characters from mSourceLine.
+ tempSourceLine = ToNewUTF8String(StringHead(*aSourceLine, 512));
+
+ if (nullptr != tempSourceName && nullptr != tempSourceLine) {
+ temp = JS_smprintf(format0, aSeverity, tempMessage, tempSourceName,
+ aLineNumber, aColumnNumber, tempSourceLine);
+ } else if (!aSourceName.IsEmpty()) {
+ temp = JS_smprintf(format1, aSeverity, tempMessage, tempSourceName,
+ aLineNumber);
+ } else {
+ temp = JS_smprintf(format2, aSeverity, tempMessage);
+ }
+
+ if (nullptr != tempMessage) free(tempMessage);
+ if (nullptr != tempSourceName) free(tempSourceName);
+ if (nullptr != tempSourceLine) free(tempSourceLine);
+
+ if (!temp) return NS_ERROR_OUT_OF_MEMORY;
+
+ aResult.Assign(temp.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::ToString(nsACString& /*UTF8*/ aResult) {
+ static const char error[] = "JavaScript Error";
+ static const char warning[] = "JavaScript Warning";
+
+ const char* severity =
+ !(mFlags & nsIScriptError::warningFlag) ? error : warning;
+
+ return ToStringHelper(severity, mMessage, mSourceName, &mSourceLine,
+ mLineNumber, mColumnNumber, aResult);
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetOuterWindowID(uint64_t* aOuterWindowID) {
+ NS_WARNING_ASSERTION(NS_IsMainThread() || mInitializedOnMainThread,
+ "This can't be safely determined off the main thread, "
+ "returning an inaccurate value!");
+
+ if (!mInitializedOnMainThread && NS_IsMainThread()) {
+ InitializeOnMainThread();
+ }
+
+ *aOuterWindowID = mOuterWindowID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetInnerWindowID(uint64_t* aInnerWindowID) {
+ *aInnerWindowID = mInnerWindowID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetTimeStamp(int64_t* aTimeStamp) {
+ *aTimeStamp = mMicroSecondTimeStamp / 1000;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetMicroSecondTimeStamp(int64_t* aTimeStamp) {
+ *aTimeStamp = mMicroSecondTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetIsFromPrivateWindow(bool* aIsFromPrivateWindow) {
+ NS_WARNING_ASSERTION(NS_IsMainThread() || mInitializedOnMainThread,
+ "This can't be safely determined off the main thread, "
+ "returning an inaccurate value!");
+
+ if (!mInitializedOnMainThread && NS_IsMainThread()) {
+ InitializeOnMainThread();
+ }
+
+ *aIsFromPrivateWindow = mIsFromPrivateWindow;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetIsFromChromeContext(bool* aIsFromChromeContext) {
+ NS_WARNING_ASSERTION(NS_IsMainThread() || mInitializedOnMainThread,
+ "This can't be safely determined off the main thread, "
+ "returning an inaccurate value!");
+ if (!mInitializedOnMainThread && NS_IsMainThread()) {
+ InitializeOnMainThread();
+ }
+ *aIsFromChromeContext = mIsFromChromeContext;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetIsPromiseRejection(bool* aIsPromiseRejection) {
+ *aIsPromiseRejection = mIsPromiseRejection;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::InitIsPromiseRejection(bool aIsPromiseRejection) {
+ mIsPromiseRejection = aIsPromiseRejection;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetIsForwardedFromContentProcess(
+ bool* aIsForwardedFromContentProcess) {
+ *aIsForwardedFromContentProcess = mIsForwardedFromContentProcess;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::SetIsForwardedFromContentProcess(
+ bool aIsForwardedFromContentProcess) {
+ mIsForwardedFromContentProcess = aIsForwardedFromContentProcess;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorBase::GetNotes(nsIArray** aNotes) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMutableArray> array = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t len = mNotes.Length();
+ for (uint32_t i = 0; i < len; i++) array->AppendElement(mNotes[i]);
+ array.forget(aNotes);
+
+ return NS_OK;
+}
+
+/* static */
+bool nsScriptErrorBase::ComputeIsFromPrivateWindow(
+ nsGlobalWindowInner* aWindow) {
+ // Never mark exceptions from chrome windows as having come from private
+ // windows, since we always want them to be reported.
+ nsIPrincipal* winPrincipal = aWindow->GetPrincipal();
+ return aWindow->IsPrivateBrowsing() && !winPrincipal->IsSystemPrincipal();
+}
+
+/* static */
+bool nsScriptErrorBase::ComputeIsFromChromeContext(
+ nsGlobalWindowInner* aWindow) {
+ nsIPrincipal* winPrincipal = aWindow->GetPrincipal();
+ return winPrincipal->IsSystemPrincipal();
+}
+
+NS_IMPL_ISUPPORTS(nsScriptError, nsIConsoleMessage, nsIScriptError)
+
+nsScriptErrorNote::nsScriptErrorNote()
+ : mMessage(),
+ mSourceName(),
+ mSourceId(0),
+ mLineNumber(0),
+ mColumnNumber(0) {}
+
+nsScriptErrorNote::~nsScriptErrorNote() = default;
+
+void nsScriptErrorNote::Init(const nsAString& message,
+ const nsAString& sourceName, uint32_t sourceId,
+ uint32_t lineNumber, uint32_t columnNumber) {
+ mMessage.Assign(message);
+ AssignSourceNameHelper(mSourceName, sourceName);
+ mSourceId = sourceId;
+ mLineNumber = lineNumber;
+ mColumnNumber = columnNumber;
+}
+
+// nsIScriptErrorNote methods
+NS_IMETHODIMP
+nsScriptErrorNote::GetErrorMessage(nsAString& aResult) {
+ aResult.Assign(mMessage);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorNote::GetSourceName(nsAString& aResult) {
+ aResult.Assign(mSourceName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorNote::GetSourceId(uint32_t* result) {
+ *result = mSourceId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorNote::GetLineNumber(uint32_t* result) {
+ *result = mLineNumber;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorNote::GetColumnNumber(uint32_t* result) {
+ *result = mColumnNumber;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorNote::ToString(nsACString& /*UTF8*/ aResult) {
+ return ToStringHelper("JavaScript Note", mMessage, mSourceName, nullptr,
+ mLineNumber, mColumnNumber, aResult);
+}
+
+NS_IMPL_ISUPPORTS(nsScriptErrorNote, nsIScriptErrorNote)
diff --git a/dom/bindings/nsScriptError.h b/dom/bindings/nsScriptError.h
new file mode 100644
index 0000000000..f00a859a59
--- /dev/null
+++ b/dom/bindings/nsScriptError.h
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_nsScriptError_h
+#define mozilla_dom_nsScriptError_h
+
+#include "mozilla/Atomics.h"
+
+#include <stdint.h>
+
+#include "jsapi.h"
+#include "js/RootingAPI.h"
+
+#include "nsCOMArray.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIScriptError.h"
+#include "nsString.h"
+
+class nsGlobalWindowInner;
+
+class nsScriptErrorNote final : public nsIScriptErrorNote {
+ public:
+ nsScriptErrorNote();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISCRIPTERRORNOTE
+
+ void Init(const nsAString& message, const nsAString& sourceName,
+ uint32_t sourceId, uint32_t lineNumber, uint32_t columnNumber);
+
+ private:
+ virtual ~nsScriptErrorNote();
+
+ nsString mMessage;
+ nsString mSourceName;
+ nsString mCssSelectors;
+ nsString mSourceLine;
+ uint32_t mSourceId;
+ uint32_t mLineNumber;
+ uint32_t mColumnNumber;
+};
+
+// Definition of nsScriptError..
+class nsScriptErrorBase : public nsIScriptError {
+ public:
+ nsScriptErrorBase();
+
+ NS_DECL_NSICONSOLEMESSAGE
+ NS_DECL_NSISCRIPTERROR
+
+ void AddNote(nsIScriptErrorNote* note);
+
+ static bool ComputeIsFromPrivateWindow(nsGlobalWindowInner* aWindow);
+
+ static bool ComputeIsFromChromeContext(nsGlobalWindowInner* aWindow);
+
+ protected:
+ virtual ~nsScriptErrorBase();
+
+ void InitializeOnMainThread();
+
+ void InitializationHelper(const nsAString& message,
+ const nsAString& sourceLine, uint32_t lineNumber,
+ uint32_t columnNumber, uint32_t flags,
+ const nsACString& category, uint64_t aInnerWindowID,
+ bool aFromChromeContext);
+
+ nsCOMArray<nsIScriptErrorNote> mNotes;
+ nsString mMessage;
+ nsString mMessageName;
+ nsString mSourceName;
+ nsString mCssSelectors;
+ uint32_t mSourceId;
+ uint32_t mLineNumber;
+ nsString mSourceLine;
+ uint32_t mColumnNumber;
+ uint32_t mFlags;
+ nsCString mCategory;
+ // mOuterWindowID is set on the main thread from InitializeOnMainThread().
+ uint64_t mOuterWindowID;
+ uint64_t mInnerWindowID;
+ int64_t mMicroSecondTimeStamp;
+ // mInitializedOnMainThread, mIsFromPrivateWindow and mIsFromChromeContext are
+ // set on the main thread from InitializeOnMainThread().
+ mozilla::Atomic<bool> mInitializedOnMainThread;
+ bool mIsFromPrivateWindow;
+ bool mIsFromChromeContext;
+ bool mIsPromiseRejection;
+ bool mIsForwardedFromContentProcess;
+};
+
+class nsScriptError final : public nsScriptErrorBase {
+ public:
+ nsScriptError() = default;
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ private:
+ virtual ~nsScriptError() = default;
+};
+
+class nsScriptErrorWithStack : public nsScriptErrorBase {
+ public:
+ nsScriptErrorWithStack(JS::Handle<mozilla::Maybe<JS::Value>> aException,
+ JS::Handle<JSObject*> aStack,
+ JS::Handle<JSObject*> aStackGlobal);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsScriptErrorWithStack)
+
+ NS_IMETHOD GetHasException(bool*) override;
+ NS_IMETHOD GetException(JS::MutableHandle<JS::Value>) override;
+
+ NS_IMETHOD GetStack(JS::MutableHandle<JS::Value>) override;
+ NS_IMETHOD GetStackGlobal(JS::MutableHandle<JS::Value>) override;
+ NS_IMETHOD ToString(nsACString& aResult) override;
+
+ private:
+ virtual ~nsScriptErrorWithStack();
+
+ // The "exception" value.
+ JS::Heap<JS::Value> mException;
+ bool mHasException;
+
+ // Complete stackframe where the error happened.
+ // Must be a (possibly wrapped) SavedFrame object.
+ JS::Heap<JSObject*> mStack;
+ // Global object that must be same-compartment with mStack.
+ JS::Heap<JSObject*> mStackGlobal;
+};
+
+// Creates either nsScriptErrorWithStack or nsScriptError,
+// depending on whether |aStack| or |aException| is passed.
+// Additionally when the first (optional) |win| argument is
+// provided this function makes sure that the GlobalWindow
+// isn't already dying to prevent leaks.
+already_AddRefed<nsScriptErrorBase> CreateScriptError(
+ nsGlobalWindowInner* win, JS::Handle<mozilla::Maybe<JS::Value>> aException,
+ JS::Handle<JSObject*> aStack, JS::Handle<JSObject*> aStackGlobal);
+
+#endif /* mozilla_dom_nsScriptError_h */
diff --git a/dom/bindings/nsScriptErrorWithStack.cpp b/dom/bindings/nsScriptErrorWithStack.cpp
new file mode 100644
index 0000000000..8b5f0bda9f
--- /dev/null
+++ b/dom/bindings/nsScriptErrorWithStack.cpp
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/*
+ * nsScriptErrorWithStack implementation.
+ * a main-thread-only, cycle-collected subclass of nsScriptErrorBase
+ * that can store a SavedFrame stack trace object.
+ */
+
+#include "nsScriptError.h"
+#include "MainThreadUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsGlobalWindow.h"
+#include "nsCycleCollectionParticipant.h"
+
+using namespace mozilla::dom;
+
+namespace {
+
+static nsCString FormatStackString(JSContext* cx, JSPrincipals* aPrincipals,
+ JS::Handle<JSObject*> aStack) {
+ JS::Rooted<JSString*> formattedStack(cx);
+ if (!JS::BuildStackString(cx, aPrincipals, aStack, &formattedStack)) {
+ return nsCString();
+ }
+
+ nsAutoJSString stackJSString;
+ if (!stackJSString.init(cx, formattedStack)) {
+ return nsCString();
+ }
+
+ return NS_ConvertUTF16toUTF8(stackJSString.get());
+}
+
+} // namespace
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsScriptErrorWithStack)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsScriptErrorWithStack)
+ tmp->mException.setUndefined();
+ tmp->mStack = nullptr;
+ tmp->mStackGlobal = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsScriptErrorWithStack)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsScriptErrorWithStack)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mException)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mStack)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mStackGlobal)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsScriptErrorWithStack)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsScriptErrorWithStack)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsScriptErrorWithStack)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsIConsoleMessage)
+ NS_INTERFACE_MAP_ENTRY(nsIScriptError)
+NS_INTERFACE_MAP_END
+
+nsScriptErrorWithStack::nsScriptErrorWithStack(
+ JS::Handle<mozilla::Maybe<JS::Value>> aException,
+ JS::Handle<JSObject*> aStack, JS::Handle<JSObject*> aStackGlobal)
+ : mStack(aStack), mStackGlobal(aStackGlobal) {
+ MOZ_ASSERT(NS_IsMainThread(), "You can't use this class on workers.");
+
+ if (aException.isSome()) {
+ mHasException = true;
+ mException.set(*aException);
+ } else {
+ mHasException = false;
+ mException.setUndefined();
+ }
+
+ if (mStack) {
+ MOZ_ASSERT(JS_IsGlobalObject(mStackGlobal));
+ js::AssertSameCompartment(mStack, mStackGlobal);
+ } else {
+ MOZ_ASSERT(!mStackGlobal);
+ }
+
+ mozilla::HoldJSObjects(this);
+}
+
+nsScriptErrorWithStack::~nsScriptErrorWithStack() {
+ mozilla::DropJSObjects(this);
+}
+
+NS_IMETHODIMP
+nsScriptErrorWithStack::GetHasException(bool* aHasException) {
+ *aHasException = mHasException;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorWithStack::GetException(JS::MutableHandle<JS::Value> aException) {
+ aException.set(mException);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorWithStack::GetStack(JS::MutableHandle<JS::Value> aStack) {
+ aStack.setObjectOrNull(mStack);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorWithStack::GetStackGlobal(
+ JS::MutableHandle<JS::Value> aStackGlobal) {
+ aStackGlobal.setObjectOrNull(mStackGlobal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptErrorWithStack::ToString(nsACString& /*UTF8*/ aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCString message;
+ nsresult rv = nsScriptErrorBase::ToString(message);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mStack) {
+ aResult.Assign(message);
+ return NS_OK;
+ }
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mStackGlobal)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JSPrincipals* principals =
+ JS::GetRealmPrincipals(js::GetNonCCWObjectRealm(mStackGlobal));
+
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> stack(cx, mStack);
+ nsCString stackString = FormatStackString(cx, principals, stack);
+ nsCString combined = message + "\n"_ns + stackString;
+ aResult.Assign(combined);
+
+ return NS_OK;
+}
+
+static bool IsObjectGlobalDying(JSObject* aObj) {
+ // CCWs are not associated with a single global
+ if (js::IsCrossCompartmentWrapper(aObj)) {
+ return false;
+ }
+
+ nsGlobalWindowInner* win = xpc::WindowGlobalOrNull(aObj);
+ return win && win->IsDying();
+}
+
+already_AddRefed<nsScriptErrorBase> CreateScriptError(
+ nsGlobalWindowInner* win, JS::Handle<mozilla::Maybe<JS::Value>> aException,
+ JS::Handle<JSObject*> aStack, JS::Handle<JSObject*> aStackGlobal) {
+ bool createWithStack = true;
+ if (aException.isNothing() && !aStack) {
+ // Neither stack nor exception, do not need nsScriptErrorWithStack.
+ createWithStack = false;
+ } else if (win && (win->IsDying() || !win->WindowID())) {
+ // The window is already dying or we don't have a WindowID,
+ // this means nsConsoleService::ClearMessagesForWindowID
+ // would be unable to cleanup this error.
+ createWithStack = false;
+ } else if ((aStackGlobal && IsObjectGlobalDying(aStackGlobal)) ||
+ (aException.isSome() && aException.value().isObject() &&
+ IsObjectGlobalDying(&aException.value().toObject()))) {
+ // Prevent leaks by not creating references to already dying globals.
+ createWithStack = false;
+ }
+
+ if (!createWithStack) {
+ RefPtr<nsScriptErrorBase> error = new nsScriptError();
+ return error.forget();
+ }
+
+ RefPtr<nsScriptErrorBase> error =
+ new nsScriptErrorWithStack(aException, aStack, aStackGlobal);
+ return error.forget();
+}
diff --git a/dom/bindings/parser/README b/dom/bindings/parser/README
new file mode 100644
index 0000000000..94b64b8845
--- /dev/null
+++ b/dom/bindings/parser/README
@@ -0,0 +1 @@
+A WebIDL parser written in Python to be used in Mozilla. \ No newline at end of file
diff --git a/dom/bindings/parser/UPSTREAM b/dom/bindings/parser/UPSTREAM
new file mode 100644
index 0000000000..7ac5899379
--- /dev/null
+++ b/dom/bindings/parser/UPSTREAM
@@ -0,0 +1 @@
+http://dev.w3.org/cvsweb/~checkout~/2006/webapi/WebIDL/Overview.html?rev=1.409;content-type=text%2Fhtml%3b+charset=utf-8 \ No newline at end of file
diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py
new file mode 100644
index 0000000000..f1fafb56c0
--- /dev/null
+++ b/dom/bindings/parser/WebIDL.py
@@ -0,0 +1,9139 @@
+# 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/.
+
+""" A WebIDL parser. """
+
+import copy
+import math
+import os
+import re
+import string
+import traceback
+from collections import OrderedDict, defaultdict
+from itertools import chain
+
+from ply import lex, yacc
+
+# Machinery
+
+
+def parseInt(literal):
+ string = literal
+ sign = 0
+ base = 0
+
+ if string[0] == "-":
+ sign = -1
+ string = string[1:]
+ else:
+ sign = 1
+
+ if string[0] == "0" and len(string) > 1:
+ if string[1] == "x" or string[1] == "X":
+ base = 16
+ string = string[2:]
+ else:
+ base = 8
+ string = string[1:]
+ else:
+ base = 10
+
+ value = int(string, base)
+ return value * sign
+
+
+def enum(*names, **kw):
+ class Foo(object):
+ attrs = OrderedDict()
+
+ def __init__(self, names):
+ for v, k in enumerate(names):
+ self.attrs[k] = v
+
+ def __getattr__(self, attr):
+ if attr in self.attrs:
+ return self.attrs[attr]
+ raise AttributeError
+
+ def __setattr__(self, name, value): # this makes it read-only
+ raise NotImplementedError
+
+ if "base" not in kw:
+ return Foo(names)
+ return Foo(chain(kw["base"].attrs.keys(), names))
+
+
+class WebIDLError(Exception):
+ def __init__(self, message, locations, warning=False):
+ self.message = message
+ self.locations = [str(loc) for loc in locations]
+ self.warning = warning
+
+ def __str__(self):
+ return "%s: %s%s%s" % (
+ self.warning and "warning" or "error",
+ self.message,
+ ", " if len(self.locations) != 0 else "",
+ "\n".join(self.locations),
+ )
+
+
+class Location(object):
+ def __init__(self, lexer, lineno, lexpos, filename):
+ self._line = None
+ self._lineno = lineno
+ self._lexpos = lexpos
+ self._lexdata = lexer.lexdata
+ self._file = filename if filename else "<unknown>"
+
+ def __eq__(self, other):
+ return self._lexpos == other._lexpos and self._file == other._file
+
+ def filename(self):
+ return self._file
+
+ def resolve(self):
+ if self._line:
+ return
+
+ startofline = self._lexdata.rfind("\n", 0, self._lexpos) + 1
+ endofline = self._lexdata.find("\n", self._lexpos, self._lexpos + 80)
+ if endofline != -1:
+ self._line = self._lexdata[startofline:endofline]
+ else:
+ self._line = self._lexdata[startofline:]
+ self._colno = self._lexpos - startofline
+
+ # Our line number seems to point to the start of self._lexdata
+ self._lineno += self._lexdata.count("\n", 0, startofline)
+
+ def get(self):
+ self.resolve()
+ return "%s line %s:%s" % (self._file, self._lineno, self._colno)
+
+ def _pointerline(self):
+ return " " * self._colno + "^"
+
+ def __str__(self):
+ self.resolve()
+ return "%s line %s:%s\n%s\n%s" % (
+ self._file,
+ self._lineno,
+ self._colno,
+ self._line,
+ self._pointerline(),
+ )
+
+
+class BuiltinLocation(object):
+ def __init__(self, text):
+ self.msg = text + "\n"
+
+ def __eq__(self, other):
+ return isinstance(other, BuiltinLocation) and self.msg == other.msg
+
+ def filename(self):
+ return "<builtin>"
+
+ def resolve(self):
+ pass
+
+ def get(self):
+ return self.msg
+
+ def __str__(self):
+ return self.get()
+
+
+# Data Model
+
+
+class IDLObject(object):
+ def __init__(self, location):
+ self.location = location
+ self.userData = dict()
+
+ def filename(self):
+ return self.location.filename()
+
+ def isInterface(self):
+ return False
+
+ def isNamespace(self):
+ return False
+
+ def isInterfaceMixin(self):
+ return False
+
+ def isEnum(self):
+ return False
+
+ def isCallback(self):
+ return False
+
+ def isType(self):
+ return False
+
+ def isDictionary(self):
+ return False
+
+ def isUnion(self):
+ return False
+
+ def isTypedef(self):
+ return False
+
+ def getUserData(self, key, default):
+ return self.userData.get(key, default)
+
+ def setUserData(self, key, value):
+ self.userData[key] = value
+
+ def addExtendedAttributes(self, attrs):
+ assert False # Override me!
+
+ def handleExtendedAttribute(self, attr):
+ assert False # Override me!
+
+ def _getDependentObjects(self):
+ assert False # Override me!
+
+ def getDeps(self, visited=None):
+ """Return a set of files that this object depends on. If any of
+ these files are changed the parser needs to be rerun to regenerate
+ a new IDLObject.
+
+ The visited argument is a set of all the objects already visited.
+ We must test to see if we are in it, and if so, do nothing. This
+ prevents infinite recursion."""
+
+ # NB: We can't use visited=set() above because the default value is
+ # evaluated when the def statement is evaluated, not when the function
+ # is executed, so there would be one set for all invocations.
+ if visited is None:
+ visited = set()
+
+ if self in visited:
+ return set()
+
+ visited.add(self)
+
+ deps = set()
+ if self.filename() != "<builtin>":
+ deps.add(self.filename())
+
+ for d in self._getDependentObjects():
+ deps.update(d.getDeps(visited))
+
+ return deps
+
+
+class IDLScope(IDLObject):
+ def __init__(self, location, parentScope, identifier):
+ IDLObject.__init__(self, location)
+
+ self.parentScope = parentScope
+ if identifier:
+ assert isinstance(identifier, IDLIdentifier)
+ self._name = identifier
+ else:
+ self._name = None
+
+ self._dict = {}
+ self.globalNames = set()
+ # A mapping from global name to the set of global interfaces
+ # that have that global name.
+ self.globalNameMapping = defaultdict(set)
+
+ def __str__(self):
+ return self.QName()
+
+ def QName(self):
+ # It's possible for us to be called before __init__ has been called, for
+ # the IDLObjectWithScope case. In that case, self._name won't be set yet.
+ if hasattr(self, "_name"):
+ name = self._name
+ else:
+ name = None
+ if name:
+ return name.QName() + "::"
+ return "::"
+
+ def ensureUnique(self, identifier, object):
+ """
+ Ensure that there is at most one 'identifier' in scope ('self').
+ Note that object can be None. This occurs if we end up here for an
+ interface type we haven't seen yet.
+ """
+ assert isinstance(identifier, IDLUnresolvedIdentifier)
+ assert not object or isinstance(object, IDLObjectWithIdentifier)
+ assert not object or object.identifier == identifier
+
+ if identifier.name in self._dict:
+ if not object:
+ return
+
+ # ensureUnique twice with the same object is not allowed
+ assert id(object) != id(self._dict[identifier.name])
+
+ replacement = self.resolveIdentifierConflict(
+ self, identifier, self._dict[identifier.name], object
+ )
+ self._dict[identifier.name] = replacement
+ return
+
+ self.addNewIdentifier(identifier, object)
+
+ def addNewIdentifier(self, identifier, object):
+ assert object
+
+ self._dict[identifier.name] = object
+
+ def resolveIdentifierConflict(self, scope, identifier, originalObject, newObject):
+ if (
+ isinstance(originalObject, IDLExternalInterface)
+ and isinstance(newObject, IDLExternalInterface)
+ and originalObject.identifier.name == newObject.identifier.name
+ ):
+ return originalObject
+
+ if isinstance(originalObject, IDLExternalInterface) or isinstance(
+ newObject, IDLExternalInterface
+ ):
+ raise WebIDLError(
+ "Name collision between "
+ "interface declarations for identifier '%s' at '%s' and '%s'"
+ % (identifier.name, originalObject.location, newObject.location),
+ [],
+ )
+
+ if isinstance(originalObject, IDLDictionary) or isinstance(
+ newObject, IDLDictionary
+ ):
+ raise WebIDLError(
+ "Name collision between dictionary declarations for "
+ "identifier '%s'.\n%s\n%s"
+ % (identifier.name, originalObject.location, newObject.location),
+ [],
+ )
+
+ # We do the merging of overloads here as opposed to in IDLInterface
+ # because we need to merge overloads of LegacyFactoryFunctions and we need to
+ # detect conflicts in those across interfaces. See also the comment in
+ # IDLInterface.addExtendedAttributes for "LegacyFactoryFunction".
+ if isinstance(originalObject, IDLMethod) and isinstance(newObject, IDLMethod):
+ return originalObject.addOverload(newObject)
+
+ # Default to throwing, derived classes can override.
+ raise self.createIdentifierConflictError(identifier, originalObject, newObject)
+
+ def createIdentifierConflictError(self, identifier, originalObject, newObject):
+ conflictdesc = "\n\t%s at %s\n\t%s at %s" % (
+ originalObject,
+ originalObject.location,
+ newObject,
+ newObject.location,
+ )
+
+ return WebIDLError(
+ "Multiple unresolvable definitions of identifier '%s' in scope '%s'%s"
+ % (identifier.name, str(self), conflictdesc),
+ [],
+ )
+
+ def _lookupIdentifier(self, identifier):
+ return self._dict[identifier.name]
+
+ def lookupIdentifier(self, identifier):
+ assert isinstance(identifier, IDLIdentifier)
+ assert identifier.scope == self
+ return self._lookupIdentifier(identifier)
+
+ def addIfaceGlobalNames(self, interfaceName, globalNames):
+ """Record the global names (from |globalNames|) that can be used in
+ [Exposed] to expose things in a global named |interfaceName|"""
+ self.globalNames.update(globalNames)
+ for name in globalNames:
+ self.globalNameMapping[name].add(interfaceName)
+
+
+class IDLIdentifier(IDLObject):
+ def __init__(self, location, scope, name):
+ IDLObject.__init__(self, location)
+
+ self.name = name
+ assert isinstance(scope, IDLScope)
+ self.scope = scope
+
+ def __str__(self):
+ return self.QName()
+
+ def QName(self):
+ return self.scope.QName() + self.name
+
+ def __hash__(self):
+ return self.QName().__hash__()
+
+ def __eq__(self, other):
+ return self.QName() == other.QName()
+
+ def object(self):
+ return self.scope.lookupIdentifier(self)
+
+
+class IDLUnresolvedIdentifier(IDLObject):
+ def __init__(
+ self, location, name, allowDoubleUnderscore=False, allowForbidden=False
+ ):
+ IDLObject.__init__(self, location)
+
+ assert len(name) > 0
+
+ if name == "__noSuchMethod__":
+ raise WebIDLError("__noSuchMethod__ is deprecated", [location])
+
+ if name[:2] == "__" and not allowDoubleUnderscore:
+ raise WebIDLError("Identifiers beginning with __ are reserved", [location])
+ if name[0] == "_" and not allowDoubleUnderscore:
+ name = name[1:]
+ if name in ["constructor", "toString"] and not allowForbidden:
+ raise WebIDLError(
+ "Cannot use reserved identifier '%s'" % (name), [location]
+ )
+
+ self.name = name
+
+ def __str__(self):
+ return self.QName()
+
+ def QName(self):
+ return "<unresolved scope>::" + self.name
+
+ def resolve(self, scope, object):
+ assert isinstance(scope, IDLScope)
+ assert not object or isinstance(object, IDLObjectWithIdentifier)
+ assert not object or object.identifier == self
+
+ scope.ensureUnique(self, object)
+
+ identifier = IDLIdentifier(self.location, scope, self.name)
+ if object:
+ object.identifier = identifier
+ return identifier
+
+ def finish(self):
+ assert False # Should replace with a resolved identifier first.
+
+
+class IDLObjectWithIdentifier(IDLObject):
+ def __init__(self, location, parentScope, identifier):
+ IDLObject.__init__(self, location)
+
+ assert isinstance(identifier, IDLUnresolvedIdentifier)
+
+ self.identifier = identifier
+
+ if parentScope:
+ self.resolve(parentScope)
+
+ def resolve(self, parentScope):
+ assert isinstance(parentScope, IDLScope)
+ assert isinstance(self.identifier, IDLUnresolvedIdentifier)
+ self.identifier.resolve(parentScope, self)
+
+
+class IDLObjectWithScope(IDLObjectWithIdentifier, IDLScope):
+ def __init__(self, location, parentScope, identifier):
+ assert isinstance(identifier, IDLUnresolvedIdentifier)
+
+ IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier)
+ IDLScope.__init__(self, location, parentScope, self.identifier)
+
+
+class IDLIdentifierPlaceholder(IDLObjectWithIdentifier):
+ def __init__(self, location, identifier):
+ assert isinstance(identifier, IDLUnresolvedIdentifier)
+ IDLObjectWithIdentifier.__init__(self, location, None, identifier)
+
+ def finish(self, scope):
+ try:
+ scope._lookupIdentifier(self.identifier)
+ except Exception:
+ raise WebIDLError(
+ "Unresolved type '%s'." % self.identifier, [self.location]
+ )
+
+ obj = self.identifier.resolve(scope, None)
+ return scope.lookupIdentifier(obj)
+
+
+class IDLExposureMixins:
+ def __init__(self, location):
+ # _exposureGlobalNames are the global names listed in our [Exposed]
+ # extended attribute. exposureSet is the exposure set as defined in the
+ # Web IDL spec: it contains interface names.
+ self._exposureGlobalNames = set()
+ self.exposureSet = set()
+ self._location = location
+ self._globalScope = None
+
+ def finish(self, scope):
+ assert scope.parentScope is None
+ self._globalScope = scope
+
+ if "*" in self._exposureGlobalNames:
+ self._exposureGlobalNames = scope.globalNames
+ else:
+ # Verify that our [Exposed] value, if any, makes sense.
+ for globalName in self._exposureGlobalNames:
+ if globalName not in scope.globalNames:
+ raise WebIDLError(
+ "Unknown [Exposed] value %s" % globalName, [self._location]
+ )
+
+ # Verify that we are exposed _somwhere_ if we have some place to be
+ # exposed. We don't want to assert that we're definitely exposed
+ # because a lot of our parser tests have small-enough IDL snippets that
+ # they don't include any globals, and we don't really want to go through
+ # and add global interfaces and [Exposed] annotations to all those
+ # tests.
+ if len(scope.globalNames) != 0:
+ if len(self._exposureGlobalNames) == 0 and not self.isPseudoInterface():
+ raise WebIDLError(
+ (
+ "'%s' is not exposed anywhere even though we have "
+ "globals to be exposed to"
+ )
+ % self,
+ [self.location],
+ )
+
+ globalNameSetToExposureSet(scope, self._exposureGlobalNames, self.exposureSet)
+
+ def isExposedInWindow(self):
+ return "Window" in self.exposureSet
+
+ def isExposedInAnyWorker(self):
+ return len(self.getWorkerExposureSet()) > 0
+
+ def isExposedInWorkerDebugger(self):
+ return len(self.getWorkerDebuggerExposureSet()) > 0
+
+ def isExposedInAnyWorklet(self):
+ return len(self.getWorkletExposureSet()) > 0
+
+ def isExposedInSomeButNotAllWorkers(self):
+ """
+ Returns true if the Exposed extended attribute for this interface
+ exposes it in some worker globals but not others. The return value does
+ not depend on whether the interface is exposed in Window or System
+ globals.
+ """
+ if not self.isExposedInAnyWorker():
+ return False
+ workerScopes = self.parentScope.globalNameMapping["Worker"]
+ return len(workerScopes.difference(self.exposureSet)) > 0
+
+ def isExposedInShadowRealms(self):
+ return "ShadowRealmGlobalScope" in self.exposureSet
+
+ def getWorkerExposureSet(self):
+ workerScopes = self._globalScope.globalNameMapping["Worker"]
+ return workerScopes.intersection(self.exposureSet)
+
+ def getWorkletExposureSet(self):
+ workletScopes = self._globalScope.globalNameMapping["Worklet"]
+ return workletScopes.intersection(self.exposureSet)
+
+ def getWorkerDebuggerExposureSet(self):
+ workerDebuggerScopes = self._globalScope.globalNameMapping["WorkerDebugger"]
+ return workerDebuggerScopes.intersection(self.exposureSet)
+
+
+class IDLExternalInterface(IDLObjectWithIdentifier):
+ def __init__(self, location, parentScope, identifier):
+ assert isinstance(identifier, IDLUnresolvedIdentifier)
+ assert isinstance(parentScope, IDLScope)
+ self.parent = None
+ IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier)
+ IDLObjectWithIdentifier.resolve(self, parentScope)
+
+ def finish(self, scope):
+ pass
+
+ def validate(self):
+ pass
+
+ def isIteratorInterface(self):
+ return False
+
+ def isAsyncIteratorInterface(self):
+ return False
+
+ def isExternal(self):
+ return True
+
+ def isInterface(self):
+ return True
+
+ def addExtendedAttributes(self, attrs):
+ if len(attrs) != 0:
+ raise WebIDLError(
+ "There are no extended attributes that are "
+ "allowed on external interfaces",
+ [attrs[0].location, self.location],
+ )
+
+ def resolve(self, parentScope):
+ pass
+
+ def getJSImplementation(self):
+ return None
+
+ def isJSImplemented(self):
+ return False
+
+ def hasProbablyShortLivingWrapper(self):
+ return False
+
+ def _getDependentObjects(self):
+ return set()
+
+
+class IDLPartialDictionary(IDLObject):
+ def __init__(self, location, name, members, nonPartialDictionary):
+ assert isinstance(name, IDLUnresolvedIdentifier)
+
+ IDLObject.__init__(self, location)
+ self.identifier = name
+ self.members = members
+ self._nonPartialDictionary = nonPartialDictionary
+ self._finished = False
+ nonPartialDictionary.addPartialDictionary(self)
+
+ def addExtendedAttributes(self, attrs):
+ pass
+
+ def finish(self, scope):
+ if self._finished:
+ return
+ self._finished = True
+
+ # Need to make sure our non-partial dictionary gets
+ # finished so it can report cases when we only have partial
+ # dictionaries.
+ self._nonPartialDictionary.finish(scope)
+
+ def validate(self):
+ pass
+
+
+class IDLPartialInterfaceOrNamespace(IDLObject):
+ def __init__(self, location, name, members, nonPartialInterfaceOrNamespace):
+ assert isinstance(name, IDLUnresolvedIdentifier)
+
+ IDLObject.__init__(self, location)
+ self.identifier = name
+ self.members = members
+ # propagatedExtendedAttrs are the ones that should get
+ # propagated to our non-partial interface.
+ self.propagatedExtendedAttrs = []
+ self._haveSecureContextExtendedAttribute = False
+ self._nonPartialInterfaceOrNamespace = nonPartialInterfaceOrNamespace
+ self._finished = False
+ nonPartialInterfaceOrNamespace.addPartial(self)
+
+ def addExtendedAttributes(self, attrs):
+ for attr in attrs:
+ identifier = attr.identifier()
+
+ if identifier == "LegacyFactoryFunction":
+ self.propagatedExtendedAttrs.append(attr)
+ elif identifier == "SecureContext":
+ self._haveSecureContextExtendedAttribute = True
+ # This gets propagated to all our members.
+ for member in self.members:
+ if member.getExtendedAttribute("SecureContext"):
+ raise WebIDLError(
+ "[SecureContext] specified on both a "
+ "partial interface member and on the "
+ "partial interface itself",
+ [member.location, attr.location],
+ )
+ member.addExtendedAttributes([attr])
+ elif identifier == "Exposed":
+ # This just gets propagated to all our members.
+ for member in self.members:
+ if len(member._exposureGlobalNames) != 0:
+ raise WebIDLError(
+ "[Exposed] specified on both a "
+ "partial interface member and on the "
+ "partial interface itself",
+ [member.location, attr.location],
+ )
+ member.addExtendedAttributes([attr])
+ else:
+ raise WebIDLError(
+ "Unknown extended attribute %s on partial "
+ "interface" % identifier,
+ [attr.location],
+ )
+
+ def finish(self, scope):
+ if self._finished:
+ return
+ self._finished = True
+ if (
+ not self._haveSecureContextExtendedAttribute
+ and self._nonPartialInterfaceOrNamespace.getExtendedAttribute(
+ "SecureContext"
+ )
+ ):
+ # This gets propagated to all our members.
+ for member in self.members:
+ if member.getExtendedAttribute("SecureContext"):
+ raise WebIDLError(
+ "[SecureContext] specified on both a "
+ "partial interface member and on the "
+ "non-partial interface",
+ [
+ member.location,
+ self._nonPartialInterfaceOrNamespace.location,
+ ],
+ )
+ member.addExtendedAttributes(
+ [
+ IDLExtendedAttribute(
+ self._nonPartialInterfaceOrNamespace.location,
+ ("SecureContext",),
+ )
+ ]
+ )
+ # Need to make sure our non-partial interface or namespace gets
+ # finished so it can report cases when we only have partial
+ # interfaces/namespaces.
+ self._nonPartialInterfaceOrNamespace.finish(scope)
+
+ def validate(self):
+ pass
+
+
+def convertExposedAttrToGlobalNameSet(exposedAttr, targetSet):
+ assert len(targetSet) == 0
+ if exposedAttr.hasValue():
+ targetSet.add(exposedAttr.value())
+ else:
+ assert exposedAttr.hasArgs()
+ targetSet.update(exposedAttr.args())
+
+
+def globalNameSetToExposureSet(globalScope, nameSet, exposureSet):
+ for name in nameSet:
+ exposureSet.update(globalScope.globalNameMapping[name])
+
+
+# Because WebIDL allows static and regular operations with the same identifier
+# we use a special class to be able to store them both in the scope for the
+# same identifier.
+class IDLOperations:
+ def __init__(self, static=None, regular=None):
+ self.static = static
+ self.regular = regular
+
+
+class IDLInterfaceOrInterfaceMixinOrNamespace(IDLObjectWithScope, IDLExposureMixins):
+ def __init__(self, location, parentScope, name):
+ assert isinstance(parentScope, IDLScope)
+ assert isinstance(name, IDLUnresolvedIdentifier)
+
+ self._finished = False
+ self.members = []
+ self._partials = []
+ self._extendedAttrDict = {}
+ self._isKnownNonPartial = False
+
+ IDLObjectWithScope.__init__(self, location, parentScope, name)
+ IDLExposureMixins.__init__(self, location)
+
+ def finish(self, scope):
+ if not self._isKnownNonPartial:
+ raise WebIDLError(
+ "%s does not have a non-partial declaration" % str(self),
+ [self.location],
+ )
+
+ IDLExposureMixins.finish(self, scope)
+
+ # Now go ahead and merge in our partials.
+ for partial in self._partials:
+ partial.finish(scope)
+ self.addExtendedAttributes(partial.propagatedExtendedAttrs)
+ self.members.extend(partial.members)
+
+ def addNewIdentifier(self, identifier, object):
+ if isinstance(object, IDLMethod):
+ if object.isStatic():
+ object = IDLOperations(static=object)
+ else:
+ object = IDLOperations(regular=object)
+
+ IDLScope.addNewIdentifier(self, identifier, object)
+
+ def resolveIdentifierConflict(self, scope, identifier, originalObject, newObject):
+ assert isinstance(scope, IDLScope)
+ assert isinstance(newObject, IDLInterfaceMember)
+
+ # The identifier of a regular operation or static operation must not be
+ # the same as the identifier of a constant or attribute.
+ if isinstance(newObject, IDLMethod) != isinstance(
+ originalObject, IDLOperations
+ ):
+ if isinstance(originalObject, IDLOperations):
+ if originalObject.regular is not None:
+ originalObject = originalObject.regular
+ else:
+ assert originalObject.static is not None
+ originalObject = originalObject.static
+
+ raise self.createIdentifierConflictError(
+ identifier, originalObject, newObject
+ )
+
+ if isinstance(newObject, IDLMethod):
+ originalOperations = originalObject
+ if newObject.isStatic():
+ if originalOperations.static is None:
+ originalOperations.static = newObject
+ return originalOperations
+
+ originalObject = originalOperations.static
+ else:
+ if originalOperations.regular is None:
+ originalOperations.regular = newObject
+ return originalOperations
+
+ originalObject = originalOperations.regular
+
+ assert isinstance(originalObject, IDLMethod)
+ else:
+ assert isinstance(originalObject, IDLInterfaceMember)
+
+ retval = IDLScope.resolveIdentifierConflict(
+ self, scope, identifier, originalObject, newObject
+ )
+
+ if isinstance(newObject, IDLMethod):
+ if newObject.isStatic():
+ originalOperations.static = retval
+ else:
+ originalOperations.regular = retval
+
+ retval = originalOperations
+
+ # Might be a ctor, which isn't in self.members
+ if newObject in self.members:
+ self.members.remove(newObject)
+ return retval
+
+ def typeName(self):
+ if self.isInterface():
+ return "interface"
+ if self.isNamespace():
+ return "namespace"
+ assert self.isInterfaceMixin()
+ return "interface mixin"
+
+ def getExtendedAttribute(self, name):
+ return self._extendedAttrDict.get(name, None)
+
+ def setNonPartial(self, location, members):
+ if self._isKnownNonPartial:
+ raise WebIDLError(
+ "Two non-partial definitions for the " "same %s" % self.typeName(),
+ [location, self.location],
+ )
+ self._isKnownNonPartial = True
+ # Now make it look like we were parsed at this new location, since
+ # that's the place where the interface is "really" defined
+ self.location = location
+ # Put the new members at the beginning
+ self.members = members + self.members
+
+ def addPartial(self, partial):
+ assert self.identifier.name == partial.identifier.name
+ self._partials.append(partial)
+
+ def getPartials(self):
+ # Don't let people mutate our guts.
+ return list(self._partials)
+
+ def finishMembers(self, scope):
+ # Assuming we've merged in our partials, set the _exposureGlobalNames on
+ # any members that don't have it set yet. Note that any partial
+ # interfaces that had [Exposed] set have already set up
+ # _exposureGlobalNames on all the members coming from them, so this is
+ # just implementing the "members default to interface or interface mixin
+ # that defined them" and "partial interfaces or interface mixins default
+ # to interface or interface mixin they're a partial for" rules from the
+ # spec.
+ for m in self.members:
+ # If m, or the partial m came from, had [Exposed]
+ # specified, it already has a nonempty exposure global names set.
+ if len(m._exposureGlobalNames) == 0:
+ m._exposureGlobalNames.update(self._exposureGlobalNames)
+ if m.isAttr() and m.stringifier:
+ m.expand(self.members)
+
+ # resolve() will modify self.members, so we need to iterate
+ # over a copy of the member list here.
+ for member in list(self.members):
+ member.resolve(self)
+
+ for member in self.members:
+ member.finish(scope)
+
+ # Now that we've finished our members, which has updated their exposure
+ # sets, make sure they aren't exposed in places where we are not.
+ for member in self.members:
+ if not member.exposureSet.issubset(self.exposureSet):
+ raise WebIDLError(
+ "Interface or interface mixin member has "
+ "larger exposure set than its container",
+ [member.location, self.location],
+ )
+
+ def isExternal(self):
+ return False
+
+
+class IDLInterfaceMixin(IDLInterfaceOrInterfaceMixinOrNamespace):
+ def __init__(self, location, parentScope, name, members, isKnownNonPartial):
+ self.actualExposureGlobalNames = set()
+
+ assert isKnownNonPartial or len(members) == 0
+ IDLInterfaceOrInterfaceMixinOrNamespace.__init__(
+ self, location, parentScope, name
+ )
+
+ if isKnownNonPartial:
+ self.setNonPartial(location, members)
+
+ def __str__(self):
+ return "Interface mixin '%s'" % self.identifier.name
+
+ def isInterfaceMixin(self):
+ return True
+
+ def finish(self, scope):
+ if self._finished:
+ return
+ self._finished = True
+
+ # Expose to the globals of interfaces that includes this mixin if this
+ # mixin has no explicit [Exposed] so that its members can be exposed
+ # based on the base interface exposure set.
+ #
+ # Make sure this is done before IDLExposureMixins.finish call, since
+ # that converts our set of exposure global names to an actual exposure
+ # set.
+ hasImplicitExposure = len(self._exposureGlobalNames) == 0
+ if hasImplicitExposure:
+ self._exposureGlobalNames.update(self.actualExposureGlobalNames)
+
+ IDLInterfaceOrInterfaceMixinOrNamespace.finish(self, scope)
+
+ self.finishMembers(scope)
+
+ def validate(self):
+ for member in self.members:
+
+ if member.isAttr():
+ if member.inherit:
+ raise WebIDLError(
+ "Interface mixin member cannot include "
+ "an inherited attribute",
+ [member.location, self.location],
+ )
+ if member.isStatic():
+ raise WebIDLError(
+ "Interface mixin member cannot include " "a static member",
+ [member.location, self.location],
+ )
+
+ if member.isMethod():
+ if member.isStatic():
+ raise WebIDLError(
+ "Interface mixin member cannot include " "a static operation",
+ [member.location, self.location],
+ )
+ if (
+ member.isGetter()
+ or member.isSetter()
+ or member.isDeleter()
+ or member.isLegacycaller()
+ ):
+ raise WebIDLError(
+ "Interface mixin member cannot include a " "special operation",
+ [member.location, self.location],
+ )
+
+ def addExtendedAttributes(self, attrs):
+ for attr in attrs:
+ identifier = attr.identifier()
+
+ if identifier == "SecureContext":
+ if not attr.noArguments():
+ raise WebIDLError(
+ "[%s] must take no arguments" % identifier, [attr.location]
+ )
+ # This gets propagated to all our members.
+ for member in self.members:
+ if member.getExtendedAttribute("SecureContext"):
+ raise WebIDLError(
+ "[SecureContext] specified on both "
+ "an interface mixin member and on"
+ "the interface mixin itself",
+ [member.location, attr.location],
+ )
+ member.addExtendedAttributes([attr])
+ elif identifier == "Exposed":
+ convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames)
+ else:
+ raise WebIDLError(
+ "Unknown extended attribute %s on interface" % identifier,
+ [attr.location],
+ )
+
+ attrlist = attr.listValue()
+ self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True
+
+ def _getDependentObjects(self):
+ return set(self.members)
+
+
+class IDLInterfaceOrNamespace(IDLInterfaceOrInterfaceMixinOrNamespace):
+ def __init__(self, location, parentScope, name, parent, members, isKnownNonPartial):
+ assert isKnownNonPartial or not parent
+ assert isKnownNonPartial or len(members) == 0
+
+ self.parent = None
+ self._callback = False
+ self.maplikeOrSetlikeOrIterable = None
+ # namedConstructors needs deterministic ordering because bindings code
+ # outputs the constructs in the order that namedConstructors enumerates
+ # them.
+ self.legacyFactoryFunctions = list()
+ self.legacyWindowAliases = []
+ self.includedMixins = set()
+ # self.interfacesBasedOnSelf is the set of interfaces that inherit from
+ # self, including self itself.
+ # Used for distinguishability checking.
+ self.interfacesBasedOnSelf = set([self])
+ self._hasChildInterfaces = False
+ self._isOnGlobalProtoChain = False
+ # Pseudo interfaces aren't exposed anywhere, and so shouldn't issue warnings
+ self._isPseudo = False
+
+ # Tracking of the number of reserved slots we need for our
+ # members and those of ancestor interfaces.
+ self.totalMembersInSlots = 0
+ # Tracking of the number of own own members we have in slots
+ self._ownMembersInSlots = 0
+ # If this is an iterator interface, we need to know what iterable
+ # interface we're iterating for in order to get its nativeType.
+ self.iterableInterface = None
+ self.asyncIterableInterface = None
+ # True if we have cross-origin members.
+ self.hasCrossOriginMembers = False
+ # True if some descendant (including ourselves) has cross-origin members
+ self.hasDescendantWithCrossOriginMembers = False
+
+ IDLInterfaceOrInterfaceMixinOrNamespace.__init__(
+ self, location, parentScope, name
+ )
+
+ if isKnownNonPartial:
+ self.setNonPartial(location, parent, members)
+
+ def ctor(self):
+ identifier = IDLUnresolvedIdentifier(
+ self.location, "constructor", allowForbidden=True
+ )
+ try:
+ return self._lookupIdentifier(identifier).static
+ except Exception:
+ return None
+
+ def isIterable(self):
+ return (
+ self.maplikeOrSetlikeOrIterable
+ and self.maplikeOrSetlikeOrIterable.isIterable()
+ )
+
+ def isAsyncIterable(self):
+ return (
+ self.maplikeOrSetlikeOrIterable
+ and self.maplikeOrSetlikeOrIterable.isAsyncIterable()
+ )
+
+ def isIteratorInterface(self):
+ return self.iterableInterface is not None
+
+ def isAsyncIteratorInterface(self):
+ return self.asyncIterableInterface is not None
+
+ def getClassName(self):
+ return self.identifier.name
+
+ def finish(self, scope):
+ if self._finished:
+ return
+
+ self._finished = True
+
+ IDLInterfaceOrInterfaceMixinOrNamespace.finish(self, scope)
+
+ if len(self.legacyWindowAliases) > 0:
+ if not self.hasInterfaceObject():
+ raise WebIDLError(
+ "Interface %s unexpectedly has [LegacyWindowAlias] "
+ "and [LegacyNoInterfaceObject] together" % self.identifier.name,
+ [self.location],
+ )
+ if not self.isExposedInWindow():
+ raise WebIDLError(
+ "Interface %s has [LegacyWindowAlias] "
+ "but not exposed in Window" % self.identifier.name,
+ [self.location],
+ )
+
+ # Generate maplike/setlike interface members. Since generated members
+ # need to be treated like regular interface members, do this before
+ # things like exposure setting.
+ for member in self.members:
+ if member.isMaplikeOrSetlikeOrIterable():
+ if self.isJSImplemented():
+ raise WebIDLError(
+ "%s declaration used on "
+ "interface that is implemented in JS"
+ % (member.maplikeOrSetlikeOrIterableType),
+ [member.location],
+ )
+ if member.valueType.isObservableArray() or (
+ member.hasKeyType() and member.keyType.isObservableArray()
+ ):
+ raise WebIDLError(
+ "%s declaration uses ObservableArray as value or key type"
+ % (member.maplikeOrSetlikeOrIterableType),
+ [member.location],
+ )
+ # Check that we only have one interface declaration (currently
+ # there can only be one maplike/setlike declaration per
+ # interface)
+ if self.maplikeOrSetlikeOrIterable:
+ raise WebIDLError(
+ "%s declaration used on "
+ "interface that already has %s "
+ "declaration"
+ % (
+ member.maplikeOrSetlikeOrIterableType,
+ self.maplikeOrSetlikeOrIterable.maplikeOrSetlikeOrIterableType,
+ ),
+ [self.maplikeOrSetlikeOrIterable.location, member.location],
+ )
+ self.maplikeOrSetlikeOrIterable = member
+ # If we've got a maplike or setlike declaration, we'll be building all of
+ # our required methods in Codegen. Generate members now.
+ self.maplikeOrSetlikeOrIterable.expand(self.members)
+
+ assert not self.parent or isinstance(self.parent, IDLIdentifierPlaceholder)
+ parent = self.parent.finish(scope) if self.parent else None
+ if parent and isinstance(parent, IDLExternalInterface):
+ raise WebIDLError(
+ "%s inherits from %s which does not have "
+ "a definition" % (self.identifier.name, self.parent.identifier.name),
+ [self.location],
+ )
+ if parent and not isinstance(parent, IDLInterface):
+ raise WebIDLError(
+ "%s inherits from %s which is not an interface "
+ % (self.identifier.name, self.parent.identifier.name),
+ [self.location, parent.location],
+ )
+
+ self.parent = parent
+
+ assert iter(self.members)
+
+ if self.isNamespace():
+ assert not self.parent
+ for m in self.members:
+ if m.isAttr() or m.isMethod():
+ if m.isStatic():
+ raise WebIDLError(
+ "Don't mark things explicitly static " "in namespaces",
+ [self.location, m.location],
+ )
+ # Just mark all our methods/attributes as static. The other
+ # option is to duplicate the relevant InterfaceMembers
+ # production bits but modified to produce static stuff to
+ # start with, but that sounds annoying.
+ m.forceStatic()
+
+ if self.parent:
+ self.parent.finish(scope)
+ self.parent._hasChildInterfaces = True
+
+ self.totalMembersInSlots = self.parent.totalMembersInSlots
+
+ # Interfaces with [Global] must not have anything inherit from them
+ if self.parent.getExtendedAttribute("Global"):
+ # Note: This is not a self.parent.isOnGlobalProtoChain() check
+ # because ancestors of a [Global] interface can have other
+ # descendants.
+ raise WebIDLError(
+ "[Global] interface has another interface " "inheriting from it",
+ [self.location, self.parent.location],
+ )
+
+ # Make sure that we're not exposed in places where our parent is not
+ if not self.exposureSet.issubset(self.parent.exposureSet):
+ raise WebIDLError(
+ "Interface %s is exposed in globals where its "
+ "parent interface %s is not exposed."
+ % (self.identifier.name, self.parent.identifier.name),
+ [self.location, self.parent.location],
+ )
+
+ # Callbacks must not inherit from non-callbacks.
+ # XXXbz Can non-callbacks inherit from callbacks? Spec issue pending.
+ if self.isCallback():
+ if not self.parent.isCallback():
+ raise WebIDLError(
+ "Callback interface %s inheriting from "
+ "non-callback interface %s"
+ % (self.identifier.name, self.parent.identifier.name),
+ [self.location, self.parent.location],
+ )
+ elif self.parent.isCallback():
+ raise WebIDLError(
+ "Non-callback interface %s inheriting from "
+ "callback interface %s"
+ % (self.identifier.name, self.parent.identifier.name),
+ [self.location, self.parent.location],
+ )
+
+ # Interfaces which have interface objects can't inherit
+ # from [LegacyNoInterfaceObject] interfaces.
+ if self.parent.getExtendedAttribute(
+ "LegacyNoInterfaceObject"
+ ) and not self.getExtendedAttribute("LegacyNoInterfaceObject"):
+ raise WebIDLError(
+ "Interface %s does not have "
+ "[LegacyNoInterfaceObject] but inherits from "
+ "interface %s which does"
+ % (self.identifier.name, self.parent.identifier.name),
+ [self.location, self.parent.location],
+ )
+
+ # Interfaces that are not [SecureContext] can't inherit
+ # from [SecureContext] interfaces.
+ if self.parent.getExtendedAttribute(
+ "SecureContext"
+ ) and not self.getExtendedAttribute("SecureContext"):
+ raise WebIDLError(
+ "Interface %s does not have "
+ "[SecureContext] but inherits from "
+ "interface %s which does"
+ % (self.identifier.name, self.parent.identifier.name),
+ [self.location, self.parent.location],
+ )
+
+ for mixin in self.includedMixins:
+ mixin.finish(scope)
+
+ cycleInGraph = self.findInterfaceLoopPoint(self)
+ if cycleInGraph:
+ raise WebIDLError(
+ "Interface %s has itself as ancestor" % self.identifier.name,
+ [self.location, cycleInGraph.location],
+ )
+
+ self.finishMembers(scope)
+
+ ctor = self.ctor()
+ if ctor is not None:
+ if not self.hasInterfaceObject():
+ raise WebIDLError(
+ "Can't have both a constructor and [LegacyNoInterfaceObject]",
+ [self.location, ctor.location],
+ )
+
+ if self.globalNames:
+ raise WebIDLError(
+ "Can't have both a constructor and [Global]",
+ [self.location, ctor.location],
+ )
+
+ assert ctor._exposureGlobalNames == self._exposureGlobalNames
+ ctor._exposureGlobalNames.update(self._exposureGlobalNames)
+ # Remove the constructor operation from our member list so
+ # it doesn't get in the way later.
+ self.members.remove(ctor)
+
+ for ctor in self.legacyFactoryFunctions:
+ if self.globalNames:
+ raise WebIDLError(
+ "Can't have both a legacy factory function and [Global]",
+ [self.location, ctor.location],
+ )
+ assert len(ctor._exposureGlobalNames) == 0
+ ctor._exposureGlobalNames.update(self._exposureGlobalNames)
+ ctor.finish(scope)
+
+ # Make a copy of our member list, so things that implement us
+ # can get those without all the stuff we implement ourselves
+ # admixed.
+ self.originalMembers = list(self.members)
+
+ for mixin in sorted(self.includedMixins, key=lambda x: x.identifier.name):
+ for mixinMember in mixin.members:
+ for member in self.members:
+ if mixinMember.identifier.name == member.identifier.name and (
+ not mixinMember.isMethod()
+ or not member.isMethod()
+ or mixinMember.isStatic() == member.isStatic()
+ ):
+ raise WebIDLError(
+ "Multiple definitions of %s on %s coming from 'includes' statements"
+ % (member.identifier.name, self),
+ [mixinMember.location, member.location],
+ )
+ self.members.extend(mixin.members)
+
+ for ancestor in self.getInheritedInterfaces():
+ ancestor.interfacesBasedOnSelf.add(self)
+ if (
+ ancestor.maplikeOrSetlikeOrIterable is not None
+ and self.maplikeOrSetlikeOrIterable is not None
+ ):
+ raise WebIDLError(
+ "Cannot have maplike/setlike on %s that "
+ "inherits %s, which is already "
+ "maplike/setlike"
+ % (self.identifier.name, ancestor.identifier.name),
+ [
+ self.maplikeOrSetlikeOrIterable.location,
+ ancestor.maplikeOrSetlikeOrIterable.location,
+ ],
+ )
+
+ # Deal with interfaces marked [LegacyUnforgeable], now that we have our full
+ # member list, except unforgeables pulled in from parents. We want to
+ # do this before we set "originatingInterface" on our unforgeable
+ # members.
+ if self.getExtendedAttribute("LegacyUnforgeable"):
+ # Check that the interface already has all the things the
+ # spec would otherwise require us to synthesize and is
+ # missing the ones we plan to synthesize.
+ if not any(m.isMethod() and m.isStringifier() for m in self.members):
+ raise WebIDLError(
+ "LegacyUnforgeable interface %s does not have a "
+ "stringifier" % self.identifier.name,
+ [self.location],
+ )
+
+ for m in self.members:
+ if m.identifier.name == "toJSON":
+ raise WebIDLError(
+ "LegacyUnforgeable interface %s has a "
+ "toJSON so we won't be able to add "
+ "one ourselves" % self.identifier.name,
+ [self.location, m.location],
+ )
+
+ if m.identifier.name == "valueOf" and not m.isStatic():
+ raise WebIDLError(
+ "LegacyUnforgeable interface %s has a valueOf "
+ "member so we won't be able to add one "
+ "ourselves" % self.identifier.name,
+ [self.location, m.location],
+ )
+
+ for member in self.members:
+ if (
+ (member.isAttr() or member.isMethod())
+ and member.isLegacyUnforgeable()
+ and not hasattr(member, "originatingInterface")
+ ):
+ member.originatingInterface = self
+
+ for member in self.members:
+ if (
+ member.isMethod() and member.getExtendedAttribute("CrossOriginCallable")
+ ) or (
+ member.isAttr()
+ and (
+ member.getExtendedAttribute("CrossOriginReadable")
+ or member.getExtendedAttribute("CrossOriginWritable")
+ )
+ ):
+ self.hasCrossOriginMembers = True
+ break
+
+ if self.hasCrossOriginMembers:
+ parent = self
+ while parent:
+ parent.hasDescendantWithCrossOriginMembers = True
+ parent = parent.parent
+
+ # Compute slot indices for our members before we pull in unforgeable
+ # members from our parent. Also, maplike/setlike declarations get a
+ # slot to hold their backing object.
+ for member in self.members:
+ if (
+ member.isAttr()
+ and (
+ member.getExtendedAttribute("StoreInSlot")
+ or member.getExtendedAttribute("Cached")
+ or member.type.isObservableArray()
+ )
+ ) or member.isMaplikeOrSetlike():
+ if self.isJSImplemented() and not member.isMaplikeOrSetlike():
+ raise WebIDLError(
+ "Interface %s is JS-implemented and we "
+ "don't support [Cached] or [StoreInSlot] or ObservableArray "
+ "on JS-implemented interfaces" % self.identifier.name,
+ [self.location, member.location],
+ )
+ if member.slotIndices is None:
+ member.slotIndices = dict()
+ member.slotIndices[self.identifier.name] = self.totalMembersInSlots
+ self.totalMembersInSlots += 1
+ if member.getExtendedAttribute("StoreInSlot"):
+ self._ownMembersInSlots += 1
+
+ if self.parent:
+ # Make sure we don't shadow any of the [LegacyUnforgeable] attributes on our
+ # ancestor interfaces. We don't have to worry about mixins here, because
+ # those have already been imported into the relevant .members lists. And
+ # we don't have to worry about anything other than our parent, because it
+ # has already imported its ancestors' unforgeable attributes into its
+ # member list.
+ for unforgeableMember in (
+ member
+ for member in self.parent.members
+ if (member.isAttr() or member.isMethod())
+ and member.isLegacyUnforgeable()
+ ):
+ shadows = [
+ m
+ for m in self.members
+ if (m.isAttr() or m.isMethod())
+ and not m.isStatic()
+ and m.identifier.name == unforgeableMember.identifier.name
+ ]
+ if len(shadows) != 0:
+ locs = [unforgeableMember.location] + [s.location for s in shadows]
+ raise WebIDLError(
+ "Interface %s shadows [LegacyUnforgeable] "
+ "members of %s"
+ % (self.identifier.name, ancestor.identifier.name),
+ locs,
+ )
+ # And now just stick it in our members, since we won't be
+ # inheriting this down the proto chain. If we really cared we
+ # could try to do something where we set up the unforgeable
+ # attributes/methods of ancestor interfaces, with their
+ # corresponding getters, on our interface, but that gets pretty
+ # complicated and seems unnecessary.
+ self.members.append(unforgeableMember)
+
+ # At this point, we have all of our members. If the current interface
+ # uses maplike/setlike, check for collisions anywhere in the current
+ # interface or higher in the inheritance chain.
+ if self.maplikeOrSetlikeOrIterable:
+ testInterface = self
+ isAncestor = False
+ while testInterface:
+ self.maplikeOrSetlikeOrIterable.checkCollisions(
+ testInterface.members, isAncestor
+ )
+ isAncestor = True
+ testInterface = testInterface.parent
+
+ # Ensure that there's at most one of each {named,indexed}
+ # {getter,setter,deleter}, at most one stringifier,
+ # and at most one legacycaller. Note that this last is not
+ # quite per spec, but in practice no one overloads
+ # legacycallers. Also note that in practice we disallow
+ # indexed deleters, but it simplifies some other code to
+ # treat deleter analogously to getter/setter by
+ # prefixing it with "named".
+ specialMembersSeen = {}
+ for member in self.members:
+ if not member.isMethod():
+ continue
+
+ if member.isGetter():
+ memberType = "getters"
+ elif member.isSetter():
+ memberType = "setters"
+ elif member.isDeleter():
+ memberType = "deleters"
+ elif member.isStringifier():
+ memberType = "stringifiers"
+ elif member.isLegacycaller():
+ memberType = "legacycallers"
+ else:
+ continue
+
+ if memberType != "stringifiers" and memberType != "legacycallers":
+ if member.isNamed():
+ memberType = "named " + memberType
+ else:
+ assert member.isIndexed()
+ memberType = "indexed " + memberType
+
+ if memberType in specialMembersSeen:
+ raise WebIDLError(
+ "Multiple " + memberType + " on %s" % (self),
+ [
+ self.location,
+ specialMembersSeen[memberType].location,
+ member.location,
+ ],
+ )
+
+ specialMembersSeen[memberType] = member
+
+ if self.getExtendedAttribute("LegacyUnenumerableNamedProperties"):
+ # Check that we have a named getter.
+ if "named getters" not in specialMembersSeen:
+ raise WebIDLError(
+ "Interface with [LegacyUnenumerableNamedProperties] does "
+ "not have a named getter",
+ [self.location],
+ )
+ ancestor = self.parent
+ while ancestor:
+ if ancestor.getExtendedAttribute("LegacyUnenumerableNamedProperties"):
+ raise WebIDLError(
+ "Interface with [LegacyUnenumerableNamedProperties] "
+ "inherits from another interface with "
+ "[LegacyUnenumerableNamedProperties]",
+ [self.location, ancestor.location],
+ )
+ ancestor = ancestor.parent
+
+ if self._isOnGlobalProtoChain:
+ # Make sure we have no named setters or deleters
+ for memberType in ["setter", "deleter"]:
+ memberId = "named " + memberType + "s"
+ if memberId in specialMembersSeen:
+ raise WebIDLError(
+ "Interface with [Global] has a named %s" % memberType,
+ [self.location, specialMembersSeen[memberId].location],
+ )
+ # Make sure we're not [LegacyOverrideBuiltIns]
+ if self.getExtendedAttribute("LegacyOverrideBuiltIns"):
+ raise WebIDLError(
+ "Interface with [Global] also has " "[LegacyOverrideBuiltIns]",
+ [self.location],
+ )
+ # Mark all of our ancestors as being on the global's proto chain too
+ parent = self.parent
+ while parent:
+ # Must not inherit from an interface with [LegacyOverrideBuiltIns]
+ if parent.getExtendedAttribute("LegacyOverrideBuiltIns"):
+ raise WebIDLError(
+ "Interface with [Global] inherits from "
+ "interface with [LegacyOverrideBuiltIns]",
+ [self.location, parent.location],
+ )
+ parent._isOnGlobalProtoChain = True
+ parent = parent.parent
+
+ def validate(self):
+ def checkDuplicateNames(member, name, attributeName):
+ for m in self.members:
+ if m.identifier.name == name:
+ raise WebIDLError(
+ "[%s=%s] has same name as interface member"
+ % (attributeName, name),
+ [member.location, m.location],
+ )
+ if m.isMethod() and m != member and name in m.aliases:
+ raise WebIDLError(
+ "conflicting [%s=%s] definitions" % (attributeName, name),
+ [member.location, m.location],
+ )
+ if m.isAttr() and m != member and name in m.bindingAliases:
+ raise WebIDLError(
+ "conflicting [%s=%s] definitions" % (attributeName, name),
+ [member.location, m.location],
+ )
+
+ # We also don't support inheriting from unforgeable interfaces.
+ if self.getExtendedAttribute("LegacyUnforgeable") and self.hasChildInterfaces():
+ locations = [self.location] + list(
+ i.location for i in self.interfacesBasedOnSelf if i.parent == self
+ )
+ raise WebIDLError(
+ "%s is an unforgeable ancestor interface" % self.identifier.name,
+ locations,
+ )
+
+ ctor = self.ctor()
+ if ctor is not None:
+ ctor.validate()
+ for namedCtor in self.legacyFactoryFunctions:
+ namedCtor.validate()
+
+ indexedGetter = None
+ hasLengthAttribute = False
+ for member in self.members:
+ member.validate()
+
+ if self.isCallback() and member.getExtendedAttribute("Replaceable"):
+ raise WebIDLError(
+ "[Replaceable] used on an attribute on "
+ "interface %s which is a callback interface" % self.identifier.name,
+ [self.location, member.location],
+ )
+
+ # Check that PutForwards refers to another attribute and that no
+ # cycles exist in forwarded assignments. Also check for a
+ # integer-typed "length" attribute.
+ if member.isAttr():
+ if member.identifier.name == "length" and member.type.isInteger():
+ hasLengthAttribute = True
+
+ iface = self
+ attr = member
+ putForwards = attr.getExtendedAttribute("PutForwards")
+ if putForwards and self.isCallback():
+ raise WebIDLError(
+ "[PutForwards] used on an attribute "
+ "on interface %s which is a callback "
+ "interface" % self.identifier.name,
+ [self.location, member.location],
+ )
+
+ while putForwards is not None:
+ forwardIface = attr.type.unroll().inner
+ fowardAttr = None
+
+ for forwardedMember in forwardIface.members:
+ if (
+ not forwardedMember.isAttr()
+ or forwardedMember.identifier.name != putForwards[0]
+ ):
+ continue
+ if forwardedMember == member:
+ raise WebIDLError(
+ "Cycle detected in forwarded "
+ "assignments for attribute %s on "
+ "%s" % (member.identifier.name, self),
+ [member.location],
+ )
+ fowardAttr = forwardedMember
+ break
+
+ if fowardAttr is None:
+ raise WebIDLError(
+ "Attribute %s on %s forwards to "
+ "missing attribute %s"
+ % (attr.identifier.name, iface, putForwards),
+ [attr.location],
+ )
+
+ iface = forwardIface
+ attr = fowardAttr
+ putForwards = attr.getExtendedAttribute("PutForwards")
+
+ # Check that the name of an [Alias] doesn't conflict with an
+ # interface member and whether we support indexed properties.
+ if member.isMethod():
+ if member.isGetter() and member.isIndexed():
+ indexedGetter = member
+
+ for alias in member.aliases:
+ if self.isOnGlobalProtoChain():
+ raise WebIDLError(
+ "[Alias] must not be used on a "
+ "[Global] interface operation",
+ [member.location],
+ )
+ if (
+ member.getExtendedAttribute("Exposed")
+ or member.getExtendedAttribute("ChromeOnly")
+ or member.getExtendedAttribute("Pref")
+ or member.getExtendedAttribute("Func")
+ or member.getExtendedAttribute("Trial")
+ or member.getExtendedAttribute("SecureContext")
+ ):
+ raise WebIDLError(
+ "[Alias] must not be used on a "
+ "conditionally exposed operation",
+ [member.location],
+ )
+ if member.isStatic():
+ raise WebIDLError(
+ "[Alias] must not be used on a " "static operation",
+ [member.location],
+ )
+ if member.isIdentifierLess():
+ raise WebIDLError(
+ "[Alias] must not be used on an "
+ "identifierless operation",
+ [member.location],
+ )
+ if member.isLegacyUnforgeable():
+ raise WebIDLError(
+ "[Alias] must not be used on an "
+ "[LegacyUnforgeable] operation",
+ [member.location],
+ )
+
+ checkDuplicateNames(member, alias, "Alias")
+
+ # Check that the name of a [BindingAlias] doesn't conflict with an
+ # interface member.
+ if member.isAttr():
+ for bindingAlias in member.bindingAliases:
+ checkDuplicateNames(member, bindingAlias, "BindingAlias")
+
+ # Conditional exposure makes no sense for interfaces with no
+ # interface object.
+ # And SecureContext makes sense for interfaces with no interface object,
+ # since it is also propagated to interface members.
+ if (
+ self.isExposedConditionally(exclusions=["SecureContext"])
+ and not self.hasInterfaceObject()
+ ):
+ raise WebIDLError(
+ "Interface with no interface object is " "exposed conditionally",
+ [self.location],
+ )
+
+ # Value iterators are only allowed on interfaces with indexed getters,
+ # and pair iterators are only allowed on interfaces without indexed
+ # getters.
+ if self.isIterable():
+ iterableDecl = self.maplikeOrSetlikeOrIterable
+ if iterableDecl.isValueIterator():
+ if not indexedGetter:
+ raise WebIDLError(
+ "Interface with value iterator does not "
+ "support indexed properties",
+ [self.location, iterableDecl.location],
+ )
+
+ if iterableDecl.valueType != indexedGetter.signatures()[0][0]:
+ raise WebIDLError(
+ "Iterable type does not match indexed " "getter type",
+ [iterableDecl.location, indexedGetter.location],
+ )
+
+ if not hasLengthAttribute:
+ raise WebIDLError(
+ "Interface with value iterator does not "
+ 'have an integer-typed "length" attribute',
+ [self.location, iterableDecl.location],
+ )
+ else:
+ assert iterableDecl.isPairIterator()
+ if indexedGetter:
+ raise WebIDLError(
+ "Interface with pair iterator supports " "indexed properties",
+ [self.location, iterableDecl.location, indexedGetter.location],
+ )
+
+ if indexedGetter and not hasLengthAttribute:
+ raise WebIDLError(
+ "Interface with an indexed getter does not have "
+ 'an integer-typed "length" attribute',
+ [self.location, indexedGetter.location],
+ )
+
+ def setCallback(self, value):
+ self._callback = value
+
+ def isCallback(self):
+ return self._callback
+
+ def isSingleOperationInterface(self):
+ assert self.isCallback() or self.isJSImplemented()
+ return (
+ # JS-implemented things should never need the
+ # this-handling weirdness of single-operation interfaces.
+ not self.isJSImplemented()
+ and
+ # Not inheriting from another interface
+ not self.parent
+ and
+ # No attributes of any kinds
+ not any(m.isAttr() for m in self.members)
+ and
+ # There is at least one regular operation, and all regular
+ # operations have the same identifier
+ len(
+ set(
+ m.identifier.name
+ for m in self.members
+ if m.isMethod() and not m.isStatic()
+ )
+ )
+ == 1
+ )
+
+ def inheritanceDepth(self):
+ depth = 0
+ parent = self.parent
+ while parent:
+ depth = depth + 1
+ parent = parent.parent
+ return depth
+
+ def hasConstants(self):
+ return any(m.isConst() for m in self.members)
+
+ def hasInterfaceObject(self):
+ if self.isCallback():
+ return self.hasConstants()
+ return not hasattr(self, "_noInterfaceObject") and not self.isPseudoInterface()
+
+ def hasInterfacePrototypeObject(self):
+ return (
+ not self.isCallback()
+ and not self.isNamespace()
+ and self.getUserData("hasConcreteDescendant", False)
+ and not self.isPseudoInterface()
+ )
+
+ def addIncludedMixin(self, includedMixin):
+ assert isinstance(includedMixin, IDLInterfaceMixin)
+ self.includedMixins.add(includedMixin)
+
+ def getInheritedInterfaces(self):
+ """
+ Returns a list of the interfaces this interface inherits from
+ (not including this interface itself). The list is in order
+ from most derived to least derived.
+ """
+ assert self._finished
+ if not self.parent:
+ return []
+ parentInterfaces = self.parent.getInheritedInterfaces()
+ parentInterfaces.insert(0, self.parent)
+ return parentInterfaces
+
+ def findInterfaceLoopPoint(self, otherInterface):
+ """
+ Finds an interface amongst our ancestors that inherits from otherInterface.
+ If there is no such interface, returns None.
+ """
+ if self.parent:
+ if self.parent == otherInterface:
+ return self
+ loopPoint = self.parent.findInterfaceLoopPoint(otherInterface)
+ if loopPoint:
+ return loopPoint
+ return None
+
+ def setNonPartial(self, location, parent, members):
+ assert not parent or isinstance(parent, IDLIdentifierPlaceholder)
+ IDLInterfaceOrInterfaceMixinOrNamespace.setNonPartial(self, location, members)
+ assert not self.parent
+ self.parent = parent
+
+ def getJSImplementation(self):
+ classId = self.getExtendedAttribute("JSImplementation")
+ if not classId:
+ return classId
+ assert isinstance(classId, list)
+ assert len(classId) == 1
+ return classId[0]
+
+ def isJSImplemented(self):
+ return bool(self.getJSImplementation())
+
+ def hasProbablyShortLivingWrapper(self):
+ current = self
+ while current:
+ if current.getExtendedAttribute("ProbablyShortLivingWrapper"):
+ return True
+ current = current.parent
+ return False
+
+ def hasChildInterfaces(self):
+ return self._hasChildInterfaces
+
+ def isOnGlobalProtoChain(self):
+ return self._isOnGlobalProtoChain
+
+ def isPseudoInterface(self):
+ return self._isPseudo
+
+ def _getDependentObjects(self):
+ deps = set(self.members)
+ deps.update(self.includedMixins)
+ if self.parent:
+ deps.add(self.parent)
+ return deps
+
+ def hasMembersInSlots(self):
+ return self._ownMembersInSlots != 0
+
+ conditionExtendedAttributes = [
+ "Pref",
+ "ChromeOnly",
+ "Func",
+ "Trial",
+ "SecureContext",
+ ]
+
+ def isExposedConditionally(self, exclusions=[]):
+ return any(
+ ((a not in exclusions) and self.getExtendedAttribute(a))
+ for a in self.conditionExtendedAttributes
+ )
+
+
+class IDLInterface(IDLInterfaceOrNamespace):
+ def __init__(
+ self,
+ location,
+ parentScope,
+ name,
+ parent,
+ members,
+ isKnownNonPartial,
+ classNameOverride=None,
+ ):
+ IDLInterfaceOrNamespace.__init__(
+ self, location, parentScope, name, parent, members, isKnownNonPartial
+ )
+ self.classNameOverride = classNameOverride
+
+ def __str__(self):
+ return "Interface '%s'" % self.identifier.name
+
+ def isInterface(self):
+ return True
+
+ def getClassName(self):
+ if self.classNameOverride:
+ return self.classNameOverride
+ return IDLInterfaceOrNamespace.getClassName(self)
+
+ def addExtendedAttributes(self, attrs):
+ for attr in attrs:
+ identifier = attr.identifier()
+
+ # Special cased attrs
+ if identifier == "TreatNonCallableAsNull":
+ raise WebIDLError(
+ "TreatNonCallableAsNull cannot be specified on interfaces",
+ [attr.location, self.location],
+ )
+ if identifier == "LegacyTreatNonObjectAsNull":
+ raise WebIDLError(
+ "LegacyTreatNonObjectAsNull cannot be specified on interfaces",
+ [attr.location, self.location],
+ )
+ elif identifier == "LegacyNoInterfaceObject":
+ if not attr.noArguments():
+ raise WebIDLError(
+ "[LegacyNoInterfaceObject] must take no arguments",
+ [attr.location],
+ )
+
+ self._noInterfaceObject = True
+ elif identifier == "LegacyFactoryFunction":
+ if not attr.hasValue():
+ raise WebIDLError(
+ (
+ "LegacyFactoryFunction must either take an "
+ "identifier or take a named argument list"
+ ),
+ [attr.location],
+ )
+
+ args = attr.args() if attr.hasArgs() else []
+
+ method = IDLConstructor(attr.location, args, attr.value())
+ method.reallyInit(self)
+
+ # Named constructors are always assumed to be able to
+ # throw (since there's no way to indicate otherwise).
+ method.addExtendedAttributes(
+ [IDLExtendedAttribute(self.location, ("Throws",))]
+ )
+
+ # We need to detect conflicts for LegacyFactoryFunctions across
+ # interfaces. We first call resolve on the parentScope,
+ # which will merge all LegacyFactoryFunctions with the same
+ # identifier accross interfaces as overloads.
+ method.resolve(self.parentScope)
+
+ # Then we look up the identifier on the parentScope. If the
+ # result is the same as the method we're adding then it
+ # hasn't been added as an overload and it's the first time
+ # we've encountered a LegacyFactoryFunction with that identifier.
+ # If the result is not the same as the method we're adding
+ # then it has been added as an overload and we need to check
+ # whether the result is actually one of our existing
+ # LegacyFactoryFunctions.
+ newMethod = self.parentScope.lookupIdentifier(method.identifier)
+ if newMethod == method:
+ self.legacyFactoryFunctions.append(method)
+ elif newMethod not in self.legacyFactoryFunctions:
+ raise WebIDLError(
+ "LegacyFactoryFunction conflicts with a "
+ "LegacyFactoryFunction of a different interface",
+ [method.location, newMethod.location],
+ )
+ elif identifier == "ExceptionClass":
+ if not attr.noArguments():
+ raise WebIDLError(
+ "[ExceptionClass] must take no arguments", [attr.location]
+ )
+ if self.parent:
+ raise WebIDLError(
+ "[ExceptionClass] must not be specified on "
+ "an interface with inherited interfaces",
+ [attr.location, self.location],
+ )
+ elif identifier == "Global":
+ if attr.hasValue():
+ self.globalNames = [attr.value()]
+ elif attr.hasArgs():
+ self.globalNames = attr.args()
+ else:
+ self.globalNames = [self.identifier.name]
+ self.parentScope.addIfaceGlobalNames(
+ self.identifier.name, self.globalNames
+ )
+ self._isOnGlobalProtoChain = True
+ elif identifier == "LegacyWindowAlias":
+ if attr.hasValue():
+ self.legacyWindowAliases = [attr.value()]
+ elif attr.hasArgs():
+ self.legacyWindowAliases = attr.args()
+ else:
+ raise WebIDLError(
+ "[%s] must either take an identifier "
+ "or take an identifier list" % identifier,
+ [attr.location],
+ )
+ for alias in self.legacyWindowAliases:
+ unresolved = IDLUnresolvedIdentifier(attr.location, alias)
+ IDLObjectWithIdentifier(attr.location, self.parentScope, unresolved)
+ elif identifier == "SecureContext":
+ if not attr.noArguments():
+ raise WebIDLError(
+ "[%s] must take no arguments" % identifier, [attr.location]
+ )
+ # This gets propagated to all our members.
+ for member in self.members:
+ if member.getExtendedAttribute("SecureContext"):
+ raise WebIDLError(
+ "[SecureContext] specified on both "
+ "an interface member and on the "
+ "interface itself",
+ [member.location, attr.location],
+ )
+ member.addExtendedAttributes([attr])
+ elif (
+ identifier == "NeedResolve"
+ or identifier == "LegacyOverrideBuiltIns"
+ or identifier == "ChromeOnly"
+ or identifier == "LegacyUnforgeable"
+ or identifier == "LegacyEventInit"
+ or identifier == "ProbablyShortLivingWrapper"
+ or identifier == "LegacyUnenumerableNamedProperties"
+ or identifier == "RunConstructorInCallerCompartment"
+ or identifier == "WantsEventListenerHooks"
+ or identifier == "Serializable"
+ ):
+ # Known extended attributes that do not take values
+ if not attr.noArguments():
+ raise WebIDLError(
+ "[%s] must take no arguments" % identifier, [attr.location]
+ )
+ elif identifier == "Exposed":
+ convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames)
+ elif (
+ identifier == "Pref"
+ or identifier == "JSImplementation"
+ or identifier == "HeaderFile"
+ or identifier == "Func"
+ or identifier == "Trial"
+ or identifier == "Deprecated"
+ ):
+ # Known extended attributes that take a string value
+ if not attr.hasValue():
+ raise WebIDLError(
+ "[%s] must have a value" % identifier, [attr.location]
+ )
+ elif identifier == "InstrumentedProps":
+ # Known extended attributes that take a list
+ if not attr.hasArgs():
+ raise WebIDLError(
+ "[%s] must have arguments" % identifier, [attr.location]
+ )
+ else:
+ raise WebIDLError(
+ "Unknown extended attribute %s on interface" % identifier,
+ [attr.location],
+ )
+
+ attrlist = attr.listValue()
+ self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True
+
+ def validate(self):
+ IDLInterfaceOrNamespace.validate(self)
+ if self.parent and self.isSerializable() and not self.parent.isSerializable():
+ raise WebIDLError(
+ "Serializable interface inherits from non-serializable "
+ "interface. Per spec, that means the object should not be "
+ "serializable, so chances are someone made a mistake here "
+ "somewhere.",
+ [self.location, self.parent.location],
+ )
+
+ def isSerializable(self):
+ return self.getExtendedAttribute("Serializable")
+
+ def setNonPartial(self, location, parent, members):
+ # Before we do anything else, finish initializing any constructors that
+ # might be in "members", so we don't have partially-initialized objects
+ # hanging around. We couldn't do it before now because we needed to have
+ # to have the IDLInterface on hand to properly set the return type.
+ for member in members:
+ if isinstance(member, IDLConstructor):
+ member.reallyInit(self)
+
+ IDLInterfaceOrNamespace.setNonPartial(self, location, parent, members)
+
+
+class IDLNamespace(IDLInterfaceOrNamespace):
+ def __init__(self, location, parentScope, name, members, isKnownNonPartial):
+ IDLInterfaceOrNamespace.__init__(
+ self, location, parentScope, name, None, members, isKnownNonPartial
+ )
+
+ def __str__(self):
+ return "Namespace '%s'" % self.identifier.name
+
+ def isNamespace(self):
+ return True
+
+ def addExtendedAttributes(self, attrs):
+ # The set of things namespaces support is small enough it's simpler
+ # to factor out into a separate method than it is to sprinkle
+ # isNamespace() checks all through
+ # IDLInterfaceOrNamespace.addExtendedAttributes.
+ for attr in attrs:
+ identifier = attr.identifier()
+
+ if identifier == "Exposed":
+ convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames)
+ elif identifier == "ClassString":
+ # Takes a string value to override the default "Object" if
+ # desired.
+ if not attr.hasValue():
+ raise WebIDLError(
+ "[%s] must have a value" % identifier, [attr.location]
+ )
+ elif identifier == "ProtoObjectHack" or identifier == "ChromeOnly":
+ if not attr.noArguments():
+ raise WebIDLError(
+ "[%s] must not have arguments" % identifier, [attr.location]
+ )
+ elif (
+ identifier == "Pref"
+ or identifier == "HeaderFile"
+ or identifier == "Func"
+ or identifier == "Trial"
+ ):
+ # Known extended attributes that take a string value
+ if not attr.hasValue():
+ raise WebIDLError(
+ "[%s] must have a value" % identifier, [attr.location]
+ )
+ else:
+ raise WebIDLError(
+ "Unknown extended attribute %s on namespace" % identifier,
+ [attr.location],
+ )
+
+ attrlist = attr.listValue()
+ self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True
+
+ def isSerializable(self):
+ return False
+
+
+class IDLDictionary(IDLObjectWithScope):
+ def __init__(self, location, parentScope, name, parent, members):
+ assert isinstance(parentScope, IDLScope)
+ assert isinstance(name, IDLUnresolvedIdentifier)
+ assert not parent or isinstance(parent, IDLIdentifierPlaceholder)
+
+ self.parent = parent
+ self._finished = False
+ self.members = list(members)
+ self._partialDictionaries = []
+ self._extendedAttrDict = {}
+ self.needsConversionToJS = False
+ self.needsConversionFromJS = False
+
+ IDLObjectWithScope.__init__(self, location, parentScope, name)
+
+ def __str__(self):
+ return "Dictionary '%s'" % self.identifier.name
+
+ def isDictionary(self):
+ return True
+
+ def canBeEmpty(self):
+ """
+ Returns true if this dictionary can be empty (that is, it has no
+ required members and neither do any of its ancestors).
+ """
+ return all(member.optional for member in self.members) and (
+ not self.parent or self.parent.canBeEmpty()
+ )
+
+ def finish(self, scope):
+ if self._finished:
+ return
+
+ self._finished = True
+
+ if self.parent:
+ assert isinstance(self.parent, IDLIdentifierPlaceholder)
+ oldParent = self.parent
+ self.parent = self.parent.finish(scope)
+ if not isinstance(self.parent, IDLDictionary):
+ raise WebIDLError(
+ "Dictionary %s has parent that is not a dictionary"
+ % self.identifier.name,
+ [oldParent.location, self.parent.location],
+ )
+
+ # Make sure the parent resolves all its members before we start
+ # looking at them.
+ self.parent.finish(scope)
+
+ # Now go ahead and merge in our partial dictionaries.
+ for partial in self._partialDictionaries:
+ partial.finish(scope)
+ self.members.extend(partial.members)
+
+ for member in self.members:
+ member.resolve(self)
+ if not member.isComplete():
+ member.complete(scope)
+ assert member.type.isComplete()
+
+ # Members of a dictionary are sorted in lexicographic order,
+ # unless the dictionary opts out.
+ if not self.getExtendedAttribute("Unsorted"):
+ self.members.sort(key=lambda x: x.identifier.name)
+
+ inheritedMembers = []
+ ancestor = self.parent
+ while ancestor:
+ if ancestor == self:
+ raise WebIDLError(
+ "Dictionary %s has itself as an ancestor" % self.identifier.name,
+ [self.identifier.location],
+ )
+ inheritedMembers.extend(ancestor.members)
+ ancestor = ancestor.parent
+
+ # Catch name duplication
+ for inheritedMember in inheritedMembers:
+ for member in self.members:
+ if member.identifier.name == inheritedMember.identifier.name:
+ raise WebIDLError(
+ "Dictionary %s has two members with name %s"
+ % (self.identifier.name, member.identifier.name),
+ [member.location, inheritedMember.location],
+ )
+
+ def validate(self):
+ def typeContainsDictionary(memberType, dictionary):
+ """
+ Returns a tuple whose:
+
+ - First element is a Boolean value indicating whether
+ memberType contains dictionary.
+
+ - Second element is:
+ A list of locations that leads from the type that was passed in
+ the memberType argument, to the dictionary being validated,
+ if the boolean value in the first element is True.
+
+ None, if the boolean value in the first element is False.
+ """
+
+ if (
+ memberType.nullable()
+ or memberType.isSequence()
+ or memberType.isRecord()
+ ):
+ return typeContainsDictionary(memberType.inner, dictionary)
+
+ if memberType.isDictionary():
+ if memberType.inner == dictionary:
+ return (True, [memberType.location])
+
+ (contains, locations) = dictionaryContainsDictionary(
+ memberType.inner, dictionary
+ )
+ if contains:
+ return (True, [memberType.location] + locations)
+
+ if memberType.isUnion():
+ for member in memberType.flatMemberTypes:
+ (contains, locations) = typeContainsDictionary(member, dictionary)
+ if contains:
+ return (True, locations)
+
+ return (False, None)
+
+ def dictionaryContainsDictionary(dictMember, dictionary):
+ for member in dictMember.members:
+ (contains, locations) = typeContainsDictionary(member.type, dictionary)
+ if contains:
+ return (True, [member.location] + locations)
+
+ if dictMember.parent:
+ if dictMember.parent == dictionary:
+ return (True, [dictMember.location])
+ else:
+ (contains, locations) = dictionaryContainsDictionary(
+ dictMember.parent, dictionary
+ )
+ if contains:
+ return (True, [dictMember.location] + locations)
+
+ return (False, None)
+
+ for member in self.members:
+ if member.type.isDictionary() and member.type.nullable():
+ raise WebIDLError(
+ "Dictionary %s has member with nullable "
+ "dictionary type" % self.identifier.name,
+ [member.location],
+ )
+ (contains, locations) = typeContainsDictionary(member.type, self)
+ if contains:
+ raise WebIDLError(
+ "Dictionary %s has member with itself as type."
+ % self.identifier.name,
+ [member.location] + locations,
+ )
+
+ if member.type.isUndefined():
+ raise WebIDLError(
+ "Dictionary %s has member with undefined as its type."
+ % self.identifier.name,
+ [member.location],
+ )
+ elif member.type.isUnion():
+ for unionMember in member.type.unroll().flatMemberTypes:
+ if unionMember.isUndefined():
+ raise WebIDLError(
+ "Dictionary %s has member with a union containing "
+ "undefined as a type." % self.identifier.name,
+ [unionMember.location],
+ )
+
+ def getExtendedAttribute(self, name):
+ return self._extendedAttrDict.get(name, None)
+
+ def addExtendedAttributes(self, attrs):
+ for attr in attrs:
+ identifier = attr.identifier()
+
+ if identifier == "GenerateInitFromJSON" or identifier == "GenerateInit":
+ if not attr.noArguments():
+ raise WebIDLError(
+ "[%s] must not have arguments" % identifier, [attr.location]
+ )
+ self.needsConversionFromJS = True
+ elif (
+ identifier == "GenerateConversionToJS" or identifier == "GenerateToJSON"
+ ):
+ if not attr.noArguments():
+ raise WebIDLError(
+ "[%s] must not have arguments" % identifier, [attr.location]
+ )
+ # ToJSON methods require to-JS conversion, because we
+ # implement ToJSON by converting to a JS object and
+ # then using JSON.stringify.
+ self.needsConversionToJS = True
+ elif identifier == "Unsorted":
+ if not attr.noArguments():
+ raise WebIDLError(
+ "[Unsorted] must take no arguments", [attr.location]
+ )
+ else:
+ raise WebIDLError(
+ "[%s] extended attribute not allowed on "
+ "dictionaries" % identifier,
+ [attr.location],
+ )
+
+ self._extendedAttrDict[identifier] = True
+
+ def _getDependentObjects(self):
+ deps = set(self.members)
+ if self.parent:
+ deps.add(self.parent)
+ return deps
+
+ def addPartialDictionary(self, partial):
+ assert self.identifier.name == partial.identifier.name
+ self._partialDictionaries.append(partial)
+
+
+class IDLEnum(IDLObjectWithIdentifier):
+ def __init__(self, location, parentScope, name, values):
+ assert isinstance(parentScope, IDLScope)
+ assert isinstance(name, IDLUnresolvedIdentifier)
+
+ if len(values) != len(set(values)):
+ raise WebIDLError(
+ "Enum %s has multiple identical strings" % name.name, [location]
+ )
+
+ IDLObjectWithIdentifier.__init__(self, location, parentScope, name)
+ self._values = values
+
+ def values(self):
+ return self._values
+
+ def finish(self, scope):
+ pass
+
+ def validate(self):
+ pass
+
+ def isEnum(self):
+ return True
+
+ def addExtendedAttributes(self, attrs):
+ if len(attrs) != 0:
+ raise WebIDLError(
+ "There are no extended attributes that are " "allowed on enums",
+ [attrs[0].location, self.location],
+ )
+
+ def _getDependentObjects(self):
+ return set()
+
+
+class IDLType(IDLObject):
+ Tags = enum(
+ # The integer types
+ "int8",
+ "uint8",
+ "int16",
+ "uint16",
+ "int32",
+ "uint32",
+ "int64",
+ "uint64",
+ # Additional primitive types
+ "bool",
+ "unrestricted_float",
+ "float",
+ "unrestricted_double",
+ # "double" last primitive type to match IDLBuiltinType
+ "double",
+ # Other types
+ "any",
+ "undefined",
+ "domstring",
+ "bytestring",
+ "usvstring",
+ "utf8string",
+ "jsstring",
+ "object",
+ # Funny stuff
+ "interface",
+ "dictionary",
+ "enum",
+ "callback",
+ "union",
+ "sequence",
+ "record",
+ "promise",
+ "observablearray",
+ )
+
+ def __init__(self, location, name):
+ IDLObject.__init__(self, location)
+ self.name = name
+ self.builtin = False
+ self.legacyNullToEmptyString = False
+ self._clamp = False
+ self._enforceRange = False
+ self._allowShared = False
+ self._extendedAttrDict = {}
+
+ def __hash__(self):
+ return (
+ hash(self.builtin)
+ + hash(self.name)
+ + hash(self._clamp)
+ + hash(self._enforceRange)
+ + hash(self.legacyNullToEmptyString)
+ + hash(self._allowShared)
+ )
+
+ def __eq__(self, other):
+ return (
+ other
+ and self.builtin == other.builtin
+ and self.name == other.name
+ and self._clamp == other.hasClamp()
+ and self._enforceRange == other.hasEnforceRange()
+ and self.legacyNullToEmptyString == other.legacyNullToEmptyString
+ and self._allowShared == other.hasAllowShared()
+ )
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __str__(self):
+ return str(self.name)
+
+ def prettyName(self):
+ """
+ A name that looks like what this type is named in the IDL spec. By default
+ this is just our .name, but types that have more interesting spec
+ representations should override this.
+ """
+ return str(self.name)
+
+ def isType(self):
+ return True
+
+ def nullable(self):
+ return False
+
+ def isPrimitive(self):
+ return False
+
+ def isBoolean(self):
+ return False
+
+ def isNumeric(self):
+ return False
+
+ def isString(self):
+ return False
+
+ def isByteString(self):
+ return False
+
+ def isDOMString(self):
+ return False
+
+ def isUSVString(self):
+ return False
+
+ def isUTF8String(self):
+ return False
+
+ def isJSString(self):
+ return False
+
+ def isUndefined(self):
+ return False
+
+ def isSequence(self):
+ return False
+
+ def isRecord(self):
+ return False
+
+ def isArrayBuffer(self):
+ return False
+
+ def isArrayBufferView(self):
+ return False
+
+ def isTypedArray(self):
+ return False
+
+ def isBufferSource(self):
+ return self.isArrayBuffer() or self.isArrayBufferView() or self.isTypedArray()
+
+ def isCallbackInterface(self):
+ return False
+
+ def isNonCallbackInterface(self):
+ return False
+
+ def isGeckoInterface(self):
+ """Returns a boolean indicating whether this type is an 'interface'
+ type that is implemented in Gecko. At the moment, this returns
+ true for all interface types that are not types from the TypedArray
+ spec."""
+ return self.isInterface() and not self.isSpiderMonkeyInterface()
+
+ def isSpiderMonkeyInterface(self):
+ """Returns a boolean indicating whether this type is an 'interface'
+ type that is implemented in SpiderMonkey."""
+ return self.isInterface() and self.isBufferSource()
+
+ def isAny(self):
+ return self.tag() == IDLType.Tags.any
+
+ def isObject(self):
+ return self.tag() == IDLType.Tags.object
+
+ def isPromise(self):
+ return False
+
+ def isComplete(self):
+ return True
+
+ def includesRestrictedFloat(self):
+ return False
+
+ def isFloat(self):
+ return False
+
+ def isUnrestricted(self):
+ # Should only call this on float types
+ assert self.isFloat()
+
+ def isJSONType(self):
+ return False
+
+ def isObservableArray(self):
+ return False
+
+ def isDictionaryLike(self):
+ return self.isDictionary() or self.isRecord() or self.isCallbackInterface()
+
+ def hasClamp(self):
+ return self._clamp
+
+ def hasEnforceRange(self):
+ return self._enforceRange
+
+ def hasAllowShared(self):
+ return self._allowShared
+
+ def tag(self):
+ assert False # Override me!
+
+ def treatNonCallableAsNull(self):
+ assert self.tag() == IDLType.Tags.callback
+ return self.nullable() and self.inner.callback._treatNonCallableAsNull
+
+ def treatNonObjectAsNull(self):
+ assert self.tag() == IDLType.Tags.callback
+ return self.nullable() and self.inner.callback._treatNonObjectAsNull
+
+ def withExtendedAttributes(self, attrs):
+ if len(attrs) > 0:
+ raise WebIDLError(
+ "Extended attributes on types only supported for builtins",
+ [attrs[0].location, self.location],
+ )
+ return self
+
+ def getExtendedAttribute(self, name):
+ return self._extendedAttrDict.get(name, None)
+
+ def resolveType(self, parentScope):
+ pass
+
+ def unroll(self):
+ return self
+
+ def isDistinguishableFrom(self, other):
+ raise TypeError(
+ "Can't tell whether a generic type is or is not "
+ "distinguishable from other things"
+ )
+
+ def isExposedInAllOf(self, exposureSet):
+ return True
+
+
+class IDLUnresolvedType(IDLType):
+ """
+ Unresolved types are interface types
+ """
+
+ def __init__(self, location, name, attrs=[]):
+ IDLType.__init__(self, location, name)
+ self.extraTypeAttributes = attrs
+
+ def isComplete(self):
+ return False
+
+ def complete(self, scope):
+ obj = None
+ try:
+ obj = scope._lookupIdentifier(self.name)
+ except Exception:
+ raise WebIDLError("Unresolved type '%s'." % self.name, [self.location])
+
+ assert obj
+ assert not obj.isType()
+ if obj.isTypedef():
+ assert self.name.name == obj.identifier.name
+ typedefType = IDLTypedefType(self.location, obj.innerType, obj.identifier)
+ assert not typedefType.isComplete()
+ return typedefType.complete(scope).withExtendedAttributes(
+ self.extraTypeAttributes
+ )
+ elif obj.isCallback() and not obj.isInterface():
+ assert self.name.name == obj.identifier.name
+ return IDLCallbackType(self.location, obj)
+
+ return IDLWrapperType(self.location, obj)
+
+ def withExtendedAttributes(self, attrs):
+ return IDLUnresolvedType(self.location, self.name, attrs)
+
+ def isDistinguishableFrom(self, other):
+ raise TypeError(
+ "Can't tell whether an unresolved type is or is not "
+ "distinguishable from other things"
+ )
+
+
+class IDLParametrizedType(IDLType):
+ def __init__(self, location, name, innerType):
+ IDLType.__init__(self, location, name)
+ self.builtin = False
+ self.inner = innerType
+
+ def includesRestrictedFloat(self):
+ return self.inner.includesRestrictedFloat()
+
+ def resolveType(self, parentScope):
+ assert isinstance(parentScope, IDLScope)
+ self.inner.resolveType(parentScope)
+
+ def isComplete(self):
+ return self.inner.isComplete()
+
+ def unroll(self):
+ return self.inner.unroll()
+
+ def _getDependentObjects(self):
+ return self.inner._getDependentObjects()
+
+
+class IDLNullableType(IDLParametrizedType):
+ def __init__(self, location, innerType):
+ assert not innerType == BuiltinTypes[IDLBuiltinType.Types.any]
+
+ IDLParametrizedType.__init__(self, location, None, innerType)
+
+ def __hash__(self):
+ return hash(self.inner)
+
+ def __eq__(self, other):
+ return isinstance(other, IDLNullableType) and self.inner == other.inner
+
+ def __str__(self):
+ return self.inner.__str__() + "OrNull"
+
+ def prettyName(self):
+ return self.inner.prettyName() + "?"
+
+ def nullable(self):
+ return True
+
+ def isCallback(self):
+ return self.inner.isCallback()
+
+ def isPrimitive(self):
+ return self.inner.isPrimitive()
+
+ def isBoolean(self):
+ return self.inner.isBoolean()
+
+ def isNumeric(self):
+ return self.inner.isNumeric()
+
+ def isString(self):
+ return self.inner.isString()
+
+ def isByteString(self):
+ return self.inner.isByteString()
+
+ def isDOMString(self):
+ return self.inner.isDOMString()
+
+ def isUSVString(self):
+ return self.inner.isUSVString()
+
+ def isUTF8String(self):
+ return self.inner.isUTF8String()
+
+ def isJSString(self):
+ return self.inner.isJSString()
+
+ def isFloat(self):
+ return self.inner.isFloat()
+
+ def isUnrestricted(self):
+ return self.inner.isUnrestricted()
+
+ def isInteger(self):
+ return self.inner.isInteger()
+
+ def isUndefined(self):
+ return self.inner.isUndefined()
+
+ def isSequence(self):
+ return self.inner.isSequence()
+
+ def isRecord(self):
+ return self.inner.isRecord()
+
+ def isArrayBuffer(self):
+ return self.inner.isArrayBuffer()
+
+ def isArrayBufferView(self):
+ return self.inner.isArrayBufferView()
+
+ def isTypedArray(self):
+ return self.inner.isTypedArray()
+
+ def isDictionary(self):
+ return self.inner.isDictionary()
+
+ def isInterface(self):
+ return self.inner.isInterface()
+
+ def isPromise(self):
+ # There is no such thing as a nullable Promise.
+ assert not self.inner.isPromise()
+ return False
+
+ def isCallbackInterface(self):
+ return self.inner.isCallbackInterface()
+
+ def isNonCallbackInterface(self):
+ return self.inner.isNonCallbackInterface()
+
+ def isEnum(self):
+ return self.inner.isEnum()
+
+ def isUnion(self):
+ return self.inner.isUnion()
+
+ def isJSONType(self):
+ return self.inner.isJSONType()
+
+ def isObservableArray(self):
+ return self.inner.isObservableArray()
+
+ def hasClamp(self):
+ return self.inner.hasClamp()
+
+ def hasEnforceRange(self):
+ return self.inner.hasEnforceRange()
+
+ def hasAllowShared(self):
+ return self.inner.hasAllowShared()
+
+ def isComplete(self):
+ return self.name is not None
+
+ def tag(self):
+ return self.inner.tag()
+
+ def complete(self, scope):
+ if not self.inner.isComplete():
+ self.inner = self.inner.complete(scope)
+ assert self.inner.isComplete()
+
+ if self.inner.nullable():
+ raise WebIDLError(
+ "The inner type of a nullable type must not be a nullable type",
+ [self.location, self.inner.location],
+ )
+ if self.inner.isUnion():
+ if self.inner.hasNullableType:
+ raise WebIDLError(
+ "The inner type of a nullable type must not "
+ "be a union type that itself has a nullable "
+ "type as a member type",
+ [self.location],
+ )
+ if self.inner.isDOMString():
+ if self.inner.legacyNullToEmptyString:
+ raise WebIDLError(
+ "[LegacyNullToEmptyString] not allowed on a nullable DOMString",
+ [self.location, self.inner.location],
+ )
+ if self.inner.isObservableArray():
+ raise WebIDLError(
+ "The inner type of a nullable type must not be an ObservableArray type",
+ [self.location, self.inner.location],
+ )
+
+ self.name = self.inner.name + "OrNull"
+ return self
+
+ def isDistinguishableFrom(self, other):
+ if (
+ other.nullable()
+ or other.isDictionary()
+ or (
+ other.isUnion() and (other.hasNullableType or other.hasDictionaryType())
+ )
+ ):
+ # Can't tell which type null should become
+ return False
+ return self.inner.isDistinguishableFrom(other)
+
+ def withExtendedAttributes(self, attrs):
+ # See https://github.com/heycam/webidl/issues/827#issuecomment-565131350
+ # Allowing extended attributes to apply to a nullable type is an intermediate solution.
+ # A potential longer term solution is to introduce a null type and get rid of nullables.
+ # For example, we could do `([Clamp] long or null) foo` in the future.
+ return IDLNullableType(self.location, self.inner.withExtendedAttributes(attrs))
+
+
+class IDLSequenceType(IDLParametrizedType):
+ def __init__(self, location, parameterType):
+ assert not parameterType.isUndefined()
+
+ IDLParametrizedType.__init__(self, location, parameterType.name, parameterType)
+ # Need to set self.name up front if our inner type is already complete,
+ # since in that case our .complete() won't be called.
+ if self.inner.isComplete():
+ self.name = self.inner.name + "Sequence"
+
+ def __hash__(self):
+ return hash(self.inner)
+
+ def __eq__(self, other):
+ return isinstance(other, IDLSequenceType) and self.inner == other.inner
+
+ def __str__(self):
+ return self.inner.__str__() + "Sequence"
+
+ def prettyName(self):
+ return "sequence<%s>" % self.inner.prettyName()
+
+ def isSequence(self):
+ return True
+
+ def isJSONType(self):
+ return self.inner.isJSONType()
+
+ def tag(self):
+ return IDLType.Tags.sequence
+
+ def complete(self, scope):
+ if self.inner.isObservableArray():
+ raise WebIDLError(
+ "The inner type of a sequence type must not be an ObservableArray type",
+ [self.location, self.inner.location],
+ )
+
+ self.inner = self.inner.complete(scope)
+ self.name = self.inner.name + "Sequence"
+ return self
+
+ def isDistinguishableFrom(self, other):
+ if other.isPromise():
+ return False
+ if other.isUnion():
+ # Just forward to the union; it'll deal
+ return other.isDistinguishableFrom(self)
+ return (
+ other.isUndefined()
+ or other.isPrimitive()
+ or other.isString()
+ or other.isEnum()
+ or other.isInterface()
+ or other.isDictionary()
+ or other.isCallback()
+ or other.isRecord()
+ )
+
+
+class IDLRecordType(IDLParametrizedType):
+ def __init__(self, location, keyType, valueType):
+ assert keyType.isString()
+ assert keyType.isComplete()
+ assert not valueType.isUndefined()
+
+ IDLParametrizedType.__init__(self, location, valueType.name, valueType)
+ self.keyType = keyType
+
+ # Need to set self.name up front if our inner type is already complete,
+ # since in that case our .complete() won't be called.
+ if self.inner.isComplete():
+ self.name = self.keyType.name + self.inner.name + "Record"
+
+ def __hash__(self):
+ return hash(self.inner)
+
+ def __eq__(self, other):
+ return isinstance(other, IDLRecordType) and self.inner == other.inner
+
+ def __str__(self):
+ return self.keyType.__str__() + self.inner.__str__() + "Record"
+
+ def prettyName(self):
+ return "record<%s, %s>" % (self.keyType.prettyName(), self.inner.prettyName())
+
+ def isRecord(self):
+ return True
+
+ def isJSONType(self):
+ return self.inner.isJSONType()
+
+ def tag(self):
+ return IDLType.Tags.record
+
+ def complete(self, scope):
+ if self.inner.isObservableArray():
+ raise WebIDLError(
+ "The value type of a record type must not be an ObservableArray type",
+ [self.location, self.inner.location],
+ )
+
+ self.inner = self.inner.complete(scope)
+ self.name = self.keyType.name + self.inner.name + "Record"
+ return self
+
+ def unroll(self):
+ # We do not unroll our inner. Just stop at ourselves. That
+ # lets us add headers for both ourselves and our inner as
+ # needed.
+ return self
+
+ def isDistinguishableFrom(self, other):
+ if other.isPromise():
+ return False
+ if other.isUnion():
+ # Just forward to the union; it'll deal
+ return other.isDistinguishableFrom(self)
+ return (
+ other.isPrimitive()
+ or other.isString()
+ or other.isEnum()
+ or other.isNonCallbackInterface()
+ or other.isSequence()
+ )
+
+ def isExposedInAllOf(self, exposureSet):
+ return self.inner.unroll().isExposedInAllOf(exposureSet)
+
+
+class IDLObservableArrayType(IDLParametrizedType):
+ def __init__(self, location, innerType):
+ assert not innerType.isUndefined()
+ IDLParametrizedType.__init__(self, location, None, innerType)
+
+ def __hash__(self):
+ return hash(self.inner)
+
+ def __eq__(self, other):
+ return isinstance(other, IDLObservableArrayType) and self.inner == other.inner
+
+ def __str__(self):
+ return self.inner.__str__() + "ObservableArray"
+
+ def prettyName(self):
+ return "ObservableArray<%s>" % self.inner.prettyName()
+
+ def isJSONType(self):
+ return self.inner.isJSONType()
+
+ def isObservableArray(self):
+ return True
+
+ def isComplete(self):
+ return self.name is not None
+
+ def tag(self):
+ return IDLType.Tags.observablearray
+
+ def complete(self, scope):
+ if not self.inner.isComplete():
+ self.inner = self.inner.complete(scope)
+ assert self.inner.isComplete()
+
+ if self.inner.isDictionary():
+ raise WebIDLError(
+ "The inner type of an ObservableArray type must not "
+ "be a dictionary type",
+ [self.location, self.inner.location],
+ )
+ if self.inner.isSequence():
+ raise WebIDLError(
+ "The inner type of an ObservableArray type must not "
+ "be a sequence type",
+ [self.location, self.inner.location],
+ )
+ if self.inner.isRecord():
+ raise WebIDLError(
+ "The inner type of an ObservableArray type must not be a record type",
+ [self.location, self.inner.location],
+ )
+ if self.inner.isObservableArray():
+ raise WebIDLError(
+ "The inner type of an ObservableArray type must not "
+ "be an ObservableArray type",
+ [self.location, self.inner.location],
+ )
+
+ self.name = self.inner.name + "ObservableArray"
+ return self
+
+ def isDistinguishableFrom(self, other):
+ # ObservableArrays are not distinguishable from anything.
+ return False
+
+
+class IDLUnionType(IDLType):
+ def __init__(self, location, memberTypes):
+ IDLType.__init__(self, location, "")
+ self.memberTypes = memberTypes
+ self.hasNullableType = False
+ self._dictionaryType = None
+ self.flatMemberTypes = None
+ self.builtin = False
+
+ def __eq__(self, other):
+ return isinstance(other, IDLUnionType) and self.memberTypes == other.memberTypes
+
+ def __hash__(self):
+ assert self.isComplete()
+ return self.name.__hash__()
+
+ def prettyName(self):
+ return "(" + " or ".join(m.prettyName() for m in self.memberTypes) + ")"
+
+ def isUnion(self):
+ return True
+
+ def isJSONType(self):
+ return all(m.isJSONType() for m in self.memberTypes)
+
+ def includesRestrictedFloat(self):
+ return any(t.includesRestrictedFloat() for t in self.memberTypes)
+
+ def tag(self):
+ return IDLType.Tags.union
+
+ def resolveType(self, parentScope):
+ assert isinstance(parentScope, IDLScope)
+ for t in self.memberTypes:
+ t.resolveType(parentScope)
+
+ def isComplete(self):
+ return self.flatMemberTypes is not None
+
+ def complete(self, scope):
+ def typeName(type):
+ if isinstance(type, IDLNullableType):
+ return typeName(type.inner) + "OrNull"
+ if isinstance(type, IDLWrapperType):
+ return typeName(type._identifier.object())
+ if isinstance(type, IDLObjectWithIdentifier):
+ return typeName(type.identifier)
+ if isinstance(type, IDLBuiltinType) and type.hasAllowShared():
+ assert type.isBufferSource()
+ return "MaybeShared" + type.name
+ return type.name
+
+ for (i, type) in enumerate(self.memberTypes):
+ if not type.isComplete():
+ self.memberTypes[i] = type.complete(scope)
+
+ self.name = "Or".join(typeName(type) for type in self.memberTypes)
+ self.flatMemberTypes = list(self.memberTypes)
+ i = 0
+ while i < len(self.flatMemberTypes):
+ if self.flatMemberTypes[i].nullable():
+ if self.hasNullableType:
+ raise WebIDLError(
+ "Can't have more than one nullable types in a union",
+ [nullableType.location, self.flatMemberTypes[i].location],
+ )
+ if self.hasDictionaryType():
+ raise WebIDLError(
+ "Can't have a nullable type and a "
+ "dictionary type in a union",
+ [
+ self._dictionaryType.location,
+ self.flatMemberTypes[i].location,
+ ],
+ )
+ self.hasNullableType = True
+ nullableType = self.flatMemberTypes[i]
+ self.flatMemberTypes[i] = self.flatMemberTypes[i].inner
+ continue
+ if self.flatMemberTypes[i].isDictionary():
+ if self.hasNullableType:
+ raise WebIDLError(
+ "Can't have a nullable type and a "
+ "dictionary type in a union",
+ [nullableType.location, self.flatMemberTypes[i].location],
+ )
+ self._dictionaryType = self.flatMemberTypes[i]
+ self.flatMemberTypes[i].inner.needsConversionFromJS = True
+ elif self.flatMemberTypes[i].isUnion():
+ self.flatMemberTypes[i : i + 1] = self.flatMemberTypes[i].memberTypes
+ continue
+ i += 1
+
+ for (i, t) in enumerate(self.flatMemberTypes[:-1]):
+ for u in self.flatMemberTypes[i + 1 :]:
+ if not t.isDistinguishableFrom(u):
+ raise WebIDLError(
+ "Flat member types of a union should be "
+ "distinguishable, " + str(t) + " is not "
+ "distinguishable from " + str(u),
+ [self.location, t.location, u.location],
+ )
+
+ return self
+
+ def isDistinguishableFrom(self, other):
+ if self.hasNullableType and other.nullable():
+ # Can't tell which type null should become
+ return False
+ if other.isUnion():
+ otherTypes = other.unroll().memberTypes
+ else:
+ otherTypes = [other]
+ # For every type in otherTypes, check that it's distinguishable from
+ # every type in our types
+ for u in otherTypes:
+ if any(not t.isDistinguishableFrom(u) for t in self.memberTypes):
+ return False
+ return True
+
+ def isExposedInAllOf(self, exposureSet):
+ # We could have different member types in different globals.
+ # Just make sure that each thing in exposureSet has one of our member types exposed in it.
+ for globalName in exposureSet:
+ if not any(
+ t.unroll().isExposedInAllOf(set([globalName]))
+ for t in self.flatMemberTypes
+ ):
+ return False
+ return True
+
+ def hasDictionaryType(self):
+ return self._dictionaryType is not None
+
+ def hasPossiblyEmptyDictionaryType(self):
+ return (
+ self._dictionaryType is not None and self._dictionaryType.inner.canBeEmpty()
+ )
+
+ def _getDependentObjects(self):
+ return set(self.memberTypes)
+
+
+class IDLTypedefType(IDLType):
+ def __init__(self, location, innerType, name):
+ IDLType.__init__(self, location, name)
+ self.inner = innerType
+ self.builtin = False
+
+ def __hash__(self):
+ return hash(self.inner)
+
+ def __eq__(self, other):
+ return isinstance(other, IDLTypedefType) and self.inner == other.inner
+
+ def __str__(self):
+ return self.name
+
+ def nullable(self):
+ return self.inner.nullable()
+
+ def isPrimitive(self):
+ return self.inner.isPrimitive()
+
+ def isBoolean(self):
+ return self.inner.isBoolean()
+
+ def isNumeric(self):
+ return self.inner.isNumeric()
+
+ def isString(self):
+ return self.inner.isString()
+
+ def isByteString(self):
+ return self.inner.isByteString()
+
+ def isDOMString(self):
+ return self.inner.isDOMString()
+
+ def isUSVString(self):
+ return self.inner.isUSVString()
+
+ def isUTF8String(self):
+ return self.inner.isUTF8String()
+
+ def isJSString(self):
+ return self.inner.isJSString()
+
+ def isUndefined(self):
+ return self.inner.isUndefined()
+
+ def isJSONType(self):
+ return self.inner.isJSONType()
+
+ def isSequence(self):
+ return self.inner.isSequence()
+
+ def isRecord(self):
+ return self.inner.isRecord()
+
+ def isDictionary(self):
+ return self.inner.isDictionary()
+
+ def isArrayBuffer(self):
+ return self.inner.isArrayBuffer()
+
+ def isArrayBufferView(self):
+ return self.inner.isArrayBufferView()
+
+ def isTypedArray(self):
+ return self.inner.isTypedArray()
+
+ def isInterface(self):
+ return self.inner.isInterface()
+
+ def isCallbackInterface(self):
+ return self.inner.isCallbackInterface()
+
+ def isNonCallbackInterface(self):
+ return self.inner.isNonCallbackInterface()
+
+ def isComplete(self):
+ return False
+
+ def complete(self, parentScope):
+ if not self.inner.isComplete():
+ self.inner = self.inner.complete(parentScope)
+ assert self.inner.isComplete()
+ return self.inner
+
+ # Do we need a resolveType impl? I don't think it's particularly useful....
+
+ def tag(self):
+ return self.inner.tag()
+
+ def unroll(self):
+ return self.inner.unroll()
+
+ def isDistinguishableFrom(self, other):
+ return self.inner.isDistinguishableFrom(other)
+
+ def _getDependentObjects(self):
+ return self.inner._getDependentObjects()
+
+ def withExtendedAttributes(self, attrs):
+ return IDLTypedefType(
+ self.location, self.inner.withExtendedAttributes(attrs), self.name
+ )
+
+
+class IDLTypedef(IDLObjectWithIdentifier):
+ def __init__(self, location, parentScope, innerType, name):
+ # Set self.innerType first, because IDLObjectWithIdentifier.__init__
+ # will call our __str__, which wants to use it.
+ self.innerType = innerType
+ identifier = IDLUnresolvedIdentifier(location, name)
+ IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier)
+
+ def __str__(self):
+ return "Typedef %s %s" % (self.identifier.name, self.innerType)
+
+ def finish(self, parentScope):
+ if not self.innerType.isComplete():
+ self.innerType = self.innerType.complete(parentScope)
+
+ def validate(self):
+ pass
+
+ def isTypedef(self):
+ return True
+
+ def addExtendedAttributes(self, attrs):
+ if len(attrs) != 0:
+ raise WebIDLError(
+ "There are no extended attributes that are " "allowed on typedefs",
+ [attrs[0].location, self.location],
+ )
+
+ def _getDependentObjects(self):
+ return self.innerType._getDependentObjects()
+
+
+class IDLWrapperType(IDLType):
+ def __init__(self, location, inner):
+ IDLType.__init__(self, location, inner.identifier.name)
+ self.inner = inner
+ self._identifier = inner.identifier
+ self.builtin = False
+
+ def __hash__(self):
+ return hash(self._identifier) + hash(self.builtin)
+
+ def __eq__(self, other):
+ return (
+ isinstance(other, IDLWrapperType)
+ and self._identifier == other._identifier
+ and self.builtin == other.builtin
+ )
+
+ def __str__(self):
+ return str(self.name) + " (Wrapper)"
+
+ def isDictionary(self):
+ return isinstance(self.inner, IDLDictionary)
+
+ def isInterface(self):
+ return isinstance(self.inner, IDLInterface) or isinstance(
+ self.inner, IDLExternalInterface
+ )
+
+ def isCallbackInterface(self):
+ return self.isInterface() and self.inner.isCallback()
+
+ def isNonCallbackInterface(self):
+ return self.isInterface() and not self.inner.isCallback()
+
+ def isEnum(self):
+ return isinstance(self.inner, IDLEnum)
+
+ def isJSONType(self):
+ if self.isInterface():
+ if self.inner.isExternal():
+ return False
+ iface = self.inner
+ while iface:
+ if any(m.isMethod() and m.isToJSON() for m in iface.members):
+ return True
+ iface = iface.parent
+ return False
+ elif self.isEnum():
+ return True
+ elif self.isDictionary():
+ dictionary = self.inner
+ while dictionary:
+ if not all(m.type.isJSONType() for m in dictionary.members):
+ return False
+ dictionary = dictionary.parent
+ return True
+ else:
+ raise WebIDLError(
+ "IDLWrapperType wraps type %s that we don't know if "
+ "is serializable" % type(self.inner),
+ [self.location],
+ )
+
+ def resolveType(self, parentScope):
+ assert isinstance(parentScope, IDLScope)
+ self.inner.resolve(parentScope)
+
+ def isComplete(self):
+ return True
+
+ def tag(self):
+ if self.isInterface():
+ return IDLType.Tags.interface
+ elif self.isEnum():
+ return IDLType.Tags.enum
+ elif self.isDictionary():
+ return IDLType.Tags.dictionary
+ else:
+ assert False
+
+ def isDistinguishableFrom(self, other):
+ if other.isPromise():
+ return False
+ if other.isUnion():
+ # Just forward to the union; it'll deal
+ return other.isDistinguishableFrom(self)
+ assert self.isInterface() or self.isEnum() or self.isDictionary()
+ if self.isEnum():
+ return (
+ other.isUndefined()
+ or other.isPrimitive()
+ or other.isInterface()
+ or other.isObject()
+ or other.isCallback()
+ or other.isDictionary()
+ or other.isSequence()
+ or other.isRecord()
+ )
+ if self.isDictionary() and (other.nullable() or other.isUndefined()):
+ return False
+ if (
+ other.isPrimitive()
+ or other.isString()
+ or other.isEnum()
+ or other.isSequence()
+ ):
+ return True
+ if self.isDictionary():
+ return other.isNonCallbackInterface()
+
+ assert self.isInterface()
+ if other.isInterface():
+ if other.isSpiderMonkeyInterface():
+ # Just let |other| handle things
+ return other.isDistinguishableFrom(self)
+ assert self.isGeckoInterface() and other.isGeckoInterface()
+ if self.inner.isExternal() or other.unroll().inner.isExternal():
+ return self != other
+ return len(
+ self.inner.interfacesBasedOnSelf
+ & other.unroll().inner.interfacesBasedOnSelf
+ ) == 0 and (self.isNonCallbackInterface() or other.isNonCallbackInterface())
+ if (
+ other.isUndefined()
+ or other.isDictionary()
+ or other.isCallback()
+ or other.isRecord()
+ ):
+ return self.isNonCallbackInterface()
+
+ # Not much else |other| can be
+ assert other.isObject()
+ return False
+
+ def isExposedInAllOf(self, exposureSet):
+ if not self.isInterface():
+ return True
+ iface = self.inner
+ if iface.isExternal():
+ # Let's say true, so we don't have to implement exposure mixins on
+ # external interfaces and sprinkle [Exposed=Window] on every single
+ # external interface declaration.
+ return True
+ return iface.exposureSet.issuperset(exposureSet)
+
+ def _getDependentObjects(self):
+ # NB: The codegen for an interface type depends on
+ # a) That the identifier is in fact an interface (as opposed to
+ # a dictionary or something else).
+ # b) The native type of the interface.
+ # If we depend on the interface object we will also depend on
+ # anything the interface depends on which is undesirable. We
+ # considered implementing a dependency just on the interface type
+ # file, but then every modification to an interface would cause this
+ # to be regenerated which is still undesirable. We decided not to
+ # depend on anything, reasoning that:
+ # 1) Changing the concrete type of the interface requires modifying
+ # Bindings.conf, which is still a global dependency.
+ # 2) Changing an interface to a dictionary (or vice versa) with the
+ # same identifier should be incredibly rare.
+ #
+ # On the other hand, if our type is a dictionary, we should
+ # depend on it, because the member types of a dictionary
+ # affect whether a method taking the dictionary as an argument
+ # takes a JSContext* argument or not.
+ if self.isDictionary():
+ return set([self.inner])
+ return set()
+
+
+class IDLPromiseType(IDLParametrizedType):
+ def __init__(self, location, innerType):
+ IDLParametrizedType.__init__(self, location, "Promise", innerType)
+
+ def __hash__(self):
+ return hash(self.promiseInnerType())
+
+ def __eq__(self, other):
+ return (
+ isinstance(other, IDLPromiseType)
+ and self.promiseInnerType() == other.promiseInnerType()
+ )
+
+ def __str__(self):
+ return self.inner.__str__() + "Promise"
+
+ def prettyName(self):
+ return "Promise<%s>" % self.inner.prettyName()
+
+ def isPromise(self):
+ return True
+
+ def promiseInnerType(self):
+ return self.inner
+
+ def tag(self):
+ return IDLType.Tags.promise
+
+ def complete(self, scope):
+ if self.inner.isObservableArray():
+ raise WebIDLError(
+ "The inner type of a promise type must not be an ObservableArray type",
+ [self.location, self.inner.location],
+ )
+
+ self.inner = self.promiseInnerType().complete(scope)
+ return self
+
+ def unroll(self):
+ # We do not unroll our inner. Just stop at ourselves. That
+ # lets us add headers for both ourselves and our inner as
+ # needed.
+ return self
+
+ def isDistinguishableFrom(self, other):
+ # Promises are not distinguishable from anything.
+ return False
+
+ def isExposedInAllOf(self, exposureSet):
+ # Check the internal type
+ return self.promiseInnerType().unroll().isExposedInAllOf(exposureSet)
+
+
+class IDLBuiltinType(IDLType):
+
+ Types = enum(
+ # The integer types
+ "byte",
+ "octet",
+ "short",
+ "unsigned_short",
+ "long",
+ "unsigned_long",
+ "long_long",
+ "unsigned_long_long",
+ # Additional primitive types
+ "boolean",
+ "unrestricted_float",
+ "float",
+ "unrestricted_double",
+ # IMPORTANT: "double" must be the last primitive type listed
+ "double",
+ # Other types
+ "any",
+ "undefined",
+ "domstring",
+ "bytestring",
+ "usvstring",
+ "utf8string",
+ "jsstring",
+ "object",
+ # Funny stuff
+ "ArrayBuffer",
+ "ArrayBufferView",
+ "Int8Array",
+ "Uint8Array",
+ "Uint8ClampedArray",
+ "Int16Array",
+ "Uint16Array",
+ "Int32Array",
+ "Uint32Array",
+ "Float32Array",
+ "Float64Array",
+ )
+
+ TagLookup = {
+ Types.byte: IDLType.Tags.int8,
+ Types.octet: IDLType.Tags.uint8,
+ Types.short: IDLType.Tags.int16,
+ Types.unsigned_short: IDLType.Tags.uint16,
+ Types.long: IDLType.Tags.int32,
+ Types.unsigned_long: IDLType.Tags.uint32,
+ Types.long_long: IDLType.Tags.int64,
+ Types.unsigned_long_long: IDLType.Tags.uint64,
+ Types.boolean: IDLType.Tags.bool,
+ Types.unrestricted_float: IDLType.Tags.unrestricted_float,
+ Types.float: IDLType.Tags.float,
+ Types.unrestricted_double: IDLType.Tags.unrestricted_double,
+ Types.double: IDLType.Tags.double,
+ Types.any: IDLType.Tags.any,
+ Types.undefined: IDLType.Tags.undefined,
+ Types.domstring: IDLType.Tags.domstring,
+ Types.bytestring: IDLType.Tags.bytestring,
+ Types.usvstring: IDLType.Tags.usvstring,
+ Types.utf8string: IDLType.Tags.utf8string,
+ Types.jsstring: IDLType.Tags.jsstring,
+ Types.object: IDLType.Tags.object,
+ Types.ArrayBuffer: IDLType.Tags.interface,
+ Types.ArrayBufferView: IDLType.Tags.interface,
+ Types.Int8Array: IDLType.Tags.interface,
+ Types.Uint8Array: IDLType.Tags.interface,
+ Types.Uint8ClampedArray: IDLType.Tags.interface,
+ Types.Int16Array: IDLType.Tags.interface,
+ Types.Uint16Array: IDLType.Tags.interface,
+ Types.Int32Array: IDLType.Tags.interface,
+ Types.Uint32Array: IDLType.Tags.interface,
+ Types.Float32Array: IDLType.Tags.interface,
+ Types.Float64Array: IDLType.Tags.interface,
+ }
+
+ PrettyNames = {
+ Types.byte: "byte",
+ Types.octet: "octet",
+ Types.short: "short",
+ Types.unsigned_short: "unsigned short",
+ Types.long: "long",
+ Types.unsigned_long: "unsigned long",
+ Types.long_long: "long long",
+ Types.unsigned_long_long: "unsigned long long",
+ Types.boolean: "boolean",
+ Types.unrestricted_float: "unrestricted float",
+ Types.float: "float",
+ Types.unrestricted_double: "unrestricted double",
+ Types.double: "double",
+ Types.any: "any",
+ Types.undefined: "undefined",
+ Types.domstring: "DOMString",
+ Types.bytestring: "ByteString",
+ Types.usvstring: "USVString",
+ Types.utf8string: "USVString", # That's what it is in spec terms
+ Types.jsstring: "USVString", # Again, that's what it is in spec terms
+ Types.object: "object",
+ Types.ArrayBuffer: "ArrayBuffer",
+ Types.ArrayBufferView: "ArrayBufferView",
+ Types.Int8Array: "Int8Array",
+ Types.Uint8Array: "Uint8Array",
+ Types.Uint8ClampedArray: "Uint8ClampedArray",
+ Types.Int16Array: "Int16Array",
+ Types.Uint16Array: "Uint16Array",
+ Types.Int32Array: "Int32Array",
+ Types.Uint32Array: "Uint32Array",
+ Types.Float32Array: "Float32Array",
+ Types.Float64Array: "Float64Array",
+ }
+
+ def __init__(
+ self,
+ location,
+ name,
+ type,
+ clamp=False,
+ enforceRange=False,
+ legacyNullToEmptyString=False,
+ allowShared=False,
+ attrLocation=[],
+ ):
+ """
+ The mutually exclusive clamp/enforceRange/legacyNullToEmptyString/allowShared arguments
+ are used to create instances of this type with the appropriate attributes attached. Use
+ .clamped(), .rangeEnforced(), .withLegacyNullToEmptyString() and .withAllowShared().
+
+ attrLocation is an array of source locations of these attributes for error reporting.
+ """
+ IDLType.__init__(self, location, name)
+ self.builtin = True
+ self._typeTag = type
+ self._clamped = None
+ self._rangeEnforced = None
+ self._withLegacyNullToEmptyString = None
+ self._withAllowShared = None
+ if self.isInteger():
+ if clamp:
+ self._clamp = True
+ self.name = "Clamped" + self.name
+ self._extendedAttrDict["Clamp"] = True
+ elif enforceRange:
+ self._enforceRange = True
+ self.name = "RangeEnforced" + self.name
+ self._extendedAttrDict["EnforceRange"] = True
+ elif clamp or enforceRange:
+ raise WebIDLError(
+ "Non-integer types cannot be [Clamp] or [EnforceRange]", attrLocation
+ )
+ if self.isDOMString() or self.isUTF8String():
+ if legacyNullToEmptyString:
+ self.legacyNullToEmptyString = True
+ self.name = "NullIsEmpty" + self.name
+ self._extendedAttrDict["LegacyNullToEmptyString"] = True
+ elif legacyNullToEmptyString:
+ raise WebIDLError(
+ "Non-string types cannot be [LegacyNullToEmptyString]", attrLocation
+ )
+ if self.isBufferSource():
+ if allowShared:
+ self._allowShared = True
+ self._extendedAttrDict["AllowShared"] = True
+ elif allowShared:
+ raise WebIDLError(
+ "Types that are not buffer source types cannot be [AllowShared]",
+ attrLocation,
+ )
+
+ def __str__(self):
+ if self._allowShared:
+ assert self.isBufferSource()
+ return "MaybeShared" + str(self.name)
+ return str(self.name)
+
+ def prettyName(self):
+ return IDLBuiltinType.PrettyNames[self._typeTag]
+
+ def clamped(self, attrLocation):
+ if not self._clamped:
+ self._clamped = IDLBuiltinType(
+ self.location,
+ self.name,
+ self._typeTag,
+ clamp=True,
+ attrLocation=attrLocation,
+ )
+ return self._clamped
+
+ def rangeEnforced(self, attrLocation):
+ if not self._rangeEnforced:
+ self._rangeEnforced = IDLBuiltinType(
+ self.location,
+ self.name,
+ self._typeTag,
+ enforceRange=True,
+ attrLocation=attrLocation,
+ )
+ return self._rangeEnforced
+
+ def withLegacyNullToEmptyString(self, attrLocation):
+ if not self._withLegacyNullToEmptyString:
+ self._withLegacyNullToEmptyString = IDLBuiltinType(
+ self.location,
+ self.name,
+ self._typeTag,
+ legacyNullToEmptyString=True,
+ attrLocation=attrLocation,
+ )
+ return self._withLegacyNullToEmptyString
+
+ def withAllowShared(self, attrLocation):
+ if not self._withAllowShared:
+ self._withAllowShared = IDLBuiltinType(
+ self.location,
+ self.name,
+ self._typeTag,
+ allowShared=True,
+ attrLocation=attrLocation,
+ )
+ return self._withAllowShared
+
+ def isPrimitive(self):
+ return self._typeTag <= IDLBuiltinType.Types.double
+
+ def isBoolean(self):
+ return self._typeTag == IDLBuiltinType.Types.boolean
+
+ def isUndefined(self):
+ return self._typeTag == IDLBuiltinType.Types.undefined
+
+ def isNumeric(self):
+ return self.isPrimitive() and not self.isBoolean()
+
+ def isString(self):
+ return (
+ self._typeTag == IDLBuiltinType.Types.domstring
+ or self._typeTag == IDLBuiltinType.Types.bytestring
+ or self._typeTag == IDLBuiltinType.Types.usvstring
+ or self._typeTag == IDLBuiltinType.Types.utf8string
+ or self._typeTag == IDLBuiltinType.Types.jsstring
+ )
+
+ def isByteString(self):
+ return self._typeTag == IDLBuiltinType.Types.bytestring
+
+ def isDOMString(self):
+ return self._typeTag == IDLBuiltinType.Types.domstring
+
+ def isUSVString(self):
+ return self._typeTag == IDLBuiltinType.Types.usvstring
+
+ def isUTF8String(self):
+ return self._typeTag == IDLBuiltinType.Types.utf8string
+
+ def isJSString(self):
+ return self._typeTag == IDLBuiltinType.Types.jsstring
+
+ def isInteger(self):
+ return self._typeTag <= IDLBuiltinType.Types.unsigned_long_long
+
+ def isArrayBuffer(self):
+ return self._typeTag == IDLBuiltinType.Types.ArrayBuffer
+
+ def isArrayBufferView(self):
+ return self._typeTag == IDLBuiltinType.Types.ArrayBufferView
+
+ def isTypedArray(self):
+ return (
+ self._typeTag >= IDLBuiltinType.Types.Int8Array
+ and self._typeTag <= IDLBuiltinType.Types.Float64Array
+ )
+
+ def isInterface(self):
+ # TypedArray things are interface types per the TypedArray spec,
+ # but we handle them as builtins because SpiderMonkey implements
+ # all of it internally.
+ return self.isArrayBuffer() or self.isArrayBufferView() or self.isTypedArray()
+
+ def isNonCallbackInterface(self):
+ # All the interfaces we can be are non-callback
+ return self.isInterface()
+
+ def isFloat(self):
+ return (
+ self._typeTag == IDLBuiltinType.Types.float
+ or self._typeTag == IDLBuiltinType.Types.double
+ or self._typeTag == IDLBuiltinType.Types.unrestricted_float
+ or self._typeTag == IDLBuiltinType.Types.unrestricted_double
+ )
+
+ def isUnrestricted(self):
+ assert self.isFloat()
+ return (
+ self._typeTag == IDLBuiltinType.Types.unrestricted_float
+ or self._typeTag == IDLBuiltinType.Types.unrestricted_double
+ )
+
+ def isJSONType(self):
+ return self.isPrimitive() or self.isString() or self.isObject()
+
+ def includesRestrictedFloat(self):
+ return self.isFloat() and not self.isUnrestricted()
+
+ def tag(self):
+ return IDLBuiltinType.TagLookup[self._typeTag]
+
+ def isDistinguishableFrom(self, other):
+ if other.isPromise():
+ return False
+ if other.isUnion():
+ # Just forward to the union; it'll deal
+ return other.isDistinguishableFrom(self)
+ if self.isUndefined():
+ return not (other.isUndefined() or other.isDictionaryLike())
+ if self.isPrimitive():
+ if (
+ other.isUndefined()
+ or other.isString()
+ or other.isEnum()
+ or other.isInterface()
+ or other.isObject()
+ or other.isCallback()
+ or other.isDictionary()
+ or other.isSequence()
+ or other.isRecord()
+ ):
+ return True
+ if self.isBoolean():
+ return other.isNumeric()
+ assert self.isNumeric()
+ return other.isBoolean()
+ if self.isString():
+ return (
+ other.isUndefined()
+ or other.isPrimitive()
+ or other.isInterface()
+ or other.isObject()
+ or other.isCallback()
+ or other.isDictionary()
+ or other.isSequence()
+ or other.isRecord()
+ )
+ if self.isAny():
+ # Can't tell "any" apart from anything
+ return False
+ if self.isObject():
+ return (
+ other.isUndefined()
+ or other.isPrimitive()
+ or other.isString()
+ or other.isEnum()
+ )
+ # Not much else we could be!
+ assert self.isSpiderMonkeyInterface()
+ # Like interfaces, but we know we're not a callback
+ return (
+ other.isUndefined()
+ or other.isPrimitive()
+ or other.isString()
+ or other.isEnum()
+ or other.isCallback()
+ or other.isDictionary()
+ or other.isSequence()
+ or other.isRecord()
+ or (
+ other.isInterface()
+ and (
+ # ArrayBuffer is distinguishable from everything
+ # that's not an ArrayBuffer or a callback interface
+ (self.isArrayBuffer() and not other.isArrayBuffer())
+ or
+ # ArrayBufferView is distinguishable from everything
+ # that's not an ArrayBufferView or typed array.
+ (
+ self.isArrayBufferView()
+ and not other.isArrayBufferView()
+ and not other.isTypedArray()
+ )
+ or
+ # Typed arrays are distinguishable from everything
+ # except ArrayBufferView and the same type of typed
+ # array
+ (
+ self.isTypedArray()
+ and not other.isArrayBufferView()
+ and not (other.isTypedArray() and other.name == self.name)
+ )
+ )
+ )
+ )
+
+ def _getDependentObjects(self):
+ return set()
+
+ def withExtendedAttributes(self, attrs):
+ ret = self
+ for attribute in attrs:
+ identifier = attribute.identifier()
+ if identifier == "Clamp":
+ if not attribute.noArguments():
+ raise WebIDLError(
+ "[Clamp] must take no arguments", [attribute.location]
+ )
+ if ret.hasEnforceRange() or self._enforceRange:
+ raise WebIDLError(
+ "[EnforceRange] and [Clamp] are mutually exclusive",
+ [self.location, attribute.location],
+ )
+ ret = self.clamped([self.location, attribute.location])
+ elif identifier == "EnforceRange":
+ if not attribute.noArguments():
+ raise WebIDLError(
+ "[EnforceRange] must take no arguments", [attribute.location]
+ )
+ if ret.hasClamp() or self._clamp:
+ raise WebIDLError(
+ "[EnforceRange] and [Clamp] are mutually exclusive",
+ [self.location, attribute.location],
+ )
+ ret = self.rangeEnforced([self.location, attribute.location])
+ elif identifier == "LegacyNullToEmptyString":
+ if not (self.isDOMString() or self.isUTF8String()):
+ raise WebIDLError(
+ "[LegacyNullToEmptyString] only allowed on DOMStrings and UTF8Strings",
+ [self.location, attribute.location],
+ )
+ assert not self.nullable()
+ if attribute.hasValue():
+ raise WebIDLError(
+ "[LegacyNullToEmptyString] must take no identifier argument",
+ [attribute.location],
+ )
+ ret = self.withLegacyNullToEmptyString(
+ [self.location, attribute.location]
+ )
+ elif identifier == "AllowShared":
+ if not attribute.noArguments():
+ raise WebIDLError(
+ "[AllowShared] must take no arguments", [attribute.location]
+ )
+ if not self.isBufferSource():
+ raise WebIDLError(
+ "[AllowShared] only allowed on buffer source types",
+ [self.location, attribute.location],
+ )
+ ret = self.withAllowShared([self.location, attribute.location])
+
+ else:
+ raise WebIDLError(
+ "Unhandled extended attribute on type",
+ [self.location, attribute.location],
+ )
+ return ret
+
+
+BuiltinTypes = {
+ IDLBuiltinType.Types.byte: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"), "Byte", IDLBuiltinType.Types.byte
+ ),
+ IDLBuiltinType.Types.octet: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"), "Octet", IDLBuiltinType.Types.octet
+ ),
+ IDLBuiltinType.Types.short: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"), "Short", IDLBuiltinType.Types.short
+ ),
+ IDLBuiltinType.Types.unsigned_short: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"),
+ "UnsignedShort",
+ IDLBuiltinType.Types.unsigned_short,
+ ),
+ IDLBuiltinType.Types.long: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"), "Long", IDLBuiltinType.Types.long
+ ),
+ IDLBuiltinType.Types.unsigned_long: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"),
+ "UnsignedLong",
+ IDLBuiltinType.Types.unsigned_long,
+ ),
+ IDLBuiltinType.Types.long_long: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"), "LongLong", IDLBuiltinType.Types.long_long
+ ),
+ IDLBuiltinType.Types.unsigned_long_long: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"),
+ "UnsignedLongLong",
+ IDLBuiltinType.Types.unsigned_long_long,
+ ),
+ IDLBuiltinType.Types.undefined: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"), "Undefined", IDLBuiltinType.Types.undefined
+ ),
+ IDLBuiltinType.Types.boolean: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"), "Boolean", IDLBuiltinType.Types.boolean
+ ),
+ IDLBuiltinType.Types.float: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"), "Float", IDLBuiltinType.Types.float
+ ),
+ IDLBuiltinType.Types.unrestricted_float: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"),
+ "UnrestrictedFloat",
+ IDLBuiltinType.Types.unrestricted_float,
+ ),
+ IDLBuiltinType.Types.double: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"), "Double", IDLBuiltinType.Types.double
+ ),
+ IDLBuiltinType.Types.unrestricted_double: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"),
+ "UnrestrictedDouble",
+ IDLBuiltinType.Types.unrestricted_double,
+ ),
+ IDLBuiltinType.Types.any: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"), "Any", IDLBuiltinType.Types.any
+ ),
+ IDLBuiltinType.Types.domstring: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"), "String", IDLBuiltinType.Types.domstring
+ ),
+ IDLBuiltinType.Types.bytestring: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"), "ByteString", IDLBuiltinType.Types.bytestring
+ ),
+ IDLBuiltinType.Types.usvstring: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"), "USVString", IDLBuiltinType.Types.usvstring
+ ),
+ IDLBuiltinType.Types.utf8string: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"), "UTF8String", IDLBuiltinType.Types.utf8string
+ ),
+ IDLBuiltinType.Types.jsstring: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"), "JSString", IDLBuiltinType.Types.jsstring
+ ),
+ IDLBuiltinType.Types.object: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"), "Object", IDLBuiltinType.Types.object
+ ),
+ IDLBuiltinType.Types.ArrayBuffer: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"),
+ "ArrayBuffer",
+ IDLBuiltinType.Types.ArrayBuffer,
+ ),
+ IDLBuiltinType.Types.ArrayBufferView: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"),
+ "ArrayBufferView",
+ IDLBuiltinType.Types.ArrayBufferView,
+ ),
+ IDLBuiltinType.Types.Int8Array: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"), "Int8Array", IDLBuiltinType.Types.Int8Array
+ ),
+ IDLBuiltinType.Types.Uint8Array: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"), "Uint8Array", IDLBuiltinType.Types.Uint8Array
+ ),
+ IDLBuiltinType.Types.Uint8ClampedArray: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"),
+ "Uint8ClampedArray",
+ IDLBuiltinType.Types.Uint8ClampedArray,
+ ),
+ IDLBuiltinType.Types.Int16Array: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"), "Int16Array", IDLBuiltinType.Types.Int16Array
+ ),
+ IDLBuiltinType.Types.Uint16Array: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"),
+ "Uint16Array",
+ IDLBuiltinType.Types.Uint16Array,
+ ),
+ IDLBuiltinType.Types.Int32Array: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"), "Int32Array", IDLBuiltinType.Types.Int32Array
+ ),
+ IDLBuiltinType.Types.Uint32Array: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"),
+ "Uint32Array",
+ IDLBuiltinType.Types.Uint32Array,
+ ),
+ IDLBuiltinType.Types.Float32Array: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"),
+ "Float32Array",
+ IDLBuiltinType.Types.Float32Array,
+ ),
+ IDLBuiltinType.Types.Float64Array: IDLBuiltinType(
+ BuiltinLocation("<builtin type>"),
+ "Float64Array",
+ IDLBuiltinType.Types.Float64Array,
+ ),
+}
+
+
+integerTypeSizes = {
+ IDLBuiltinType.Types.byte: (-128, 127),
+ IDLBuiltinType.Types.octet: (0, 255),
+ IDLBuiltinType.Types.short: (-32768, 32767),
+ IDLBuiltinType.Types.unsigned_short: (0, 65535),
+ IDLBuiltinType.Types.long: (-2147483648, 2147483647),
+ IDLBuiltinType.Types.unsigned_long: (0, 4294967295),
+ IDLBuiltinType.Types.long_long: (-9223372036854775808, 9223372036854775807),
+ IDLBuiltinType.Types.unsigned_long_long: (0, 18446744073709551615),
+}
+
+
+def matchIntegerValueToType(value):
+ for type, extremes in integerTypeSizes.items():
+ (min, max) = extremes
+ if value <= max and value >= min:
+ return BuiltinTypes[type]
+
+ return None
+
+
+class NoCoercionFoundError(WebIDLError):
+ """
+ A class we use to indicate generic coercion failures because none of the
+ types worked out in IDLValue.coerceToType.
+ """
+
+
+class IDLValue(IDLObject):
+ def __init__(self, location, type, value):
+ IDLObject.__init__(self, location)
+ self.type = type
+ assert isinstance(type, IDLType)
+
+ self.value = value
+
+ def coerceToType(self, type, location):
+ if type == self.type:
+ return self # Nothing to do
+
+ # We first check for unions to ensure that even if the union is nullable
+ # we end up with the right flat member type, not the union's type.
+ if type.isUnion():
+ # We use the flat member types here, because if we have a nullable
+ # member type, or a nested union, we want the type the value
+ # actually coerces to, not the nullable or nested union type.
+ for subtype in type.unroll().flatMemberTypes:
+ try:
+ coercedValue = self.coerceToType(subtype, location)
+ # Create a new IDLValue to make sure that we have the
+ # correct float/double type. This is necessary because we
+ # use the value's type when it is a default value of a
+ # union, and the union cares about the exact float type.
+ return IDLValue(self.location, subtype, coercedValue.value)
+ except Exception as e:
+ # Make sure to propagate out WebIDLErrors that are not the
+ # generic "hey, we could not coerce to this type at all"
+ # exception, because those are specific "coercion failed for
+ # reason X" exceptions. Note that we want to swallow
+ # non-WebIDLErrors here, because those can just happen if
+ # "type" is not something that can have a default value at
+ # all.
+ if isinstance(e, WebIDLError) and not isinstance(
+ e, NoCoercionFoundError
+ ):
+ raise e
+
+ # If the type allows null, rerun this matching on the inner type, except
+ # nullable enums. We handle those specially, because we want our
+ # default string values to stay strings even when assigned to a nullable
+ # enum.
+ elif type.nullable() and not type.isEnum():
+ innerValue = self.coerceToType(type.inner, location)
+ return IDLValue(self.location, type, innerValue.value)
+
+ elif self.type.isInteger() and type.isInteger():
+ # We're both integer types. See if we fit.
+
+ (min, max) = integerTypeSizes[type._typeTag]
+ if self.value <= max and self.value >= min:
+ # Promote
+ return IDLValue(self.location, type, self.value)
+ else:
+ raise WebIDLError(
+ "Value %s is out of range for type %s." % (self.value, type),
+ [location],
+ )
+ elif self.type.isInteger() and type.isFloat():
+ # Convert an integer literal into float
+ if -(2 ** 24) <= self.value <= 2 ** 24:
+ return IDLValue(self.location, type, float(self.value))
+ else:
+ raise WebIDLError(
+ "Converting value %s to %s will lose precision."
+ % (self.value, type),
+ [location],
+ )
+ elif self.type.isString() and type.isEnum():
+ # Just keep our string, but make sure it's a valid value for this enum
+ enum = type.unroll().inner
+ if self.value not in enum.values():
+ raise WebIDLError(
+ "'%s' is not a valid default value for enum %s"
+ % (self.value, enum.identifier.name),
+ [location, enum.location],
+ )
+ return self
+ elif self.type.isFloat() and type.isFloat():
+ if not type.isUnrestricted() and (
+ self.value == float("inf")
+ or self.value == float("-inf")
+ or math.isnan(self.value)
+ ):
+ raise WebIDLError(
+ "Trying to convert unrestricted value %s to non-unrestricted"
+ % self.value,
+ [location],
+ )
+ return IDLValue(self.location, type, self.value)
+ elif self.type.isString() and type.isUSVString():
+ # Allow USVStrings to use default value just like
+ # DOMString. No coercion is required in this case as Codegen.py
+ # treats USVString just like DOMString, but with an
+ # extra normalization step.
+ assert self.type.isDOMString()
+ return self
+ elif self.type.isString() and (
+ type.isByteString() or type.isJSString() or type.isUTF8String()
+ ):
+ # Allow ByteStrings, UTF8String, and JSStrings to use a default
+ # value like DOMString.
+ # No coercion is required as Codegen.py will handle the
+ # extra steps. We want to make sure that our string contains
+ # only valid characters, so we check that here.
+ valid_ascii_lit = (
+ " " + string.ascii_letters + string.digits + string.punctuation
+ )
+ for idx, c in enumerate(self.value):
+ if c not in valid_ascii_lit:
+ raise WebIDLError(
+ "Coercing this string literal %s to a ByteString is not supported yet. "
+ "Coercion failed due to an unsupported byte %d at index %d."
+ % (self.value.__repr__(), ord(c), idx),
+ [location],
+ )
+
+ return IDLValue(self.location, type, self.value)
+ elif self.type.isDOMString() and type.legacyNullToEmptyString:
+ # LegacyNullToEmptyString is a different type for resolution reasons,
+ # however once you have a value it doesn't matter
+ return self
+
+ raise NoCoercionFoundError(
+ "Cannot coerce type %s to type %s." % (self.type, type), [location]
+ )
+
+ def _getDependentObjects(self):
+ return set()
+
+
+class IDLNullValue(IDLObject):
+ def __init__(self, location):
+ IDLObject.__init__(self, location)
+ self.type = None
+ self.value = None
+
+ def coerceToType(self, type, location):
+ if (
+ not isinstance(type, IDLNullableType)
+ and not (type.isUnion() and type.hasNullableType)
+ and not type.isAny()
+ ):
+ raise WebIDLError("Cannot coerce null value to type %s." % type, [location])
+
+ nullValue = IDLNullValue(self.location)
+ if type.isUnion() and not type.nullable() and type.hasDictionaryType():
+ # We're actually a default value for the union's dictionary member.
+ # Use its type.
+ for t in type.flatMemberTypes:
+ if t.isDictionary():
+ nullValue.type = t
+ return nullValue
+ nullValue.type = type
+ return nullValue
+
+ def _getDependentObjects(self):
+ return set()
+
+
+class IDLEmptySequenceValue(IDLObject):
+ def __init__(self, location):
+ IDLObject.__init__(self, location)
+ self.type = None
+ self.value = None
+
+ def coerceToType(self, type, location):
+ if type.isUnion():
+ # We use the flat member types here, because if we have a nullable
+ # member type, or a nested union, we want the type the value
+ # actually coerces to, not the nullable or nested union type.
+ for subtype in type.unroll().flatMemberTypes:
+ try:
+ return self.coerceToType(subtype, location)
+ except Exception:
+ pass
+
+ if not type.isSequence():
+ raise WebIDLError(
+ "Cannot coerce empty sequence value to type %s." % type, [location]
+ )
+
+ emptySequenceValue = IDLEmptySequenceValue(self.location)
+ emptySequenceValue.type = type
+ return emptySequenceValue
+
+ def _getDependentObjects(self):
+ return set()
+
+
+class IDLDefaultDictionaryValue(IDLObject):
+ def __init__(self, location):
+ IDLObject.__init__(self, location)
+ self.type = None
+ self.value = None
+
+ def coerceToType(self, type, location):
+ if type.isUnion():
+ # We use the flat member types here, because if we have a nullable
+ # member type, or a nested union, we want the type the value
+ # actually coerces to, not the nullable or nested union type.
+ for subtype in type.unroll().flatMemberTypes:
+ try:
+ return self.coerceToType(subtype, location)
+ except Exception:
+ pass
+
+ if not type.isDictionary():
+ raise WebIDLError(
+ "Cannot coerce default dictionary value to type %s." % type, [location]
+ )
+
+ defaultDictionaryValue = IDLDefaultDictionaryValue(self.location)
+ defaultDictionaryValue.type = type
+ return defaultDictionaryValue
+
+ def _getDependentObjects(self):
+ return set()
+
+
+class IDLUndefinedValue(IDLObject):
+ def __init__(self, location):
+ IDLObject.__init__(self, location)
+ self.type = None
+ self.value = None
+
+ def coerceToType(self, type, location):
+ if not type.isAny():
+ raise WebIDLError(
+ "Cannot coerce undefined value to type %s." % type, [location]
+ )
+
+ undefinedValue = IDLUndefinedValue(self.location)
+ undefinedValue.type = type
+ return undefinedValue
+
+ def _getDependentObjects(self):
+ return set()
+
+
+class IDLInterfaceMember(IDLObjectWithIdentifier, IDLExposureMixins):
+
+ Tags = enum(
+ "Const", "Attr", "Method", "MaplikeOrSetlike", "AsyncIterable", "Iterable"
+ )
+
+ Special = enum("Static", "Stringifier")
+
+ AffectsValues = ("Nothing", "Everything")
+ DependsOnValues = ("Nothing", "DOMState", "DeviceState", "Everything")
+
+ def __init__(self, location, identifier, tag, extendedAttrDict=None):
+ IDLObjectWithIdentifier.__init__(self, location, None, identifier)
+ IDLExposureMixins.__init__(self, location)
+ self.tag = tag
+ if extendedAttrDict is None:
+ self._extendedAttrDict = {}
+ else:
+ self._extendedAttrDict = extendedAttrDict
+
+ def isMethod(self):
+ return self.tag == IDLInterfaceMember.Tags.Method
+
+ def isAttr(self):
+ return self.tag == IDLInterfaceMember.Tags.Attr
+
+ def isConst(self):
+ return self.tag == IDLInterfaceMember.Tags.Const
+
+ def isMaplikeOrSetlikeOrIterable(self):
+ return (
+ self.tag == IDLInterfaceMember.Tags.MaplikeOrSetlike
+ or self.tag == IDLInterfaceMember.Tags.AsyncIterable
+ or self.tag == IDLInterfaceMember.Tags.Iterable
+ )
+
+ def isMaplikeOrSetlike(self):
+ return self.tag == IDLInterfaceMember.Tags.MaplikeOrSetlike
+
+ def addExtendedAttributes(self, attrs):
+ for attr in attrs:
+ self.handleExtendedAttribute(attr)
+ attrlist = attr.listValue()
+ self._extendedAttrDict[attr.identifier()] = (
+ attrlist if len(attrlist) else True
+ )
+
+ def handleExtendedAttribute(self, attr):
+ pass
+
+ def getExtendedAttribute(self, name):
+ return self._extendedAttrDict.get(name, None)
+
+ def finish(self, scope):
+ IDLExposureMixins.finish(self, scope)
+
+ def validate(self):
+ if self.isAttr() or self.isMethod():
+ if self.affects == "Everything" and self.dependsOn != "Everything":
+ raise WebIDLError(
+ "Interface member is flagged as affecting "
+ "everything but not depending on everything. "
+ "That seems rather unlikely.",
+ [self.location],
+ )
+
+ if self.getExtendedAttribute("NewObject"):
+ if self.dependsOn == "Nothing" or self.dependsOn == "DOMState":
+ raise WebIDLError(
+ "A [NewObject] method is not idempotent, "
+ "so it has to depend on something other than DOM state.",
+ [self.location],
+ )
+ if self.getExtendedAttribute("Cached") or self.getExtendedAttribute(
+ "StoreInSlot"
+ ):
+ raise WebIDLError(
+ "A [NewObject] attribute shouldnt be "
+ "[Cached] or [StoreInSlot], since the point "
+ "of those is to keep returning the same "
+ "thing across multiple calls, which is not "
+ "what [NewObject] does.",
+ [self.location],
+ )
+
+ def _setDependsOn(self, dependsOn):
+ if self.dependsOn != "Everything":
+ raise WebIDLError(
+ "Trying to specify multiple different DependsOn, "
+ "Pure, or Constant extended attributes for "
+ "attribute",
+ [self.location],
+ )
+ if dependsOn not in IDLInterfaceMember.DependsOnValues:
+ raise WebIDLError(
+ "Invalid [DependsOn=%s] on attribute" % dependsOn, [self.location]
+ )
+ self.dependsOn = dependsOn
+
+ def _setAffects(self, affects):
+ if self.affects != "Everything":
+ raise WebIDLError(
+ "Trying to specify multiple different Affects, "
+ "Pure, or Constant extended attributes for "
+ "attribute",
+ [self.location],
+ )
+ if affects not in IDLInterfaceMember.AffectsValues:
+ raise WebIDLError(
+ "Invalid [Affects=%s] on attribute" % affects, [self.location]
+ )
+ self.affects = affects
+
+ def _addAlias(self, alias):
+ if alias in self.aliases:
+ raise WebIDLError(
+ "Duplicate [Alias=%s] on attribute" % alias, [self.location]
+ )
+ self.aliases.append(alias)
+
+ def _addBindingAlias(self, bindingAlias):
+ if bindingAlias in self.bindingAliases:
+ raise WebIDLError(
+ "Duplicate [BindingAlias=%s] on attribute" % bindingAlias,
+ [self.location],
+ )
+ self.bindingAliases.append(bindingAlias)
+
+
+class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember):
+ def __init__(self, location, identifier, ifaceType, keyType, valueType, ifaceKind):
+ IDLInterfaceMember.__init__(self, location, identifier, ifaceKind)
+ if keyType is not None:
+ assert isinstance(keyType, IDLType)
+ else:
+ assert valueType is not None
+ assert ifaceType in ["maplike", "setlike", "iterable", "asynciterable"]
+ if valueType is not None:
+ assert isinstance(valueType, IDLType)
+ self.keyType = keyType
+ self.valueType = valueType
+ self.maplikeOrSetlikeOrIterableType = ifaceType
+ self.disallowedMemberNames = []
+ self.disallowedNonMethodNames = []
+
+ def isMaplike(self):
+ return self.maplikeOrSetlikeOrIterableType == "maplike"
+
+ def isSetlike(self):
+ return self.maplikeOrSetlikeOrIterableType == "setlike"
+
+ def isIterable(self):
+ return self.maplikeOrSetlikeOrIterableType == "iterable"
+
+ def isAsyncIterable(self):
+ return self.maplikeOrSetlikeOrIterableType == "asynciterable"
+
+ def hasKeyType(self):
+ return self.keyType is not None
+
+ def hasValueType(self):
+ return self.valueType is not None
+
+ def checkCollisions(self, members, isAncestor):
+ for member in members:
+ # Check that there are no disallowed members
+ if member.identifier.name in self.disallowedMemberNames and not (
+ (
+ member.isMethod()
+ and (
+ member.isStatic() or member.isMaplikeOrSetlikeOrIterableMethod()
+ )
+ )
+ or (member.isAttr() and member.isMaplikeOrSetlikeAttr())
+ ):
+ raise WebIDLError(
+ "Member '%s' conflicts "
+ "with reserved %s name."
+ % (member.identifier.name, self.maplikeOrSetlikeOrIterableType),
+ [self.location, member.location],
+ )
+ # Check that there are no disallowed non-method members.
+ # Ancestor members are always disallowed here; own members
+ # are disallowed only if they're non-methods.
+ if (
+ isAncestor or member.isAttr() or member.isConst()
+ ) and member.identifier.name in self.disallowedNonMethodNames:
+ raise WebIDLError(
+ "Member '%s' conflicts "
+ "with reserved %s method."
+ % (member.identifier.name, self.maplikeOrSetlikeOrIterableType),
+ [self.location, member.location],
+ )
+
+ def addMethod(
+ self,
+ name,
+ members,
+ allowExistingOperations,
+ returnType,
+ args=[],
+ chromeOnly=False,
+ isPure=False,
+ affectsNothing=False,
+ newObject=False,
+ isIteratorAlias=False,
+ ):
+ """
+ Create an IDLMethod based on the parameters passed in.
+
+ - members is the member list to add this function to, since this is
+ called during the member expansion portion of interface object
+ building.
+
+ - chromeOnly is only True for read-only js implemented classes, to
+ implement underscore prefixed convenience functions which would
+ otherwise not be available, unlike the case of C++ bindings.
+
+ - isPure is only True for idempotent functions, so it is not valid for
+ things like keys, values, etc. that return a new object every time.
+
+ - affectsNothing means that nothing changes due to this method, which
+ affects JIT optimization behavior
+
+ - newObject means the method creates and returns a new object.
+
+ """
+ # Only add name to lists for collision checks if it's not chrome
+ # only.
+ if chromeOnly:
+ name = "__" + name
+ else:
+ if not allowExistingOperations:
+ self.disallowedMemberNames.append(name)
+ else:
+ self.disallowedNonMethodNames.append(name)
+ # If allowExistingOperations is True, and another operation exists
+ # with the same name as the one we're trying to add, don't add the
+ # maplike/setlike operation.
+ if allowExistingOperations:
+ for m in members:
+ if m.identifier.name == name and m.isMethod() and not m.isStatic():
+ return
+ method = IDLMethod(
+ self.location,
+ IDLUnresolvedIdentifier(
+ self.location, name, allowDoubleUnderscore=chromeOnly
+ ),
+ returnType,
+ args,
+ maplikeOrSetlikeOrIterable=self,
+ )
+ # We need to be able to throw from declaration methods
+ method.addExtendedAttributes([IDLExtendedAttribute(self.location, ("Throws",))])
+ if chromeOnly:
+ method.addExtendedAttributes(
+ [IDLExtendedAttribute(self.location, ("ChromeOnly",))]
+ )
+ if isPure:
+ method.addExtendedAttributes(
+ [IDLExtendedAttribute(self.location, ("Pure",))]
+ )
+ # Following attributes are used for keys/values/entries. Can't mark
+ # them pure, since they return a new object each time they are run.
+ if affectsNothing:
+ method.addExtendedAttributes(
+ [
+ IDLExtendedAttribute(self.location, ("DependsOn", "Everything")),
+ IDLExtendedAttribute(self.location, ("Affects", "Nothing")),
+ ]
+ )
+ if newObject:
+ method.addExtendedAttributes(
+ [IDLExtendedAttribute(self.location, ("NewObject",))]
+ )
+ if isIteratorAlias:
+ if not self.isAsyncIterable():
+ method.addExtendedAttributes(
+ [IDLExtendedAttribute(self.location, ("Alias", "@@iterator"))]
+ )
+ else:
+ method.addExtendedAttributes(
+ [IDLExtendedAttribute(self.location, ("Alias", "@@asyncIterator"))]
+ )
+ members.append(method)
+
+ def resolve(self, parentScope):
+ if self.keyType:
+ self.keyType.resolveType(parentScope)
+ if self.valueType:
+ self.valueType.resolveType(parentScope)
+
+ def finish(self, scope):
+ IDLInterfaceMember.finish(self, scope)
+ if self.keyType and not self.keyType.isComplete():
+ t = self.keyType.complete(scope)
+
+ assert not isinstance(t, IDLUnresolvedType)
+ assert not isinstance(t, IDLTypedefType)
+ assert not isinstance(t.name, IDLUnresolvedIdentifier)
+ self.keyType = t
+ if self.valueType and not self.valueType.isComplete():
+ t = self.valueType.complete(scope)
+
+ assert not isinstance(t, IDLUnresolvedType)
+ assert not isinstance(t, IDLTypedefType)
+ assert not isinstance(t.name, IDLUnresolvedIdentifier)
+ self.valueType = t
+
+ def validate(self):
+ IDLInterfaceMember.validate(self)
+
+ def handleExtendedAttribute(self, attr):
+ IDLInterfaceMember.handleExtendedAttribute(self, attr)
+
+ def _getDependentObjects(self):
+ deps = set()
+ if self.keyType:
+ deps.add(self.keyType)
+ if self.valueType:
+ deps.add(self.valueType)
+ return deps
+
+ def getForEachArguments(self):
+ return [
+ IDLArgument(
+ self.location,
+ IDLUnresolvedIdentifier(
+ BuiltinLocation("<auto-generated-identifier>"), "callback"
+ ),
+ BuiltinTypes[IDLBuiltinType.Types.object],
+ ),
+ IDLArgument(
+ self.location,
+ IDLUnresolvedIdentifier(
+ BuiltinLocation("<auto-generated-identifier>"), "thisArg"
+ ),
+ BuiltinTypes[IDLBuiltinType.Types.any],
+ optional=True,
+ ),
+ ]
+
+
+# Iterable adds ES6 iterator style functions and traits
+# (keys/values/entries/@@iterator) to an interface.
+class IDLIterable(IDLMaplikeOrSetlikeOrIterableBase):
+ def __init__(self, location, identifier, keyType, valueType, scope):
+ IDLMaplikeOrSetlikeOrIterableBase.__init__(
+ self,
+ location,
+ identifier,
+ "iterable",
+ keyType,
+ valueType,
+ IDLInterfaceMember.Tags.Iterable,
+ )
+ self.iteratorType = None
+
+ def __str__(self):
+ return "declared iterable with key '%s' and value '%s'" % (
+ self.keyType,
+ self.valueType,
+ )
+
+ def expand(self, members):
+ """
+ In order to take advantage of all of the method machinery in Codegen,
+ we generate our functions as if they were part of the interface
+ specification during parsing.
+ """
+ # We only need to add entries/keys/values here if we're a pair iterator.
+ # Value iterators just copy these from %ArrayPrototype% instead.
+ if not self.isPairIterator():
+ return
+
+ # object entries()
+ self.addMethod(
+ "entries",
+ members,
+ False,
+ self.iteratorType,
+ affectsNothing=True,
+ newObject=True,
+ isIteratorAlias=True,
+ )
+ # object keys()
+ self.addMethod(
+ "keys",
+ members,
+ False,
+ self.iteratorType,
+ affectsNothing=True,
+ newObject=True,
+ )
+ # object values()
+ self.addMethod(
+ "values",
+ members,
+ False,
+ self.iteratorType,
+ affectsNothing=True,
+ newObject=True,
+ )
+
+ # undefined forEach(callback(valueType, keyType), optional any thisArg)
+ self.addMethod(
+ "forEach",
+ members,
+ False,
+ BuiltinTypes[IDLBuiltinType.Types.undefined],
+ self.getForEachArguments(),
+ )
+
+ def isValueIterator(self):
+ return not self.isPairIterator()
+
+ def isPairIterator(self):
+ return self.hasKeyType()
+
+
+class IDLAsyncIterable(IDLMaplikeOrSetlikeOrIterableBase):
+ def __init__(self, location, identifier, keyType, valueType, argList, scope):
+ for arg in argList:
+ if not arg.optional:
+ raise WebIDLError(
+ "The arguments of the asynchronously iterable declaration on "
+ "%s must all be optional arguments." % identifier,
+ [arg.location],
+ )
+
+ IDLMaplikeOrSetlikeOrIterableBase.__init__(
+ self,
+ location,
+ identifier,
+ "asynciterable",
+ keyType,
+ valueType,
+ IDLInterfaceMember.Tags.AsyncIterable,
+ )
+ self.iteratorType = None
+ self.argList = argList
+
+ def __str__(self):
+ return "declared async iterable with key '%s' and value '%s'" % (
+ self.keyType,
+ self.valueType,
+ )
+
+ def expand(self, members):
+ """
+ In order to take advantage of all of the method machinery in Codegen,
+ we generate our functions as if they were part of the interface
+ specification during parsing.
+ """
+ # object values()
+ self.addMethod(
+ "values",
+ members,
+ False,
+ self.iteratorType,
+ self.argList,
+ affectsNothing=True,
+ newObject=True,
+ isIteratorAlias=(not self.isPairIterator()),
+ )
+
+ # We only need to add entries/keys here if we're a pair iterator.
+ if not self.isPairIterator():
+ return
+
+ # Methods can't share their IDLArguments, so we need to make copies here.
+ def copyArgList(argList):
+ return map(copy.copy, argList)
+
+ # object entries()
+ self.addMethod(
+ "entries",
+ members,
+ False,
+ self.iteratorType,
+ copyArgList(self.argList),
+ affectsNothing=True,
+ newObject=True,
+ isIteratorAlias=True,
+ )
+ # object keys()
+ self.addMethod(
+ "keys",
+ members,
+ False,
+ self.iteratorType,
+ copyArgList(self.argList),
+ affectsNothing=True,
+ newObject=True,
+ )
+
+ def isValueIterator(self):
+ return not self.isPairIterator()
+
+ def isPairIterator(self):
+ return self.hasKeyType()
+
+
+# MaplikeOrSetlike adds ES6 map-or-set-like traits to an interface.
+class IDLMaplikeOrSetlike(IDLMaplikeOrSetlikeOrIterableBase):
+ def __init__(
+ self, location, identifier, maplikeOrSetlikeType, readonly, keyType, valueType
+ ):
+ IDLMaplikeOrSetlikeOrIterableBase.__init__(
+ self,
+ location,
+ identifier,
+ maplikeOrSetlikeType,
+ keyType,
+ valueType,
+ IDLInterfaceMember.Tags.MaplikeOrSetlike,
+ )
+ self.readonly = readonly
+ self.slotIndices = None
+
+ # 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.maplikeOrSetlikeOrIterableType,
+ self.keyType,
+ )
+
+ def expand(self, members):
+ """
+ In order to take advantage of all of the method machinery in Codegen,
+ we generate our functions as if they were part of the interface
+ specification during parsing.
+ """
+ # Both maplike and setlike have a size attribute
+ members.append(
+ IDLAttribute(
+ self.location,
+ IDLUnresolvedIdentifier(
+ BuiltinLocation("<auto-generated-identifier>"), "size"
+ ),
+ BuiltinTypes[IDLBuiltinType.Types.unsigned_long],
+ True,
+ maplikeOrSetlike=self,
+ )
+ )
+ self.reserved_ro_names = ["size"]
+ self.disallowedMemberNames.append("size")
+
+ # object entries()
+ self.addMethod(
+ "entries",
+ members,
+ False,
+ BuiltinTypes[IDLBuiltinType.Types.object],
+ affectsNothing=True,
+ isIteratorAlias=self.isMaplike(),
+ )
+ # object keys()
+ self.addMethod(
+ "keys",
+ members,
+ False,
+ BuiltinTypes[IDLBuiltinType.Types.object],
+ affectsNothing=True,
+ )
+ # object values()
+ self.addMethod(
+ "values",
+ members,
+ False,
+ BuiltinTypes[IDLBuiltinType.Types.object],
+ affectsNothing=True,
+ isIteratorAlias=self.isSetlike(),
+ )
+
+ # undefined forEach(callback(valueType, keyType), thisVal)
+ self.addMethod(
+ "forEach",
+ members,
+ False,
+ BuiltinTypes[IDLBuiltinType.Types.undefined],
+ self.getForEachArguments(),
+ )
+
+ def getKeyArg():
+ return IDLArgument(
+ self.location,
+ IDLUnresolvedIdentifier(self.location, "key"),
+ self.keyType,
+ )
+
+ # boolean has(keyType key)
+ self.addMethod(
+ "has",
+ members,
+ False,
+ BuiltinTypes[IDLBuiltinType.Types.boolean],
+ [getKeyArg()],
+ isPure=True,
+ )
+
+ if not self.readonly:
+ # undefined clear()
+ self.addMethod(
+ "clear", members, True, BuiltinTypes[IDLBuiltinType.Types.undefined], []
+ )
+ # boolean delete(keyType key)
+ self.addMethod(
+ "delete",
+ members,
+ True,
+ BuiltinTypes[IDLBuiltinType.Types.boolean],
+ [getKeyArg()],
+ )
+
+ if self.isSetlike():
+ if not self.readonly:
+ # Add returns the set object it just added to.
+ # object add(keyType key)
+
+ self.addMethod(
+ "add",
+ members,
+ True,
+ BuiltinTypes[IDLBuiltinType.Types.object],
+ [getKeyArg()],
+ )
+ return
+
+ # If we get this far, we're a maplike declaration.
+
+ # valueType get(keyType key)
+ #
+ # Note that instead of the value type, we're using any here. The
+ # validity checks should happen as things are inserted into the map,
+ # and using any as the return type makes code generation much simpler.
+ #
+ # TODO: Bug 1155340 may change this to use specific type to provide
+ # more info to JIT.
+ self.addMethod(
+ "get",
+ members,
+ False,
+ BuiltinTypes[IDLBuiltinType.Types.any],
+ [getKeyArg()],
+ isPure=True,
+ )
+
+ def getValueArg():
+ return IDLArgument(
+ self.location,
+ IDLUnresolvedIdentifier(self.location, "value"),
+ self.valueType,
+ )
+
+ if not self.readonly:
+ self.addMethod(
+ "set",
+ members,
+ True,
+ BuiltinTypes[IDLBuiltinType.Types.object],
+ [getKeyArg(), getValueArg()],
+ )
+
+
+class IDLConst(IDLInterfaceMember):
+ def __init__(self, location, identifier, type, value):
+ IDLInterfaceMember.__init__(
+ self, location, identifier, IDLInterfaceMember.Tags.Const
+ )
+
+ assert isinstance(type, IDLType)
+ if type.isDictionary():
+ raise WebIDLError(
+ "A constant cannot be of a dictionary type", [self.location]
+ )
+ if type.isRecord():
+ raise WebIDLError("A constant cannot be of a record type", [self.location])
+ self.type = type
+ self.value = value
+
+ if identifier.name == "prototype":
+ raise WebIDLError(
+ "The identifier of a constant must not be 'prototype'", [location]
+ )
+
+ def __str__(self):
+ return "'%s' const '%s'" % (self.type, self.identifier)
+
+ def finish(self, scope):
+ IDLInterfaceMember.finish(self, scope)
+
+ if not self.type.isComplete():
+ type = self.type.complete(scope)
+ if not type.isPrimitive() and not type.isString():
+ locations = [self.type.location, type.location]
+ try:
+ locations.append(type.inner.location)
+ except Exception:
+ pass
+ raise WebIDLError("Incorrect type for constant", locations)
+ self.type = type
+
+ # The value might not match the type
+ coercedValue = self.value.coerceToType(self.type, self.location)
+ assert coercedValue
+
+ self.value = coercedValue
+
+ def validate(self):
+ IDLInterfaceMember.validate(self)
+
+ def handleExtendedAttribute(self, attr):
+ identifier = attr.identifier()
+ if identifier == "Exposed":
+ convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames)
+ elif (
+ identifier == "Pref"
+ or identifier == "ChromeOnly"
+ or identifier == "Func"
+ or identifier == "Trial"
+ or identifier == "SecureContext"
+ or identifier == "NonEnumerable"
+ ):
+ # Known attributes that we don't need to do anything with here
+ pass
+ else:
+ raise WebIDLError(
+ "Unknown extended attribute %s on constant" % identifier,
+ [attr.location],
+ )
+ IDLInterfaceMember.handleExtendedAttribute(self, attr)
+
+ def _getDependentObjects(self):
+ return set([self.type, self.value])
+
+
+class IDLAttribute(IDLInterfaceMember):
+ def __init__(
+ self,
+ location,
+ identifier,
+ type,
+ readonly,
+ inherit=False,
+ static=False,
+ stringifier=False,
+ maplikeOrSetlike=None,
+ extendedAttrDict=None,
+ ):
+ IDLInterfaceMember.__init__(
+ self,
+ location,
+ identifier,
+ IDLInterfaceMember.Tags.Attr,
+ extendedAttrDict=extendedAttrDict,
+ )
+
+ assert isinstance(type, IDLType)
+ self.type = type
+ self.readonly = readonly
+ self.inherit = inherit
+ self._static = static
+ self.legacyLenientThis = False
+ self._legacyUnforgeable = False
+ self.stringifier = stringifier
+ self.slotIndices = None
+ assert maplikeOrSetlike is None or isinstance(
+ maplikeOrSetlike, IDLMaplikeOrSetlike
+ )
+ self.maplikeOrSetlike = maplikeOrSetlike
+ self.dependsOn = "Everything"
+ self.affects = "Everything"
+ self.bindingAliases = []
+
+ if static and identifier.name == "prototype":
+ raise WebIDLError(
+ "The identifier of a static attribute must not be 'prototype'",
+ [location],
+ )
+
+ if readonly and inherit:
+ raise WebIDLError(
+ "An attribute cannot be both 'readonly' and 'inherit'", [self.location]
+ )
+
+ def isStatic(self):
+ return self._static
+
+ def forceStatic(self):
+ self._static = True
+
+ def __str__(self):
+ return "'%s' attribute '%s'" % (self.type, self.identifier)
+
+ def finish(self, scope):
+ IDLInterfaceMember.finish(self, scope)
+
+ if not self.type.isComplete():
+ t = self.type.complete(scope)
+
+ assert not isinstance(t, IDLUnresolvedType)
+ assert not isinstance(t, IDLTypedefType)
+ assert not isinstance(t.name, IDLUnresolvedIdentifier)
+ self.type = t
+
+ if self.readonly and (
+ self.type.hasClamp()
+ or self.type.hasEnforceRange()
+ or self.type.hasAllowShared()
+ or self.type.legacyNullToEmptyString
+ ):
+ raise WebIDLError(
+ "A readonly attribute cannot be [Clamp] or [EnforceRange] or [AllowShared]",
+ [self.location],
+ )
+ if self.type.isDictionary() and not self.getExtendedAttribute("Cached"):
+ raise WebIDLError(
+ "An attribute cannot be of a dictionary type", [self.location]
+ )
+ if self.type.isSequence() and not self.getExtendedAttribute("Cached"):
+ raise WebIDLError(
+ "A non-cached attribute cannot be of a sequence " "type",
+ [self.location],
+ )
+ if self.type.isRecord() and not self.getExtendedAttribute("Cached"):
+ raise WebIDLError(
+ "A non-cached attribute cannot be of a record " "type", [self.location]
+ )
+ if self.type.isUnion():
+ for f in self.type.unroll().flatMemberTypes:
+ if f.isDictionary():
+ raise WebIDLError(
+ "An attribute cannot be of a union "
+ "type if one of its member types (or "
+ "one of its member types's member "
+ "types, and so on) is a dictionary "
+ "type",
+ [self.location, f.location],
+ )
+ if f.isSequence():
+ raise WebIDLError(
+ "An attribute cannot be of a union "
+ "type if one of its member types (or "
+ "one of its member types's member "
+ "types, and so on) is a sequence "
+ "type",
+ [self.location, f.location],
+ )
+ if f.isRecord():
+ raise WebIDLError(
+ "An attribute cannot be of a union "
+ "type if one of its member types (or "
+ "one of its member types's member "
+ "types, and so on) is a record "
+ "type",
+ [self.location, f.location],
+ )
+ if not self.type.isInterface() and self.getExtendedAttribute("PutForwards"):
+ raise WebIDLError(
+ "An attribute with [PutForwards] must have an "
+ "interface type as its type",
+ [self.location],
+ )
+
+ if not self.type.isInterface() and self.getExtendedAttribute("SameObject"):
+ raise WebIDLError(
+ "An attribute with [SameObject] must have an "
+ "interface type as its type",
+ [self.location],
+ )
+
+ if self.type.isPromise() and not self.readonly:
+ raise WebIDLError(
+ "Promise-returning attributes must be readonly", [self.location]
+ )
+
+ if self.type.isObservableArray():
+ if self.isStatic():
+ raise WebIDLError(
+ "A static attribute cannot have an ObservableArray type",
+ [self.location],
+ )
+ if self.getExtendedAttribute("Cached") or self.getExtendedAttribute(
+ "StoreInSlot"
+ ):
+ raise WebIDLError(
+ "[Cached] and [StoreInSlot] must not be used "
+ "on an attribute whose type is ObservableArray",
+ [self.location],
+ )
+
+ def validate(self):
+ def typeContainsChromeOnlyDictionaryMember(type):
+ if type.nullable() or type.isSequence() or type.isRecord():
+ return typeContainsChromeOnlyDictionaryMember(type.inner)
+
+ if type.isUnion():
+ for memberType in type.flatMemberTypes:
+ (contains, location) = typeContainsChromeOnlyDictionaryMember(
+ memberType
+ )
+ if contains:
+ return (True, location)
+
+ if type.isDictionary():
+ dictionary = type.inner
+ while dictionary:
+ (contains, location) = dictionaryContainsChromeOnlyMember(
+ dictionary
+ )
+ if contains:
+ return (True, location)
+ dictionary = dictionary.parent
+
+ return (False, None)
+
+ def dictionaryContainsChromeOnlyMember(dictionary):
+ for member in dictionary.members:
+ if member.getExtendedAttribute("ChromeOnly"):
+ return (True, member.location)
+ (contains, location) = typeContainsChromeOnlyDictionaryMember(
+ member.type
+ )
+ if contains:
+ return (True, location)
+ return (False, None)
+
+ IDLInterfaceMember.validate(self)
+
+ if self.getExtendedAttribute("Cached") or self.getExtendedAttribute(
+ "StoreInSlot"
+ ):
+ if not self.affects == "Nothing":
+ raise WebIDLError(
+ "Cached attributes and attributes stored in "
+ "slots must be Constant or Pure or "
+ "Affects=Nothing, since the getter won't always "
+ "be called.",
+ [self.location],
+ )
+ (contains, location) = typeContainsChromeOnlyDictionaryMember(self.type)
+ if contains:
+ raise WebIDLError(
+ "[Cached] and [StoreInSlot] must not be used "
+ "on an attribute whose type contains a "
+ "[ChromeOnly] dictionary member",
+ [self.location, location],
+ )
+ if self.getExtendedAttribute("Frozen"):
+ if (
+ not self.type.isSequence()
+ and not self.type.isDictionary()
+ and not self.type.isRecord()
+ ):
+ raise WebIDLError(
+ "[Frozen] is only allowed on "
+ "sequence-valued, dictionary-valued, and "
+ "record-valued attributes",
+ [self.location],
+ )
+ if not self.type.unroll().isExposedInAllOf(self.exposureSet):
+ raise WebIDLError(
+ "Attribute returns a type that is not exposed "
+ "everywhere where the attribute is exposed",
+ [self.location],
+ )
+ if self.getExtendedAttribute("CEReactions"):
+ if self.readonly:
+ raise WebIDLError(
+ "[CEReactions] is not allowed on " "readonly attributes",
+ [self.location],
+ )
+
+ def handleExtendedAttribute(self, attr):
+ identifier = attr.identifier()
+ if (
+ identifier == "SetterThrows"
+ or identifier == "SetterCanOOM"
+ or identifier == "SetterNeedsSubjectPrincipal"
+ ) and self.readonly:
+ raise WebIDLError(
+ "Readonly attributes must not be flagged as " "[%s]" % identifier,
+ [self.location],
+ )
+ elif identifier == "BindingAlias":
+ if not attr.hasValue():
+ raise WebIDLError(
+ "[BindingAlias] takes an identifier or string", [attr.location]
+ )
+ self._addBindingAlias(attr.value())
+ elif (
+ (
+ identifier == "Throws"
+ or identifier == "GetterThrows"
+ or identifier == "CanOOM"
+ or identifier == "GetterCanOOM"
+ )
+ and self.getExtendedAttribute("StoreInSlot")
+ ) or (
+ identifier == "StoreInSlot"
+ and (
+ self.getExtendedAttribute("Throws")
+ or self.getExtendedAttribute("GetterThrows")
+ or self.getExtendedAttribute("CanOOM")
+ or self.getExtendedAttribute("GetterCanOOM")
+ )
+ ):
+ raise WebIDLError("Throwing things can't be [StoreInSlot]", [attr.location])
+ elif identifier == "LegacyLenientThis":
+ if not attr.noArguments():
+ raise WebIDLError(
+ "[LegacyLenientThis] must take no arguments", [attr.location]
+ )
+ if self.isStatic():
+ raise WebIDLError(
+ "[LegacyLenientThis] is only allowed on non-static " "attributes",
+ [attr.location, self.location],
+ )
+ if self.getExtendedAttribute("CrossOriginReadable"):
+ raise WebIDLError(
+ "[LegacyLenientThis] is not allowed in combination "
+ "with [CrossOriginReadable]",
+ [attr.location, self.location],
+ )
+ if self.getExtendedAttribute("CrossOriginWritable"):
+ raise WebIDLError(
+ "[LegacyLenientThis] is not allowed in combination "
+ "with [CrossOriginWritable]",
+ [attr.location, self.location],
+ )
+ self.legacyLenientThis = True
+ elif identifier == "LegacyUnforgeable":
+ if self.isStatic():
+ raise WebIDLError(
+ "[LegacyUnforgeable] is only allowed on non-static " "attributes",
+ [attr.location, self.location],
+ )
+ self._legacyUnforgeable = True
+ elif identifier == "SameObject" and not self.readonly:
+ raise WebIDLError(
+ "[SameObject] only allowed on readonly attributes",
+ [attr.location, self.location],
+ )
+ elif identifier == "Constant" and not self.readonly:
+ raise WebIDLError(
+ "[Constant] only allowed on readonly attributes",
+ [attr.location, self.location],
+ )
+ elif identifier == "PutForwards":
+ if not self.readonly:
+ raise WebIDLError(
+ "[PutForwards] is only allowed on readonly " "attributes",
+ [attr.location, self.location],
+ )
+ if self.type.isPromise():
+ raise WebIDLError(
+ "[PutForwards] is not allowed on " "Promise-typed attributes",
+ [attr.location, self.location],
+ )
+ if self.isStatic():
+ raise WebIDLError(
+ "[PutForwards] is only allowed on non-static " "attributes",
+ [attr.location, self.location],
+ )
+ if self.getExtendedAttribute("Replaceable") is not None:
+ raise WebIDLError(
+ "[PutForwards] and [Replaceable] can't both "
+ "appear on the same attribute",
+ [attr.location, self.location],
+ )
+ if not attr.hasValue():
+ raise WebIDLError(
+ "[PutForwards] takes an identifier", [attr.location, self.location]
+ )
+ elif identifier == "Replaceable":
+ if not attr.noArguments():
+ raise WebIDLError(
+ "[Replaceable] must take no arguments", [attr.location]
+ )
+ if not self.readonly:
+ raise WebIDLError(
+ "[Replaceable] is only allowed on readonly " "attributes",
+ [attr.location, self.location],
+ )
+ if self.type.isPromise():
+ raise WebIDLError(
+ "[Replaceable] is not allowed on " "Promise-typed attributes",
+ [attr.location, self.location],
+ )
+ if self.isStatic():
+ raise WebIDLError(
+ "[Replaceable] is only allowed on non-static " "attributes",
+ [attr.location, self.location],
+ )
+ if self.getExtendedAttribute("PutForwards") is not None:
+ raise WebIDLError(
+ "[PutForwards] and [Replaceable] can't both "
+ "appear on the same attribute",
+ [attr.location, self.location],
+ )
+ elif identifier == "LegacyLenientSetter":
+ if not attr.noArguments():
+ raise WebIDLError(
+ "[LegacyLenientSetter] must take no arguments", [attr.location]
+ )
+ if not self.readonly:
+ raise WebIDLError(
+ "[LegacyLenientSetter] is only allowed on readonly " "attributes",
+ [attr.location, self.location],
+ )
+ if self.type.isPromise():
+ raise WebIDLError(
+ "[LegacyLenientSetter] is not allowed on "
+ "Promise-typed attributes",
+ [attr.location, self.location],
+ )
+ if self.isStatic():
+ raise WebIDLError(
+ "[LegacyLenientSetter] is only allowed on non-static " "attributes",
+ [attr.location, self.location],
+ )
+ if self.getExtendedAttribute("PutForwards") is not None:
+ raise WebIDLError(
+ "[LegacyLenientSetter] and [PutForwards] can't both "
+ "appear on the same attribute",
+ [attr.location, self.location],
+ )
+ if self.getExtendedAttribute("Replaceable") is not None:
+ raise WebIDLError(
+ "[LegacyLenientSetter] and [Replaceable] can't both "
+ "appear on the same attribute",
+ [attr.location, self.location],
+ )
+ elif identifier == "LenientFloat":
+ if self.readonly:
+ raise WebIDLError(
+ "[LenientFloat] used on a readonly attribute",
+ [attr.location, self.location],
+ )
+ if not self.type.includesRestrictedFloat():
+ raise WebIDLError(
+ "[LenientFloat] used on an attribute with a "
+ "non-restricted-float type",
+ [attr.location, self.location],
+ )
+ elif identifier == "StoreInSlot":
+ if self.getExtendedAttribute("Cached"):
+ raise WebIDLError(
+ "[StoreInSlot] and [Cached] must not be "
+ "specified on the same attribute",
+ [attr.location, self.location],
+ )
+ elif identifier == "Cached":
+ if self.getExtendedAttribute("StoreInSlot"):
+ raise WebIDLError(
+ "[Cached] and [StoreInSlot] must not be "
+ "specified on the same attribute",
+ [attr.location, self.location],
+ )
+ elif identifier == "CrossOriginReadable" or identifier == "CrossOriginWritable":
+ if not attr.noArguments():
+ raise WebIDLError(
+ "[%s] must take no arguments" % identifier, [attr.location]
+ )
+ if self.isStatic():
+ raise WebIDLError(
+ "[%s] is only allowed on non-static " "attributes" % identifier,
+ [attr.location, self.location],
+ )
+ if self.getExtendedAttribute("LegacyLenientThis"):
+ raise WebIDLError(
+ "[LegacyLenientThis] is not allowed in combination "
+ "with [%s]" % identifier,
+ [attr.location, self.location],
+ )
+ elif identifier == "Exposed":
+ convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames)
+ elif identifier == "Pure":
+ if not attr.noArguments():
+ raise WebIDLError("[Pure] must take no arguments", [attr.location])
+ self._setDependsOn("DOMState")
+ self._setAffects("Nothing")
+ elif identifier == "Constant" or identifier == "SameObject":
+ if not attr.noArguments():
+ raise WebIDLError(
+ "[%s] must take no arguments" % identifier, [attr.location]
+ )
+ self._setDependsOn("Nothing")
+ self._setAffects("Nothing")
+ elif identifier == "Affects":
+ if not attr.hasValue():
+ raise WebIDLError("[Affects] takes an identifier", [attr.location])
+ self._setAffects(attr.value())
+ elif identifier == "DependsOn":
+ if not attr.hasValue():
+ raise WebIDLError("[DependsOn] takes an identifier", [attr.location])
+ if (
+ attr.value() != "Everything"
+ and attr.value() != "DOMState"
+ and not self.readonly
+ ):
+ raise WebIDLError(
+ "[DependsOn=%s] only allowed on "
+ "readonly attributes" % attr.value(),
+ [attr.location, self.location],
+ )
+ self._setDependsOn(attr.value())
+ elif identifier == "UseCounter":
+ if self.stringifier:
+ raise WebIDLError(
+ "[UseCounter] must not be used on a " "stringifier attribute",
+ [attr.location, self.location],
+ )
+ elif identifier == "Unscopable":
+ if not attr.noArguments():
+ raise WebIDLError(
+ "[Unscopable] must take no arguments", [attr.location]
+ )
+ if self.isStatic():
+ raise WebIDLError(
+ "[Unscopable] is only allowed on non-static "
+ "attributes and operations",
+ [attr.location, self.location],
+ )
+ elif identifier == "CEReactions":
+ if not attr.noArguments():
+ raise WebIDLError(
+ "[CEReactions] must take no arguments", [attr.location]
+ )
+ elif (
+ identifier == "Pref"
+ or identifier == "Deprecated"
+ or identifier == "SetterThrows"
+ or identifier == "Throws"
+ or identifier == "GetterThrows"
+ or identifier == "SetterCanOOM"
+ or identifier == "CanOOM"
+ or identifier == "GetterCanOOM"
+ or identifier == "ChromeOnly"
+ or identifier == "Func"
+ or identifier == "Trial"
+ or identifier == "SecureContext"
+ or identifier == "Frozen"
+ or identifier == "NewObject"
+ or identifier == "NeedsSubjectPrincipal"
+ or identifier == "SetterNeedsSubjectPrincipal"
+ or identifier == "GetterNeedsSubjectPrincipal"
+ or identifier == "NeedsCallerType"
+ or identifier == "ReturnValueNeedsContainsHack"
+ or identifier == "BinaryName"
+ or identifier == "NonEnumerable"
+ ):
+ # Known attributes that we don't need to do anything with here
+ pass
+ else:
+ raise WebIDLError(
+ "Unknown extended attribute %s on attribute" % identifier,
+ [attr.location],
+ )
+ IDLInterfaceMember.handleExtendedAttribute(self, attr)
+
+ def resolve(self, parentScope):
+ assert isinstance(parentScope, IDLScope)
+ self.type.resolveType(parentScope)
+ IDLObjectWithIdentifier.resolve(self, parentScope)
+
+ def hasLegacyLenientThis(self):
+ return self.legacyLenientThis
+
+ def isMaplikeOrSetlikeAttr(self):
+ """
+ True if this attribute was generated from an interface with
+ maplike/setlike (e.g. this is the size attribute for
+ maplike/setlike)
+ """
+ return self.maplikeOrSetlike is not None
+
+ def isLegacyUnforgeable(self):
+ return self._legacyUnforgeable
+
+ def _getDependentObjects(self):
+ return set([self.type])
+
+ def expand(self, members):
+ assert self.stringifier
+ if (
+ not self.type.isDOMString()
+ and not self.type.isUSVString()
+ and not self.type.isUTF8String()
+ ):
+ raise WebIDLError(
+ "The type of a stringifer attribute must be "
+ "either DOMString, USVString or UTF8String",
+ [self.location],
+ )
+ identifier = IDLUnresolvedIdentifier(
+ self.location, "__stringifier", allowDoubleUnderscore=True
+ )
+ method = IDLMethod(
+ self.location,
+ identifier,
+ returnType=self.type,
+ arguments=[],
+ stringifier=True,
+ underlyingAttr=self,
+ )
+ allowedExtAttrs = ["Throws", "NeedsSubjectPrincipal", "Pure"]
+ # Safe to ignore these as they are only meaningful for attributes
+ attributeOnlyExtAttrs = [
+ "CEReactions",
+ "CrossOriginWritable",
+ "SetterThrows",
+ ]
+ for (key, value) in self._extendedAttrDict.items():
+ if key in allowedExtAttrs:
+ if value is not True:
+ raise WebIDLError(
+ "[%s] with a value is currently "
+ "unsupported in stringifier attributes, "
+ "please file a bug to add support" % key,
+ [self.location],
+ )
+ method.addExtendedAttributes(
+ [IDLExtendedAttribute(self.location, (key,))]
+ )
+ elif key not in attributeOnlyExtAttrs:
+ raise WebIDLError(
+ "[%s] is currently unsupported in "
+ "stringifier attributes, please file a bug "
+ "to add support" % key,
+ [self.location],
+ )
+ members.append(method)
+
+
+class IDLArgument(IDLObjectWithIdentifier):
+ def __init__(
+ self,
+ location,
+ identifier,
+ type,
+ optional=False,
+ defaultValue=None,
+ variadic=False,
+ dictionaryMember=False,
+ allowTypeAttributes=False,
+ ):
+ IDLObjectWithIdentifier.__init__(self, location, None, identifier)
+
+ assert isinstance(type, IDLType)
+ self.type = type
+
+ self.optional = optional
+ self.defaultValue = defaultValue
+ self.variadic = variadic
+ self.dictionaryMember = dictionaryMember
+ self._isComplete = False
+ self._allowTreatNonCallableAsNull = False
+ self._extendedAttrDict = {}
+ self.allowTypeAttributes = allowTypeAttributes
+
+ assert not variadic or optional
+ assert not variadic or not defaultValue
+
+ def addExtendedAttributes(self, attrs):
+ for attribute in attrs:
+ identifier = attribute.identifier()
+ if self.allowTypeAttributes and (
+ identifier == "EnforceRange"
+ or identifier == "Clamp"
+ or identifier == "LegacyNullToEmptyString"
+ or identifier == "AllowShared"
+ ):
+ self.type = self.type.withExtendedAttributes([attribute])
+ elif identifier == "TreatNonCallableAsNull":
+ self._allowTreatNonCallableAsNull = True
+ elif self.dictionaryMember and (
+ identifier == "ChromeOnly"
+ or identifier == "Func"
+ or identifier == "Trial"
+ or identifier == "Pref"
+ ):
+ if not self.optional:
+ raise WebIDLError(
+ "[%s] must not be used on a required "
+ "dictionary member" % identifier,
+ [attribute.location],
+ )
+ else:
+ raise WebIDLError(
+ "Unhandled extended attribute on %s"
+ % (
+ "a dictionary member"
+ if self.dictionaryMember
+ else "an argument"
+ ),
+ [attribute.location],
+ )
+ attrlist = attribute.listValue()
+ self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True
+
+ def getExtendedAttribute(self, name):
+ return self._extendedAttrDict.get(name, None)
+
+ def isComplete(self):
+ return self._isComplete
+
+ def complete(self, scope):
+ if self._isComplete:
+ return
+
+ self._isComplete = True
+
+ if not self.type.isComplete():
+ type = self.type.complete(scope)
+ assert not isinstance(type, IDLUnresolvedType)
+ assert not isinstance(type, IDLTypedefType)
+ assert not isinstance(type.name, IDLUnresolvedIdentifier)
+ self.type = type
+
+ if self.type.isUndefined():
+ raise WebIDLError(
+ "undefined must not be used as the type of an argument in any circumstance",
+ [self.location],
+ )
+
+ if self.type.isAny():
+ assert self.defaultValue is None or isinstance(
+ self.defaultValue, IDLNullValue
+ )
+ # optional 'any' values always have a default value
+ if self.optional and not self.defaultValue and not self.variadic:
+ # Set the default value to undefined, for simplicity, so the
+ # codegen doesn't have to special-case this.
+ self.defaultValue = IDLUndefinedValue(self.location)
+
+ if self.dictionaryMember and self.type.legacyNullToEmptyString:
+ raise WebIDLError(
+ "Dictionary members cannot be [LegacyNullToEmptyString]",
+ [self.location],
+ )
+ if self.type.isObservableArray():
+ raise WebIDLError(
+ "%s cannot have an ObservableArray type"
+ % ("Dictionary members" if self.dictionaryMember else "Arguments"),
+ [self.location],
+ )
+ # Now do the coercing thing; this needs to happen after the
+ # above creation of a default value.
+ if self.defaultValue:
+ self.defaultValue = self.defaultValue.coerceToType(self.type, self.location)
+ assert self.defaultValue
+
+ def allowTreatNonCallableAsNull(self):
+ return self._allowTreatNonCallableAsNull
+
+ def _getDependentObjects(self):
+ deps = set([self.type])
+ if self.defaultValue:
+ deps.add(self.defaultValue)
+ return deps
+
+ def canHaveMissingValue(self):
+ return self.optional and not self.defaultValue
+
+
+class IDLCallback(IDLObjectWithScope):
+ def __init__(
+ self, location, parentScope, identifier, returnType, arguments, isConstructor
+ ):
+ assert isinstance(returnType, IDLType)
+
+ self._returnType = returnType
+ # Clone the list
+ self._arguments = list(arguments)
+
+ IDLObjectWithScope.__init__(self, location, parentScope, identifier)
+
+ for (returnType, arguments) in self.signatures():
+ for argument in arguments:
+ argument.resolve(self)
+
+ self._treatNonCallableAsNull = False
+ self._treatNonObjectAsNull = False
+ self._isRunScriptBoundary = False
+ self._isConstructor = isConstructor
+
+ def isCallback(self):
+ return True
+
+ def isConstructor(self):
+ return self._isConstructor
+
+ def signatures(self):
+ return [(self._returnType, self._arguments)]
+
+ def finish(self, scope):
+ if not self._returnType.isComplete():
+ type = self._returnType.complete(scope)
+
+ assert not isinstance(type, IDLUnresolvedType)
+ assert not isinstance(type, IDLTypedefType)
+ assert not isinstance(type.name, IDLUnresolvedIdentifier)
+ self._returnType = type
+
+ for argument in self._arguments:
+ if argument.type.isComplete():
+ continue
+
+ type = argument.type.complete(scope)
+
+ assert not isinstance(type, IDLUnresolvedType)
+ assert not isinstance(type, IDLTypedefType)
+ assert not isinstance(type.name, IDLUnresolvedIdentifier)
+ argument.type = type
+
+ def validate(self):
+ for argument in self._arguments:
+ if argument.type.isUndefined():
+ raise WebIDLError(
+ "undefined must not be used as the type of an argument in any circumstance",
+ [self.location],
+ )
+
+ def addExtendedAttributes(self, attrs):
+ unhandledAttrs = []
+ for attr in attrs:
+ if attr.identifier() == "TreatNonCallableAsNull":
+ self._treatNonCallableAsNull = True
+ elif attr.identifier() == "LegacyTreatNonObjectAsNull":
+ if self._isConstructor:
+ raise WebIDLError(
+ "[LegacyTreatNonObjectAsNull] is not supported "
+ "on constructors",
+ [self.location],
+ )
+ self._treatNonObjectAsNull = True
+ elif attr.identifier() == "MOZ_CAN_RUN_SCRIPT_BOUNDARY":
+ if self._isConstructor:
+ raise WebIDLError(
+ "[MOZ_CAN_RUN_SCRIPT_BOUNDARY] is not "
+ "permitted on constructors",
+ [self.location],
+ )
+ self._isRunScriptBoundary = True
+ else:
+ unhandledAttrs.append(attr)
+ if self._treatNonCallableAsNull and self._treatNonObjectAsNull:
+ raise WebIDLError(
+ "Cannot specify both [TreatNonCallableAsNull] "
+ "and [LegacyTreatNonObjectAsNull]",
+ [self.location],
+ )
+ if len(unhandledAttrs) != 0:
+ IDLType.addExtendedAttributes(self, unhandledAttrs)
+
+ def _getDependentObjects(self):
+ return set([self._returnType] + self._arguments)
+
+ def isRunScriptBoundary(self):
+ return self._isRunScriptBoundary
+
+
+class IDLCallbackType(IDLType):
+ def __init__(self, location, callback):
+ IDLType.__init__(self, location, callback.identifier.name)
+ self.callback = callback
+
+ def isCallback(self):
+ return True
+
+ def tag(self):
+ return IDLType.Tags.callback
+
+ def isDistinguishableFrom(self, other):
+ if other.isPromise():
+ return False
+ if other.isUnion():
+ # Just forward to the union; it'll deal
+ return other.isDistinguishableFrom(self)
+ return (
+ other.isUndefined()
+ or other.isPrimitive()
+ or other.isString()
+ or other.isEnum()
+ or other.isNonCallbackInterface()
+ or other.isSequence()
+ )
+
+ def _getDependentObjects(self):
+ return self.callback._getDependentObjects()
+
+
+class IDLMethodOverload:
+ """
+ A class that represents a single overload of a WebIDL method. This is not
+ quite the same as an element of the "effective overload set" in the spec,
+ because separate IDLMethodOverloads are not created based on arguments being
+ optional. Rather, when multiple methods have the same name, there is an
+ IDLMethodOverload for each one, all hanging off an IDLMethod representing
+ the full set of overloads.
+ """
+
+ def __init__(self, returnType, arguments, location):
+ self.returnType = returnType
+ # Clone the list of arguments, just in case
+ self.arguments = list(arguments)
+ self.location = location
+
+ def _getDependentObjects(self):
+ deps = set(self.arguments)
+ deps.add(self.returnType)
+ return deps
+
+ def includesRestrictedFloatArgument(self):
+ return any(arg.type.includesRestrictedFloat() for arg in self.arguments)
+
+
+class IDLMethod(IDLInterfaceMember, IDLScope):
+
+ Special = enum(
+ "Getter", "Setter", "Deleter", "LegacyCaller", base=IDLInterfaceMember.Special
+ )
+
+ NamedOrIndexed = enum("Neither", "Named", "Indexed")
+
+ def __init__(
+ self,
+ location,
+ identifier,
+ returnType,
+ arguments,
+ static=False,
+ getter=False,
+ setter=False,
+ deleter=False,
+ specialType=NamedOrIndexed.Neither,
+ legacycaller=False,
+ stringifier=False,
+ maplikeOrSetlikeOrIterable=None,
+ underlyingAttr=None,
+ ):
+ # REVIEW: specialType is NamedOrIndexed -- wow, this is messed up.
+ IDLInterfaceMember.__init__(
+ self, location, identifier, IDLInterfaceMember.Tags.Method
+ )
+
+ self._hasOverloads = False
+
+ assert isinstance(returnType, IDLType)
+
+ # self._overloads is a list of IDLMethodOverloads
+ self._overloads = [IDLMethodOverload(returnType, arguments, location)]
+
+ assert isinstance(static, bool)
+ self._static = static
+ assert isinstance(getter, bool)
+ self._getter = getter
+ assert isinstance(setter, bool)
+ self._setter = setter
+ assert isinstance(deleter, bool)
+ self._deleter = deleter
+ assert isinstance(legacycaller, bool)
+ self._legacycaller = legacycaller
+ assert isinstance(stringifier, bool)
+ self._stringifier = stringifier
+ assert maplikeOrSetlikeOrIterable is None or isinstance(
+ maplikeOrSetlikeOrIterable, IDLMaplikeOrSetlikeOrIterableBase
+ )
+ self.maplikeOrSetlikeOrIterable = maplikeOrSetlikeOrIterable
+ self._htmlConstructor = False
+ self.underlyingAttr = underlyingAttr
+ self._specialType = specialType
+ self._legacyUnforgeable = False
+ self.dependsOn = "Everything"
+ self.affects = "Everything"
+ self.aliases = []
+
+ if static and identifier.name == "prototype":
+ raise WebIDLError(
+ "The identifier of a static operation must not be 'prototype'",
+ [location],
+ )
+
+ self.assertSignatureConstraints()
+
+ def __str__(self):
+ return "Method '%s'" % self.identifier
+
+ def assertSignatureConstraints(self):
+ if self._getter or self._deleter:
+ assert len(self._overloads) == 1
+ overload = self._overloads[0]
+ arguments = overload.arguments
+ assert len(arguments) == 1
+ assert (
+ arguments[0].type == BuiltinTypes[IDLBuiltinType.Types.domstring]
+ or arguments[0].type == BuiltinTypes[IDLBuiltinType.Types.unsigned_long]
+ )
+ assert not arguments[0].optional and not arguments[0].variadic
+ assert not self._getter or not overload.returnType.isUndefined()
+
+ if self._setter:
+ assert len(self._overloads) == 1
+ arguments = self._overloads[0].arguments
+ assert len(arguments) == 2
+ assert (
+ arguments[0].type == BuiltinTypes[IDLBuiltinType.Types.domstring]
+ or arguments[0].type == BuiltinTypes[IDLBuiltinType.Types.unsigned_long]
+ )
+ assert not arguments[0].optional and not arguments[0].variadic
+ assert not arguments[1].optional and not arguments[1].variadic
+
+ if self._stringifier:
+ assert len(self._overloads) == 1
+ overload = self._overloads[0]
+ assert len(overload.arguments) == 0
+ if not self.underlyingAttr:
+ assert (
+ overload.returnType == BuiltinTypes[IDLBuiltinType.Types.domstring]
+ )
+
+ def isStatic(self):
+ return self._static
+
+ def forceStatic(self):
+ self._static = True
+
+ def isGetter(self):
+ return self._getter
+
+ def isSetter(self):
+ return self._setter
+
+ def isDeleter(self):
+ return self._deleter
+
+ def isNamed(self):
+ assert (
+ self._specialType == IDLMethod.NamedOrIndexed.Named
+ or self._specialType == IDLMethod.NamedOrIndexed.Indexed
+ )
+ return self._specialType == IDLMethod.NamedOrIndexed.Named
+
+ def isIndexed(self):
+ assert (
+ self._specialType == IDLMethod.NamedOrIndexed.Named
+ or self._specialType == IDLMethod.NamedOrIndexed.Indexed
+ )
+ return self._specialType == IDLMethod.NamedOrIndexed.Indexed
+
+ def isLegacycaller(self):
+ return self._legacycaller
+
+ def isStringifier(self):
+ return self._stringifier
+
+ def isToJSON(self):
+ return self.identifier.name == "toJSON"
+
+ def isDefaultToJSON(self):
+ return self.isToJSON() and self.getExtendedAttribute("Default")
+
+ def isMaplikeOrSetlikeOrIterableMethod(self):
+ """
+ True if this method was generated as part of a
+ maplike/setlike/etc interface (e.g. has/get methods)
+ """
+ return self.maplikeOrSetlikeOrIterable is not None
+
+ def isSpecial(self):
+ return (
+ self.isGetter()
+ or self.isSetter()
+ or self.isDeleter()
+ or self.isLegacycaller()
+ or self.isStringifier()
+ )
+
+ def isHTMLConstructor(self):
+ return self._htmlConstructor
+
+ def hasOverloads(self):
+ return self._hasOverloads
+
+ def isIdentifierLess(self):
+ """
+ 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.isMaplikeOrSetlikeOrIterableMethod()
+ )
+
+ def resolve(self, parentScope):
+ assert isinstance(parentScope, IDLScope)
+ IDLObjectWithIdentifier.resolve(self, parentScope)
+ IDLScope.__init__(self, self.location, parentScope, self.identifier)
+ for (returnType, arguments) in self.signatures():
+ for argument in arguments:
+ argument.resolve(self)
+
+ def addOverload(self, method):
+ assert len(method._overloads) == 1
+
+ if self._extendedAttrDict != method._extendedAttrDict:
+ extendedAttrDiff = set(self._extendedAttrDict.keys()) ^ set(
+ method._extendedAttrDict.keys()
+ )
+
+ if extendedAttrDiff == {"LenientFloat"}:
+ if "LenientFloat" not in self._extendedAttrDict:
+ for overload in self._overloads:
+ if overload.includesRestrictedFloatArgument():
+ raise WebIDLError(
+ "Restricted float behavior differs on different "
+ "overloads of %s" % method.identifier,
+ [overload.location, method.location],
+ )
+ self._extendedAttrDict["LenientFloat"] = method._extendedAttrDict[
+ "LenientFloat"
+ ]
+ elif method._overloads[0].includesRestrictedFloatArgument():
+ raise WebIDLError(
+ "Restricted float behavior differs on different "
+ "overloads of %s" % method.identifier,
+ [self.location, method.location],
+ )
+ else:
+ raise WebIDLError(
+ "Extended attributes differ on different "
+ "overloads of %s" % method.identifier,
+ [self.location, method.location],
+ )
+
+ self._overloads.extend(method._overloads)
+
+ self._hasOverloads = True
+
+ if self.isStatic() != method.isStatic():
+ raise WebIDLError(
+ "Overloaded identifier %s appears with different values of the 'static' attribute"
+ % method.identifier,
+ [method.location],
+ )
+
+ if self.isLegacycaller() != method.isLegacycaller():
+ raise WebIDLError(
+ (
+ "Overloaded identifier %s appears with different "
+ "values of the 'legacycaller' attribute" % method.identifier
+ ),
+ [method.location],
+ )
+
+ # Can't overload special things!
+ assert not self.isGetter()
+ assert not method.isGetter()
+ assert not self.isSetter()
+ assert not method.isSetter()
+ assert not self.isDeleter()
+ assert not method.isDeleter()
+ assert not self.isStringifier()
+ assert not method.isStringifier()
+ assert not self.isHTMLConstructor()
+ assert not method.isHTMLConstructor()
+
+ return self
+
+ def signatures(self):
+ return [
+ (overload.returnType, overload.arguments) for overload in self._overloads
+ ]
+
+ def finish(self, scope):
+ IDLInterfaceMember.finish(self, scope)
+
+ for overload in self._overloads:
+ returnType = overload.returnType
+ if not returnType.isComplete():
+ returnType = returnType.complete(scope)
+ assert not isinstance(returnType, IDLUnresolvedType)
+ assert not isinstance(returnType, IDLTypedefType)
+ assert not isinstance(returnType.name, IDLUnresolvedIdentifier)
+ overload.returnType = returnType
+
+ for argument in overload.arguments:
+ if not argument.isComplete():
+ argument.complete(scope)
+ assert argument.type.isComplete()
+
+ # Now compute various information that will be used by the
+ # WebIDL overload resolution algorithm.
+ self.maxArgCount = max(len(s[1]) for s in self.signatures())
+ self.allowedArgCounts = [
+ i
+ for i in range(self.maxArgCount + 1)
+ if len(self.signaturesForArgCount(i)) != 0
+ ]
+
+ def validate(self):
+ IDLInterfaceMember.validate(self)
+
+ # Make sure our overloads are properly distinguishable and don't have
+ # different argument types before the distinguishing args.
+ for argCount in self.allowedArgCounts:
+ possibleOverloads = self.overloadsForArgCount(argCount)
+ if len(possibleOverloads) == 1:
+ continue
+ distinguishingIndex = self.distinguishingIndexForArgCount(argCount)
+ for idx in range(distinguishingIndex):
+ firstSigType = possibleOverloads[0].arguments[idx].type
+ for overload in possibleOverloads[1:]:
+ if overload.arguments[idx].type != firstSigType:
+ raise WebIDLError(
+ "Signatures for method '%s' with %d arguments have "
+ "different types of arguments at index %d, which "
+ "is before distinguishing index %d"
+ % (
+ self.identifier.name,
+ argCount,
+ idx,
+ distinguishingIndex,
+ ),
+ [self.location, overload.location],
+ )
+
+ overloadWithPromiseReturnType = None
+ overloadWithoutPromiseReturnType = None
+ for overload in self._overloads:
+ returnType = overload.returnType
+ if not returnType.unroll().isExposedInAllOf(self.exposureSet):
+ raise WebIDLError(
+ "Overload returns a type that is not exposed "
+ "everywhere where the method is exposed",
+ [overload.location],
+ )
+
+ variadicArgument = None
+
+ arguments = overload.arguments
+ for (idx, argument) in enumerate(arguments):
+ assert argument.type.isComplete()
+
+ if (
+ argument.type.isDictionary()
+ and argument.type.unroll().inner.canBeEmpty()
+ ) or (
+ argument.type.isUnion()
+ and argument.type.unroll().hasPossiblyEmptyDictionaryType()
+ ):
+ # Optional dictionaries and unions containing optional
+ # dictionaries at the end of the list or followed by
+ # optional arguments must be optional.
+ if not argument.optional and all(
+ arg.optional for arg in arguments[idx + 1 :]
+ ):
+ raise WebIDLError(
+ "Dictionary argument without any "
+ "required fields or union argument "
+ "containing such dictionary not "
+ "followed by a required argument "
+ "must be optional",
+ [argument.location],
+ )
+
+ if not argument.defaultValue and all(
+ arg.optional for arg in arguments[idx + 1 :]
+ ):
+ raise WebIDLError(
+ "Dictionary argument without any "
+ "required fields or union argument "
+ "containing such dictionary not "
+ "followed by a required argument "
+ "must have a default value",
+ [argument.location],
+ )
+
+ # An argument cannot be a nullable dictionary or a
+ # nullable union containing a dictionary.
+ if argument.type.nullable() and (
+ argument.type.isDictionary()
+ or (
+ argument.type.isUnion()
+ and argument.type.unroll().hasDictionaryType()
+ )
+ ):
+ raise WebIDLError(
+ "An argument cannot be a nullable "
+ "dictionary or nullable union "
+ "containing a dictionary",
+ [argument.location],
+ )
+
+ # Only the last argument can be variadic
+ if variadicArgument:
+ raise WebIDLError(
+ "Variadic argument is not last argument",
+ [variadicArgument.location],
+ )
+ if argument.variadic:
+ variadicArgument = argument
+
+ if returnType.isPromise():
+ overloadWithPromiseReturnType = overload
+ else:
+ overloadWithoutPromiseReturnType = overload
+
+ # Make sure either all our overloads return Promises or none do
+ if overloadWithPromiseReturnType and overloadWithoutPromiseReturnType:
+ raise WebIDLError(
+ "We have overloads with both Promise and " "non-Promise return types",
+ [
+ overloadWithPromiseReturnType.location,
+ overloadWithoutPromiseReturnType.location,
+ ],
+ )
+
+ if overloadWithPromiseReturnType and self._legacycaller:
+ raise WebIDLError(
+ "May not have a Promise return type for a " "legacycaller.",
+ [overloadWithPromiseReturnType.location],
+ )
+
+ if self.getExtendedAttribute("StaticClassOverride") and not (
+ self.identifier.scope.isJSImplemented() and self.isStatic()
+ ):
+ raise WebIDLError(
+ "StaticClassOverride can be applied to static"
+ " methods on JS-implemented classes only.",
+ [self.location],
+ )
+
+ # Ensure that toJSON methods satisfy the spec constraints on them.
+ if self.identifier.name == "toJSON":
+ if len(self.signatures()) != 1:
+ raise WebIDLError(
+ "toJSON method has multiple overloads",
+ [self._overloads[0].location, self._overloads[1].location],
+ )
+ if len(self.signatures()[0][1]) != 0:
+ raise WebIDLError("toJSON method has arguments", [self.location])
+ if not self.signatures()[0][0].isJSONType():
+ raise WebIDLError(
+ "toJSON method has non-JSON return type", [self.location]
+ )
+
+ def overloadsForArgCount(self, argc):
+ return [
+ overload
+ for overload in self._overloads
+ if len(overload.arguments) == argc
+ or (
+ len(overload.arguments) > argc
+ and all(arg.optional for arg in overload.arguments[argc:])
+ )
+ or (
+ len(overload.arguments) < argc
+ and len(overload.arguments) > 0
+ and overload.arguments[-1].variadic
+ )
+ ]
+
+ def signaturesForArgCount(self, argc):
+ return [
+ (overload.returnType, overload.arguments)
+ for overload in self.overloadsForArgCount(argc)
+ ]
+
+ def locationsForArgCount(self, argc):
+ return [overload.location for overload in self.overloadsForArgCount(argc)]
+
+ def distinguishingIndexForArgCount(self, argc):
+ def isValidDistinguishingIndex(idx, signatures):
+ for (firstSigIndex, (firstRetval, firstArgs)) in enumerate(signatures[:-1]):
+ for (secondRetval, secondArgs) in signatures[firstSigIndex + 1 :]:
+ if idx < len(firstArgs):
+ firstType = firstArgs[idx].type
+ else:
+ assert firstArgs[-1].variadic
+ firstType = firstArgs[-1].type
+ if idx < len(secondArgs):
+ secondType = secondArgs[idx].type
+ else:
+ assert secondArgs[-1].variadic
+ secondType = secondArgs[-1].type
+ if not firstType.isDistinguishableFrom(secondType):
+ return False
+ return True
+
+ signatures = self.signaturesForArgCount(argc)
+ for idx in range(argc):
+ if isValidDistinguishingIndex(idx, signatures):
+ return idx
+ # No valid distinguishing index. Time to throw
+ locations = self.locationsForArgCount(argc)
+ raise WebIDLError(
+ "Signatures with %d arguments for method '%s' are not "
+ "distinguishable" % (argc, self.identifier.name),
+ locations,
+ )
+
+ def handleExtendedAttribute(self, attr):
+ identifier = attr.identifier()
+ if (
+ identifier == "GetterThrows"
+ or identifier == "SetterThrows"
+ or identifier == "GetterCanOOM"
+ or identifier == "SetterCanOOM"
+ or identifier == "SetterNeedsSubjectPrincipal"
+ or identifier == "GetterNeedsSubjectPrincipal"
+ ):
+ raise WebIDLError(
+ "Methods must not be flagged as " "[%s]" % identifier,
+ [attr.location, self.location],
+ )
+ elif identifier == "LegacyUnforgeable":
+ if self.isStatic():
+ raise WebIDLError(
+ "[LegacyUnforgeable] is only allowed on non-static " "methods",
+ [attr.location, self.location],
+ )
+ self._legacyUnforgeable = True
+ elif identifier == "SameObject":
+ raise WebIDLError(
+ "Methods must not be flagged as [SameObject]",
+ [attr.location, self.location],
+ )
+ elif identifier == "Constant":
+ raise WebIDLError(
+ "Methods must not be flagged as [Constant]",
+ [attr.location, self.location],
+ )
+ elif identifier == "PutForwards":
+ raise WebIDLError(
+ "Only attributes support [PutForwards]", [attr.location, self.location]
+ )
+ elif identifier == "LegacyLenientSetter":
+ raise WebIDLError(
+ "Only attributes support [LegacyLenientSetter]",
+ [attr.location, self.location],
+ )
+ elif identifier == "LenientFloat":
+ # This is called before we've done overload resolution
+ overloads = self._overloads
+ assert len(overloads) == 1
+ if not overloads[0].returnType.isUndefined():
+ raise WebIDLError(
+ "[LenientFloat] used on a non-undefined method",
+ [attr.location, self.location],
+ )
+ if not overloads[0].includesRestrictedFloatArgument():
+ raise WebIDLError(
+ "[LenientFloat] used on an operation with no "
+ "restricted float type arguments",
+ [attr.location, self.location],
+ )
+ elif identifier == "Exposed":
+ convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames)
+ elif (
+ identifier == "CrossOriginCallable"
+ or identifier == "WebGLHandlesContextLoss"
+ ):
+ # Known no-argument attributes.
+ if not attr.noArguments():
+ raise WebIDLError(
+ "[%s] must take no arguments" % identifier, [attr.location]
+ )
+ if identifier == "CrossOriginCallable" and self.isStatic():
+ raise WebIDLError(
+ "[CrossOriginCallable] is only allowed on non-static " "attributes",
+ [attr.location, self.location],
+ )
+ elif identifier == "Pure":
+ if not attr.noArguments():
+ raise WebIDLError("[Pure] must take no arguments", [attr.location])
+ self._setDependsOn("DOMState")
+ self._setAffects("Nothing")
+ elif identifier == "Affects":
+ if not attr.hasValue():
+ raise WebIDLError("[Affects] takes an identifier", [attr.location])
+ self._setAffects(attr.value())
+ elif identifier == "DependsOn":
+ if not attr.hasValue():
+ raise WebIDLError("[DependsOn] takes an identifier", [attr.location])
+ self._setDependsOn(attr.value())
+ elif identifier == "Alias":
+ if not attr.hasValue():
+ raise WebIDLError(
+ "[Alias] takes an identifier or string", [attr.location]
+ )
+ self._addAlias(attr.value())
+ elif identifier == "UseCounter":
+ if self.isSpecial():
+ raise WebIDLError(
+ "[UseCounter] must not be used on a special " "operation",
+ [attr.location, self.location],
+ )
+ elif identifier == "Unscopable":
+ if not attr.noArguments():
+ raise WebIDLError(
+ "[Unscopable] must take no arguments", [attr.location]
+ )
+ if self.isStatic():
+ raise WebIDLError(
+ "[Unscopable] is only allowed on non-static "
+ "attributes and operations",
+ [attr.location, self.location],
+ )
+ elif identifier == "CEReactions":
+ if not attr.noArguments():
+ raise WebIDLError(
+ "[CEReactions] must take no arguments", [attr.location]
+ )
+
+ if self.isSpecial() and not self.isSetter() and not self.isDeleter():
+ raise WebIDLError(
+ "[CEReactions] is only allowed on operation, "
+ "attribute, setter, and deleter",
+ [attr.location, self.location],
+ )
+ elif identifier == "Default":
+ if not attr.noArguments():
+ raise WebIDLError("[Default] must take no arguments", [attr.location])
+
+ if not self.isToJSON():
+ raise WebIDLError(
+ "[Default] is only allowed on toJSON operations",
+ [attr.location, self.location],
+ )
+
+ if self.signatures()[0][0] != BuiltinTypes[IDLBuiltinType.Types.object]:
+ raise WebIDLError(
+ "The return type of the default toJSON "
+ "operation must be 'object'",
+ [attr.location, self.location],
+ )
+ elif (
+ identifier == "Throws"
+ or identifier == "CanOOM"
+ or identifier == "NewObject"
+ or identifier == "ChromeOnly"
+ or identifier == "Pref"
+ or identifier == "Deprecated"
+ or identifier == "Func"
+ or identifier == "Trial"
+ or identifier == "SecureContext"
+ or identifier == "BinaryName"
+ or identifier == "NeedsSubjectPrincipal"
+ or identifier == "NeedsCallerType"
+ or identifier == "StaticClassOverride"
+ or identifier == "NonEnumerable"
+ or identifier == "Unexposed"
+ or identifier == "WebExtensionStub"
+ ):
+ # Known attributes that we don't need to do anything with here
+ pass
+ else:
+ raise WebIDLError(
+ "Unknown extended attribute %s on method" % identifier, [attr.location]
+ )
+ IDLInterfaceMember.handleExtendedAttribute(self, attr)
+
+ def returnsPromise(self):
+ return self._overloads[0].returnType.isPromise()
+
+ def isLegacyUnforgeable(self):
+ return self._legacyUnforgeable
+
+ def _getDependentObjects(self):
+ deps = set()
+ for overload in self._overloads:
+ deps.update(overload._getDependentObjects())
+ return deps
+
+
+class IDLConstructor(IDLMethod):
+ def __init__(self, location, args, name):
+ # We can't actually init our IDLMethod yet, because we do not know the
+ # return type yet. Just save the info we have for now and we will init
+ # it later.
+ self._initLocation = location
+ self._initArgs = args
+ self._initName = name
+ self._inited = False
+ self._initExtendedAttrs = []
+
+ def addExtendedAttributes(self, attrs):
+ if self._inited:
+ return IDLMethod.addExtendedAttributes(self, attrs)
+ self._initExtendedAttrs.extend(attrs)
+
+ def handleExtendedAttribute(self, attr):
+ identifier = attr.identifier()
+ if (
+ identifier == "BinaryName"
+ or identifier == "ChromeOnly"
+ or identifier == "NewObject"
+ or identifier == "SecureContext"
+ or identifier == "Throws"
+ or identifier == "Func"
+ or identifier == "Trial"
+ or identifier == "Pref"
+ or identifier == "UseCounter"
+ ):
+ IDLMethod.handleExtendedAttribute(self, attr)
+ elif identifier == "HTMLConstructor":
+ if not attr.noArguments():
+ raise WebIDLError(
+ "[HTMLConstructor] must take no arguments", [attr.location]
+ )
+ # We shouldn't end up here for legacy factory functions.
+ assert self.identifier.name == "constructor"
+
+ if any(len(sig[1]) != 0 for sig in self.signatures()):
+ raise WebIDLError(
+ "[HTMLConstructor] must not be applied to a "
+ "constructor operation that has arguments.",
+ [attr.location],
+ )
+ self._htmlConstructor = True
+ else:
+ raise WebIDLError(
+ "Unknown extended attribute %s on method" % identifier, [attr.location]
+ )
+
+ def reallyInit(self, parentInterface):
+ name = self._initName
+ location = self._initLocation
+ identifier = IDLUnresolvedIdentifier(location, name, allowForbidden=True)
+ retType = IDLWrapperType(parentInterface.location, parentInterface)
+ IDLMethod.__init__(
+ self, location, identifier, retType, self._initArgs, static=True
+ )
+ self._inited = True
+ # Propagate through whatever extended attributes we already had
+ self.addExtendedAttributes(self._initExtendedAttrs)
+ self._initExtendedAttrs = []
+ # Constructors are always NewObject. Whether they throw or not is
+ # indicated by [Throws] annotations in the usual way.
+ self.addExtendedAttributes(
+ [IDLExtendedAttribute(self.location, ("NewObject",))]
+ )
+
+
+class IDLIncludesStatement(IDLObject):
+ def __init__(self, location, interface, mixin):
+ IDLObject.__init__(self, location)
+ self.interface = interface
+ self.mixin = mixin
+ self._finished = False
+
+ def finish(self, scope):
+ if self._finished:
+ return
+ self._finished = True
+ assert isinstance(self.interface, IDLIdentifierPlaceholder)
+ assert isinstance(self.mixin, IDLIdentifierPlaceholder)
+ interface = self.interface.finish(scope)
+ mixin = self.mixin.finish(scope)
+ # NOTE: we depend on not setting self.interface and
+ # self.mixin here to keep track of the original
+ # locations.
+ if not isinstance(interface, IDLInterface):
+ raise WebIDLError(
+ "Left-hand side of 'includes' is not an " "interface",
+ [self.interface.location, interface.location],
+ )
+ if interface.isCallback():
+ raise WebIDLError(
+ "Left-hand side of 'includes' is a callback " "interface",
+ [self.interface.location, interface.location],
+ )
+ if not isinstance(mixin, IDLInterfaceMixin):
+ raise WebIDLError(
+ "Right-hand side of 'includes' is not an " "interface mixin",
+ [self.mixin.location, mixin.location],
+ )
+
+ mixin.actualExposureGlobalNames.update(interface._exposureGlobalNames)
+
+ interface.addIncludedMixin(mixin)
+ self.interface = interface
+ self.mixin = mixin
+
+ def validate(self):
+ pass
+
+ def addExtendedAttributes(self, attrs):
+ if len(attrs) != 0:
+ raise WebIDLError(
+ "There are no extended attributes that are "
+ "allowed on includes statements",
+ [attrs[0].location, self.location],
+ )
+
+
+class IDLExtendedAttribute(IDLObject):
+ """
+ A class to represent IDL extended attributes so we can give them locations
+ """
+
+ def __init__(self, location, tuple):
+ IDLObject.__init__(self, location)
+ self._tuple = tuple
+
+ def identifier(self):
+ return self._tuple[0]
+
+ def noArguments(self):
+ return len(self._tuple) == 1
+
+ def hasValue(self):
+ return len(self._tuple) >= 2 and isinstance(self._tuple[1], str)
+
+ def value(self):
+ assert self.hasValue()
+ return self._tuple[1]
+
+ def hasArgs(self):
+ return (
+ len(self._tuple) == 2
+ and isinstance(self._tuple[1], list)
+ or len(self._tuple) == 3
+ )
+
+ def args(self):
+ assert self.hasArgs()
+ # Our args are our last element
+ return self._tuple[-1]
+
+ def listValue(self):
+ """
+ Backdoor for storing random data in _extendedAttrDict
+ """
+ return list(self._tuple)[1:]
+
+
+# Parser
+
+
+class Tokenizer(object):
+ tokens = ["INTEGER", "FLOATLITERAL", "IDENTIFIER", "STRING", "WHITESPACE", "OTHER"]
+
+ def t_FLOATLITERAL(self, t):
+ r"(-?(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][+-]?[0-9]+)?|[0-9]+[Ee][+-]?[0-9]+|Infinity))|NaN"
+ t.value = float(t.value)
+ return t
+
+ def t_INTEGER(self, t):
+ r"-?(0([0-7]+|[Xx][0-9A-Fa-f]+)?|[1-9][0-9]*)"
+ try:
+ # Can't use int(), because that doesn't handle octal properly.
+ t.value = parseInt(t.value)
+ except Exception:
+ raise WebIDLError(
+ "Invalid integer literal",
+ [
+ Location(
+ lexer=self.lexer,
+ lineno=self.lexer.lineno,
+ lexpos=self.lexer.lexpos,
+ filename=self._filename,
+ )
+ ],
+ )
+ return t
+
+ def t_IDENTIFIER(self, t):
+ r"[_-]?[A-Za-z][0-9A-Z_a-z-]*"
+ t.type = self.keywords.get(t.value, "IDENTIFIER")
+ return t
+
+ def t_STRING(self, t):
+ r'"[^"]*"'
+ t.value = t.value[1:-1]
+ return t
+
+ def t_WHITESPACE(self, t):
+ r"[\t\n\r ]+|[\t\n\r ]*((//[^\n]*|/\*.*?\*/)[\t\n\r ]*)+"
+ pass
+
+ def t_ELLIPSIS(self, t):
+ r"\.\.\."
+ t.type = self.keywords.get(t.value)
+ return t
+
+ def t_OTHER(self, t):
+ r"[^\t\n\r 0-9A-Z_a-z]"
+ t.type = self.keywords.get(t.value, "OTHER")
+ return t
+
+ keywords = {
+ "interface": "INTERFACE",
+ "partial": "PARTIAL",
+ "mixin": "MIXIN",
+ "dictionary": "DICTIONARY",
+ "exception": "EXCEPTION",
+ "enum": "ENUM",
+ "callback": "CALLBACK",
+ "typedef": "TYPEDEF",
+ "includes": "INCLUDES",
+ "const": "CONST",
+ "null": "NULL",
+ "true": "TRUE",
+ "false": "FALSE",
+ "serializer": "SERIALIZER",
+ "stringifier": "STRINGIFIER",
+ "unrestricted": "UNRESTRICTED",
+ "attribute": "ATTRIBUTE",
+ "readonly": "READONLY",
+ "inherit": "INHERIT",
+ "static": "STATIC",
+ "getter": "GETTER",
+ "setter": "SETTER",
+ "deleter": "DELETER",
+ "legacycaller": "LEGACYCALLER",
+ "optional": "OPTIONAL",
+ "...": "ELLIPSIS",
+ "::": "SCOPE",
+ "DOMString": "DOMSTRING",
+ "ByteString": "BYTESTRING",
+ "USVString": "USVSTRING",
+ "JSString": "JSSTRING",
+ "UTF8String": "UTF8STRING",
+ "any": "ANY",
+ "boolean": "BOOLEAN",
+ "byte": "BYTE",
+ "double": "DOUBLE",
+ "float": "FLOAT",
+ "long": "LONG",
+ "object": "OBJECT",
+ "ObservableArray": "OBSERVABLEARRAY",
+ "octet": "OCTET",
+ "Promise": "PROMISE",
+ "required": "REQUIRED",
+ "sequence": "SEQUENCE",
+ "record": "RECORD",
+ "short": "SHORT",
+ "unsigned": "UNSIGNED",
+ "undefined": "UNDEFINED",
+ ":": "COLON",
+ ";": "SEMICOLON",
+ "{": "LBRACE",
+ "}": "RBRACE",
+ "(": "LPAREN",
+ ")": "RPAREN",
+ "[": "LBRACKET",
+ "]": "RBRACKET",
+ "?": "QUESTIONMARK",
+ "*": "ASTERISK",
+ ",": "COMMA",
+ "=": "EQUALS",
+ "<": "LT",
+ ">": "GT",
+ "ArrayBuffer": "ARRAYBUFFER",
+ "or": "OR",
+ "maplike": "MAPLIKE",
+ "setlike": "SETLIKE",
+ "iterable": "ITERABLE",
+ "namespace": "NAMESPACE",
+ "constructor": "CONSTRUCTOR",
+ "symbol": "SYMBOL",
+ "async": "ASYNC",
+ }
+
+ tokens.extend(keywords.values())
+
+ def t_error(self, t):
+ raise WebIDLError(
+ "Unrecognized Input",
+ [
+ Location(
+ lexer=self.lexer,
+ lineno=self.lexer.lineno,
+ lexpos=self.lexer.lexpos,
+ filename=self.filename,
+ )
+ ],
+ )
+
+ def __init__(self, outputdir, lexer=None):
+ if lexer:
+ self.lexer = lexer
+ else:
+ self.lexer = lex.lex(object=self, reflags=re.DOTALL)
+
+
+class SqueakyCleanLogger(object):
+ errorWhitelist = [
+ # Web IDL defines the WHITESPACE token, but doesn't actually
+ # use it ... so far.
+ "Token 'WHITESPACE' defined, but not used",
+ # And that means we have an unused token
+ "There is 1 unused token",
+ # Web IDL defines a OtherOrComma rule that's only used in
+ # ExtendedAttributeInner, which we don't use yet.
+ "Rule 'OtherOrComma' defined, but not used",
+ # And an unused rule
+ "There is 1 unused rule",
+ # And the OtherOrComma grammar symbol is unreachable.
+ "Symbol 'OtherOrComma' is unreachable",
+ # Which means the Other symbol is unreachable.
+ "Symbol 'Other' is unreachable",
+ ]
+
+ def __init__(self):
+ self.errors = []
+
+ def debug(self, msg, *args, **kwargs):
+ pass
+
+ info = debug
+
+ def warning(self, msg, *args, **kwargs):
+ if (
+ msg == "%s:%d: Rule %r defined, but not used"
+ or msg == "%s:%d: Rule '%s' defined, but not used"
+ ):
+ # Munge things so we don't have to hardcode filenames and
+ # line numbers in our whitelist.
+ whitelistmsg = "Rule %r defined, but not used"
+ whitelistargs = args[2:]
+ else:
+ whitelistmsg = msg
+ whitelistargs = args
+ if (whitelistmsg % whitelistargs) not in SqueakyCleanLogger.errorWhitelist:
+ self.errors.append(msg % args)
+
+ error = warning
+
+ def reportGrammarErrors(self):
+ if self.errors:
+ raise WebIDLError("\n".join(self.errors), [])
+
+
+class Parser(Tokenizer):
+ def getLocation(self, p, i):
+ return Location(self.lexer, p.lineno(i), p.lexpos(i), self._filename)
+
+ def globalScope(self):
+ return self._globalScope
+
+ # The p_Foo functions here must match the WebIDL spec's grammar.
+ # It's acceptable to split things at '|' boundaries.
+ def p_Definitions(self, p):
+ """
+ Definitions : ExtendedAttributeList Definition Definitions
+ """
+ if p[2]:
+ p[0] = [p[2]]
+ p[2].addExtendedAttributes(p[1])
+ else:
+ assert not p[1]
+ p[0] = []
+
+ p[0].extend(p[3])
+
+ def p_DefinitionsEmpty(self, p):
+ """
+ Definitions :
+ """
+ p[0] = []
+
+ def p_Definition(self, p):
+ """
+ Definition : CallbackOrInterfaceOrMixin
+ | Namespace
+ | Partial
+ | Dictionary
+ | Exception
+ | Enum
+ | Typedef
+ | IncludesStatement
+ """
+ p[0] = p[1]
+ assert p[1] # We might not have implemented something ...
+
+ def p_CallbackOrInterfaceOrMixinCallback(self, p):
+ """
+ CallbackOrInterfaceOrMixin : CALLBACK CallbackRestOrInterface
+ """
+ if p[2].isInterface():
+ assert isinstance(p[2], IDLInterface)
+ p[2].setCallback(True)
+
+ p[0] = p[2]
+
+ def p_CallbackOrInterfaceOrMixinInterfaceOrMixin(self, p):
+ """
+ CallbackOrInterfaceOrMixin : INTERFACE InterfaceOrMixin
+ """
+ p[0] = p[2]
+
+ def p_CallbackRestOrInterface(self, p):
+ """
+ CallbackRestOrInterface : CallbackRest
+ | CallbackConstructorRest
+ | CallbackInterface
+ """
+ assert p[1]
+ p[0] = p[1]
+
+ def handleNonPartialObject(
+ self, location, identifier, constructor, constructorArgs, nonPartialArgs
+ ):
+ """
+ This handles non-partial objects (interfaces, namespaces and
+ dictionaries) by checking for an existing partial object, and promoting
+ it to non-partial as needed. The return value is the non-partial
+ object.
+
+ constructorArgs are all the args for the constructor except the last
+ one: isKnownNonPartial.
+
+ nonPartialArgs are the args for the setNonPartial call.
+ """
+ # The name of the class starts with "IDL", so strip that off.
+ # Also, starts with a capital letter after that, so nix that
+ # as well.
+ prettyname = constructor.__name__[3:].lower()
+
+ try:
+ existingObj = self.globalScope()._lookupIdentifier(identifier)
+ if existingObj:
+ if not isinstance(existingObj, constructor):
+ raise WebIDLError(
+ "%s has the same name as "
+ "non-%s object" % (prettyname.capitalize(), prettyname),
+ [location, existingObj.location],
+ )
+ existingObj.setNonPartial(*nonPartialArgs)
+ return existingObj
+ except Exception as ex:
+ if isinstance(ex, WebIDLError):
+ raise ex
+ pass
+
+ # True for isKnownNonPartial
+ return constructor(*(constructorArgs + [True]))
+
+ def p_InterfaceOrMixin(self, p):
+ """
+ InterfaceOrMixin : InterfaceRest
+ | MixinRest
+ """
+ p[0] = p[1]
+
+ def p_CallbackInterface(self, p):
+ """
+ CallbackInterface : INTERFACE InterfaceRest
+ """
+ p[0] = p[2]
+
+ def p_InterfaceRest(self, p):
+ """
+ InterfaceRest : IDENTIFIER Inheritance LBRACE InterfaceMembers RBRACE SEMICOLON
+ """
+ location = self.getLocation(p, 1)
+ identifier = IDLUnresolvedIdentifier(location, p[1])
+ members = p[4]
+ parent = p[2]
+
+ p[0] = self.handleNonPartialObject(
+ location,
+ identifier,
+ IDLInterface,
+ [location, self.globalScope(), identifier, parent, members],
+ [location, parent, members],
+ )
+
+ def p_InterfaceForwardDecl(self, p):
+ """
+ InterfaceRest : IDENTIFIER SEMICOLON
+ """
+ location = self.getLocation(p, 1)
+ identifier = IDLUnresolvedIdentifier(location, p[1])
+
+ try:
+ if self.globalScope()._lookupIdentifier(identifier):
+ p[0] = self.globalScope()._lookupIdentifier(identifier)
+ if not isinstance(p[0], IDLExternalInterface):
+ raise WebIDLError(
+ "Name collision between external "
+ "interface declaration for identifier "
+ "%s and %s" % (identifier.name, p[0]),
+ [location, p[0].location],
+ )
+ return
+ except Exception as ex:
+ if isinstance(ex, WebIDLError):
+ raise ex
+ pass
+
+ p[0] = IDLExternalInterface(location, self.globalScope(), identifier)
+
+ def p_MixinRest(self, p):
+ """
+ MixinRest : MIXIN IDENTIFIER LBRACE MixinMembers RBRACE SEMICOLON
+ """
+ location = self.getLocation(p, 1)
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2])
+ members = p[4]
+
+ p[0] = self.handleNonPartialObject(
+ location,
+ identifier,
+ IDLInterfaceMixin,
+ [location, self.globalScope(), identifier, members],
+ [location, members],
+ )
+
+ def p_Namespace(self, p):
+ """
+ Namespace : NAMESPACE IDENTIFIER LBRACE InterfaceMembers RBRACE SEMICOLON
+ """
+ location = self.getLocation(p, 1)
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2])
+ members = p[4]
+
+ p[0] = self.handleNonPartialObject(
+ location,
+ identifier,
+ IDLNamespace,
+ [location, self.globalScope(), identifier, members],
+ [location, None, members],
+ )
+
+ def p_Partial(self, p):
+ """
+ Partial : PARTIAL PartialDefinition
+ """
+ p[0] = p[2]
+
+ def p_PartialDefinitionInterface(self, p):
+ """
+ PartialDefinition : INTERFACE PartialInterfaceOrPartialMixin
+ """
+ p[0] = p[2]
+
+ def p_PartialDefinition(self, p):
+ """
+ PartialDefinition : PartialNamespace
+ | PartialDictionary
+ """
+ p[0] = p[1]
+
+ def handlePartialObject(
+ self,
+ location,
+ identifier,
+ nonPartialConstructor,
+ nonPartialConstructorArgs,
+ partialConstructorArgs,
+ ):
+ """
+ This handles partial objects (interfaces, namespaces and dictionaries)
+ by checking for an existing non-partial object, and adding ourselves to
+ it as needed. The return value is our partial object. We use
+ IDLPartialInterfaceOrNamespace for partial interfaces or namespaces,
+ and IDLPartialDictionary for partial dictionaries.
+
+ nonPartialConstructorArgs are all the args for the non-partial
+ constructor except the last two: members and isKnownNonPartial.
+
+ partialConstructorArgs are the arguments for the partial object
+ constructor, except the last one (the non-partial object).
+ """
+ # The name of the class starts with "IDL", so strip that off.
+ # Also, starts with a capital letter after that, so nix that
+ # as well.
+ prettyname = nonPartialConstructor.__name__[3:].lower()
+
+ nonPartialObject = None
+ try:
+ nonPartialObject = self.globalScope()._lookupIdentifier(identifier)
+ if nonPartialObject:
+ if not isinstance(nonPartialObject, nonPartialConstructor):
+ raise WebIDLError(
+ "Partial %s has the same name as "
+ "non-%s object" % (prettyname, prettyname),
+ [location, nonPartialObject.location],
+ )
+ except Exception as ex:
+ if isinstance(ex, WebIDLError):
+ raise ex
+ pass
+
+ if not nonPartialObject:
+ nonPartialObject = nonPartialConstructor(
+ # No members, False for isKnownNonPartial
+ *(nonPartialConstructorArgs),
+ members=[],
+ isKnownNonPartial=False
+ )
+
+ partialObject = None
+ if isinstance(nonPartialObject, IDLDictionary):
+ partialObject = IDLPartialDictionary(
+ *(partialConstructorArgs + [nonPartialObject])
+ )
+ elif isinstance(
+ nonPartialObject, (IDLInterface, IDLInterfaceMixin, IDLNamespace)
+ ):
+ partialObject = IDLPartialInterfaceOrNamespace(
+ *(partialConstructorArgs + [nonPartialObject])
+ )
+ else:
+ raise WebIDLError(
+ "Unknown partial object type %s" % type(partialObject), [location]
+ )
+
+ return partialObject
+
+ def p_PartialInterfaceOrPartialMixin(self, p):
+ """
+ PartialInterfaceOrPartialMixin : PartialInterfaceRest
+ | PartialMixinRest
+ """
+ p[0] = p[1]
+
+ def p_PartialInterfaceRest(self, p):
+ """
+ PartialInterfaceRest : IDENTIFIER LBRACE PartialInterfaceMembers RBRACE SEMICOLON
+ """
+ location = self.getLocation(p, 1)
+ identifier = IDLUnresolvedIdentifier(location, p[1])
+ members = p[3]
+
+ p[0] = self.handlePartialObject(
+ location,
+ identifier,
+ IDLInterface,
+ [location, self.globalScope(), identifier, None],
+ [location, identifier, members],
+ )
+
+ def p_PartialMixinRest(self, p):
+ """
+ PartialMixinRest : MIXIN IDENTIFIER LBRACE MixinMembers RBRACE SEMICOLON
+ """
+ location = self.getLocation(p, 1)
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2])
+ members = p[4]
+
+ p[0] = self.handlePartialObject(
+ location,
+ identifier,
+ IDLInterfaceMixin,
+ [location, self.globalScope(), identifier],
+ [location, identifier, members],
+ )
+
+ def p_PartialNamespace(self, p):
+ """
+ PartialNamespace : NAMESPACE IDENTIFIER LBRACE InterfaceMembers RBRACE SEMICOLON
+ """
+ location = self.getLocation(p, 1)
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2])
+ members = p[4]
+
+ p[0] = self.handlePartialObject(
+ location,
+ identifier,
+ IDLNamespace,
+ [location, self.globalScope(), identifier],
+ [location, identifier, members],
+ )
+
+ def p_PartialDictionary(self, p):
+ """
+ PartialDictionary : DICTIONARY IDENTIFIER LBRACE DictionaryMembers RBRACE SEMICOLON
+ """
+ location = self.getLocation(p, 1)
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2])
+ members = p[4]
+
+ p[0] = self.handlePartialObject(
+ location,
+ identifier,
+ IDLDictionary,
+ [location, self.globalScope(), identifier],
+ [location, identifier, members],
+ )
+
+ def p_Inheritance(self, p):
+ """
+ Inheritance : COLON ScopedName
+ """
+ p[0] = IDLIdentifierPlaceholder(self.getLocation(p, 2), p[2])
+
+ def p_InheritanceEmpty(self, p):
+ """
+ Inheritance :
+ """
+ pass
+
+ def p_InterfaceMembers(self, p):
+ """
+ InterfaceMembers : ExtendedAttributeList InterfaceMember InterfaceMembers
+ """
+ p[0] = [p[2]]
+
+ assert not p[1] or p[2]
+ p[2].addExtendedAttributes(p[1])
+
+ p[0].extend(p[3])
+
+ def p_InterfaceMembersEmpty(self, p):
+ """
+ InterfaceMembers :
+ """
+ p[0] = []
+
+ def p_InterfaceMember(self, p):
+ """
+ InterfaceMember : PartialInterfaceMember
+ | Constructor
+ """
+ p[0] = p[1]
+
+ def p_Constructor(self, p):
+ """
+ Constructor : CONSTRUCTOR LPAREN ArgumentList RPAREN SEMICOLON
+ """
+ p[0] = IDLConstructor(self.getLocation(p, 1), p[3], "constructor")
+
+ def p_PartialInterfaceMembers(self, p):
+ """
+ PartialInterfaceMembers : ExtendedAttributeList PartialInterfaceMember PartialInterfaceMembers
+ """
+ p[0] = [p[2]]
+
+ assert not p[1] or p[2]
+ p[2].addExtendedAttributes(p[1])
+
+ p[0].extend(p[3])
+
+ def p_PartialInterfaceMembersEmpty(self, p):
+ """
+ PartialInterfaceMembers :
+ """
+ p[0] = []
+
+ def p_PartialInterfaceMember(self, p):
+ """
+ PartialInterfaceMember : Const
+ | AttributeOrOperationOrMaplikeOrSetlikeOrIterable
+ """
+ p[0] = p[1]
+
+ def p_MixinMembersEmpty(self, p):
+ """
+ MixinMembers :
+ """
+ p[0] = []
+
+ def p_MixinMembers(self, p):
+ """
+ MixinMembers : ExtendedAttributeList MixinMember MixinMembers
+ """
+ p[0] = [p[2]]
+
+ assert not p[1] or p[2]
+ p[2].addExtendedAttributes(p[1])
+
+ p[0].extend(p[3])
+
+ def p_MixinMember(self, p):
+ """
+ MixinMember : Const
+ | Attribute
+ | Operation
+ """
+ p[0] = p[1]
+
+ def p_Dictionary(self, p):
+ """
+ Dictionary : DICTIONARY IDENTIFIER Inheritance LBRACE DictionaryMembers RBRACE SEMICOLON
+ """
+ location = self.getLocation(p, 1)
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2])
+ members = p[5]
+ p[0] = IDLDictionary(location, self.globalScope(), identifier, p[3], members)
+
+ def p_DictionaryMembers(self, p):
+ """
+ DictionaryMembers : ExtendedAttributeList DictionaryMember DictionaryMembers
+ |
+ """
+ if len(p) == 1:
+ # We're at the end of the list
+ p[0] = []
+ return
+ p[2].addExtendedAttributes(p[1])
+ p[0] = [p[2]]
+ p[0].extend(p[3])
+
+ def p_DictionaryMemberRequired(self, p):
+ """
+ DictionaryMember : REQUIRED TypeWithExtendedAttributes IDENTIFIER SEMICOLON
+ """
+ # These quack a lot like required arguments, so just treat them that way.
+ t = p[2]
+ assert isinstance(t, IDLType)
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 3), p[3])
+
+ p[0] = IDLArgument(
+ self.getLocation(p, 3),
+ identifier,
+ t,
+ optional=False,
+ defaultValue=None,
+ variadic=False,
+ dictionaryMember=True,
+ )
+
+ def p_DictionaryMember(self, p):
+ """
+ DictionaryMember : Type IDENTIFIER Default SEMICOLON
+ """
+ # These quack a lot like optional arguments, so just treat them that way.
+ t = p[1]
+ assert isinstance(t, IDLType)
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2])
+ defaultValue = p[3]
+
+ # Any attributes that precede this may apply to the type, so
+ # we configure the argument to forward type attributes down instead of producing
+ # a parse error
+ p[0] = IDLArgument(
+ self.getLocation(p, 2),
+ identifier,
+ t,
+ optional=True,
+ defaultValue=defaultValue,
+ variadic=False,
+ dictionaryMember=True,
+ allowTypeAttributes=True,
+ )
+
+ def p_Default(self, p):
+ """
+ Default : EQUALS DefaultValue
+ |
+ """
+ if len(p) > 1:
+ p[0] = p[2]
+ else:
+ p[0] = None
+
+ def p_DefaultValue(self, p):
+ """
+ DefaultValue : ConstValue
+ | LBRACKET RBRACKET
+ | LBRACE RBRACE
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ assert len(p) == 3 # Must be [] or {}
+ if p[1] == "[":
+ p[0] = IDLEmptySequenceValue(self.getLocation(p, 1))
+ else:
+ assert p[1] == "{"
+ p[0] = IDLDefaultDictionaryValue(self.getLocation(p, 1))
+
+ def p_DefaultValueNull(self, p):
+ """
+ DefaultValue : NULL
+ """
+ p[0] = IDLNullValue(self.getLocation(p, 1))
+
+ def p_DefaultValueUndefined(self, p):
+ """
+ DefaultValue : UNDEFINED
+ """
+ p[0] = IDLUndefinedValue(self.getLocation(p, 1))
+
+ def p_Exception(self, p):
+ """
+ Exception : EXCEPTION IDENTIFIER Inheritance LBRACE ExceptionMembers RBRACE SEMICOLON
+ """
+ pass
+
+ def p_Enum(self, p):
+ """
+ Enum : ENUM IDENTIFIER LBRACE EnumValueList RBRACE SEMICOLON
+ """
+ location = self.getLocation(p, 1)
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2])
+
+ values = p[4]
+ assert values
+ p[0] = IDLEnum(location, self.globalScope(), identifier, values)
+
+ def p_EnumValueList(self, p):
+ """
+ EnumValueList : STRING EnumValueListComma
+ """
+ p[0] = [p[1]]
+ p[0].extend(p[2])
+
+ def p_EnumValueListComma(self, p):
+ """
+ EnumValueListComma : COMMA EnumValueListString
+ """
+ p[0] = p[2]
+
+ def p_EnumValueListCommaEmpty(self, p):
+ """
+ EnumValueListComma :
+ """
+ p[0] = []
+
+ def p_EnumValueListString(self, p):
+ """
+ EnumValueListString : STRING EnumValueListComma
+ """
+ p[0] = [p[1]]
+ p[0].extend(p[2])
+
+ def p_EnumValueListStringEmpty(self, p):
+ """
+ EnumValueListString :
+ """
+ p[0] = []
+
+ def p_CallbackRest(self, p):
+ """
+ CallbackRest : IDENTIFIER EQUALS Type LPAREN ArgumentList RPAREN SEMICOLON
+ """
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1])
+ p[0] = IDLCallback(
+ self.getLocation(p, 1),
+ self.globalScope(),
+ identifier,
+ p[3],
+ p[5],
+ isConstructor=False,
+ )
+
+ def p_CallbackConstructorRest(self, p):
+ """
+ CallbackConstructorRest : CONSTRUCTOR IDENTIFIER EQUALS Type LPAREN ArgumentList RPAREN SEMICOLON
+ """
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2])
+ p[0] = IDLCallback(
+ self.getLocation(p, 2),
+ self.globalScope(),
+ identifier,
+ p[4],
+ p[6],
+ isConstructor=True,
+ )
+
+ def p_ExceptionMembers(self, p):
+ """
+ ExceptionMembers : ExtendedAttributeList ExceptionMember ExceptionMembers
+ |
+ """
+ pass
+
+ def p_Typedef(self, p):
+ """
+ Typedef : TYPEDEF TypeWithExtendedAttributes IDENTIFIER SEMICOLON
+ """
+ typedef = IDLTypedef(self.getLocation(p, 1), self.globalScope(), p[2], p[3])
+ p[0] = typedef
+
+ def p_IncludesStatement(self, p):
+ """
+ IncludesStatement : ScopedName INCLUDES ScopedName SEMICOLON
+ """
+ assert p[2] == "includes"
+ interface = IDLIdentifierPlaceholder(self.getLocation(p, 1), p[1])
+ mixin = IDLIdentifierPlaceholder(self.getLocation(p, 3), p[3])
+ p[0] = IDLIncludesStatement(self.getLocation(p, 1), interface, mixin)
+
+ def p_Const(self, p):
+ """
+ Const : CONST ConstType IDENTIFIER EQUALS ConstValue SEMICOLON
+ """
+ location = self.getLocation(p, 1)
+ type = p[2]
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 3), p[3])
+ value = p[5]
+ p[0] = IDLConst(location, identifier, type, value)
+
+ def p_ConstValueBoolean(self, p):
+ """
+ ConstValue : BooleanLiteral
+ """
+ location = self.getLocation(p, 1)
+ booleanType = BuiltinTypes[IDLBuiltinType.Types.boolean]
+ p[0] = IDLValue(location, booleanType, p[1])
+
+ def p_ConstValueInteger(self, p):
+ """
+ ConstValue : INTEGER
+ """
+ location = self.getLocation(p, 1)
+
+ # We don't know ahead of time what type the integer literal is.
+ # Determine the smallest type it could possibly fit in and use that.
+ integerType = matchIntegerValueToType(p[1])
+ if integerType is None:
+ raise WebIDLError("Integer literal out of range", [location])
+
+ p[0] = IDLValue(location, integerType, p[1])
+
+ def p_ConstValueFloat(self, p):
+ """
+ ConstValue : FLOATLITERAL
+ """
+ location = self.getLocation(p, 1)
+ p[0] = IDLValue(
+ location, BuiltinTypes[IDLBuiltinType.Types.unrestricted_float], p[1]
+ )
+
+ def p_ConstValueString(self, p):
+ """
+ ConstValue : STRING
+ """
+ location = self.getLocation(p, 1)
+ stringType = BuiltinTypes[IDLBuiltinType.Types.domstring]
+ p[0] = IDLValue(location, stringType, p[1])
+
+ def p_BooleanLiteralTrue(self, p):
+ """
+ BooleanLiteral : TRUE
+ """
+ p[0] = True
+
+ def p_BooleanLiteralFalse(self, p):
+ """
+ BooleanLiteral : FALSE
+ """
+ p[0] = False
+
+ def p_AttributeOrOperationOrMaplikeOrSetlikeOrIterable(self, p):
+ """
+ AttributeOrOperationOrMaplikeOrSetlikeOrIterable : Attribute
+ | Maplike
+ | Setlike
+ | Iterable
+ | AsyncIterable
+ | Operation
+ """
+ p[0] = p[1]
+
+ def p_Iterable(self, p):
+ """
+ Iterable : ITERABLE LT TypeWithExtendedAttributes GT SEMICOLON
+ | ITERABLE LT TypeWithExtendedAttributes COMMA TypeWithExtendedAttributes GT SEMICOLON
+ """
+ location = self.getLocation(p, 2)
+ identifier = IDLUnresolvedIdentifier(
+ location, "__iterable", allowDoubleUnderscore=True
+ )
+ if len(p) > 6:
+ keyType = p[3]
+ valueType = p[5]
+ else:
+ keyType = None
+ valueType = p[3]
+
+ p[0] = IDLIterable(location, identifier, keyType, valueType, self.globalScope())
+
+ def p_AsyncIterable(self, p):
+ """
+ AsyncIterable : ASYNC ITERABLE LT TypeWithExtendedAttributes GT SEMICOLON
+ | ASYNC ITERABLE LT TypeWithExtendedAttributes COMMA TypeWithExtendedAttributes GT SEMICOLON
+ | ASYNC ITERABLE LT TypeWithExtendedAttributes GT LPAREN ArgumentList RPAREN SEMICOLON
+ | ASYNC ITERABLE LT TypeWithExtendedAttributes COMMA TypeWithExtendedAttributes GT LPAREN ArgumentList RPAREN SEMICOLON
+ """
+ location = self.getLocation(p, 2)
+ identifier = IDLUnresolvedIdentifier(
+ location, "__iterable", allowDoubleUnderscore=True
+ )
+ if len(p) == 12:
+ keyType = p[4]
+ valueType = p[6]
+ argList = p[9]
+ elif len(p) == 10:
+ keyType = None
+ valueType = p[4]
+ argList = p[7]
+ elif len(p) == 9:
+ keyType = p[4]
+ valueType = p[6]
+ argList = []
+ else:
+ keyType = None
+ valueType = p[4]
+ argList = []
+
+ p[0] = IDLAsyncIterable(
+ location, identifier, keyType, valueType, argList, self.globalScope()
+ )
+
+ def p_Setlike(self, p):
+ """
+ Setlike : ReadOnly SETLIKE LT TypeWithExtendedAttributes GT SEMICOLON
+ """
+ readonly = p[1]
+ maplikeOrSetlikeType = p[2]
+ location = self.getLocation(p, 2)
+ identifier = IDLUnresolvedIdentifier(
+ location, "__setlike", allowDoubleUnderscore=True
+ )
+ keyType = p[4]
+ valueType = keyType
+ p[0] = IDLMaplikeOrSetlike(
+ location, identifier, maplikeOrSetlikeType, readonly, keyType, valueType
+ )
+
+ def p_Maplike(self, p):
+ """
+ Maplike : ReadOnly MAPLIKE LT TypeWithExtendedAttributes COMMA TypeWithExtendedAttributes GT SEMICOLON
+ """
+ readonly = p[1]
+ maplikeOrSetlikeType = p[2]
+ location = self.getLocation(p, 2)
+ identifier = IDLUnresolvedIdentifier(
+ location, "__maplike", allowDoubleUnderscore=True
+ )
+ keyType = p[4]
+ valueType = p[6]
+ p[0] = IDLMaplikeOrSetlike(
+ location, identifier, maplikeOrSetlikeType, readonly, keyType, valueType
+ )
+
+ def p_AttributeWithQualifier(self, p):
+ """
+ Attribute : Qualifier AttributeRest
+ """
+ static = IDLInterfaceMember.Special.Static in p[1]
+ stringifier = IDLInterfaceMember.Special.Stringifier in p[1]
+ (location, identifier, type, readonly) = p[2]
+ p[0] = IDLAttribute(
+ location, identifier, type, readonly, static=static, stringifier=stringifier
+ )
+
+ def p_AttributeInherited(self, p):
+ """
+ Attribute : INHERIT AttributeRest
+ """
+ (location, identifier, type, readonly) = p[2]
+ p[0] = IDLAttribute(location, identifier, type, readonly, inherit=True)
+
+ def p_Attribute(self, p):
+ """
+ Attribute : AttributeRest
+ """
+ (location, identifier, type, readonly) = p[1]
+ p[0] = IDLAttribute(location, identifier, type, readonly, inherit=False)
+
+ def p_AttributeRest(self, p):
+ """
+ AttributeRest : ReadOnly ATTRIBUTE TypeWithExtendedAttributes AttributeName SEMICOLON
+ """
+ location = self.getLocation(p, 2)
+ readonly = p[1]
+ t = p[3]
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 4), p[4])
+ p[0] = (location, identifier, t, readonly)
+
+ def p_ReadOnly(self, p):
+ """
+ ReadOnly : READONLY
+ """
+ p[0] = True
+
+ def p_ReadOnlyEmpty(self, p):
+ """
+ ReadOnly :
+ """
+ p[0] = False
+
+ def p_Operation(self, p):
+ """
+ Operation : Qualifiers OperationRest
+ """
+ qualifiers = p[1]
+
+ # Disallow duplicates in the qualifier set
+ if not len(set(qualifiers)) == len(qualifiers):
+ raise WebIDLError(
+ "Duplicate qualifiers are not allowed", [self.getLocation(p, 1)]
+ )
+
+ static = IDLInterfaceMember.Special.Static in p[1]
+ # If static is there that's all that's allowed. This is disallowed
+ # by the parser, so we can assert here.
+ assert not static or len(qualifiers) == 1
+
+ stringifier = IDLInterfaceMember.Special.Stringifier in p[1]
+ # If stringifier is there that's all that's allowed. This is disallowed
+ # by the parser, so we can assert here.
+ assert not stringifier or len(qualifiers) == 1
+
+ getter = True if IDLMethod.Special.Getter in p[1] else False
+ setter = True if IDLMethod.Special.Setter in p[1] else False
+ deleter = True if IDLMethod.Special.Deleter in p[1] else False
+ legacycaller = True if IDLMethod.Special.LegacyCaller in p[1] else False
+
+ if getter or deleter:
+ if setter:
+ raise WebIDLError(
+ "getter and deleter are incompatible with setter",
+ [self.getLocation(p, 1)],
+ )
+
+ (returnType, identifier, arguments) = p[2]
+
+ assert isinstance(returnType, IDLType)
+
+ specialType = IDLMethod.NamedOrIndexed.Neither
+
+ if getter or deleter:
+ if len(arguments) != 1:
+ raise WebIDLError(
+ "%s has wrong number of arguments"
+ % ("getter" if getter else "deleter"),
+ [self.getLocation(p, 2)],
+ )
+ argType = arguments[0].type
+ if argType == BuiltinTypes[IDLBuiltinType.Types.domstring]:
+ specialType = IDLMethod.NamedOrIndexed.Named
+ elif argType == BuiltinTypes[IDLBuiltinType.Types.unsigned_long]:
+ specialType = IDLMethod.NamedOrIndexed.Indexed
+ if deleter:
+ raise WebIDLError(
+ "There is no such thing as an indexed deleter.",
+ [self.getLocation(p, 1)],
+ )
+ else:
+ raise WebIDLError(
+ "%s has wrong argument type (must be DOMString or UnsignedLong)"
+ % ("getter" if getter else "deleter"),
+ [arguments[0].location],
+ )
+ if arguments[0].optional or arguments[0].variadic:
+ raise WebIDLError(
+ "%s cannot have %s argument"
+ % (
+ "getter" if getter else "deleter",
+ "optional" if arguments[0].optional else "variadic",
+ ),
+ [arguments[0].location],
+ )
+ if getter:
+ if returnType.isUndefined():
+ raise WebIDLError(
+ "getter cannot have undefined return type", [self.getLocation(p, 2)]
+ )
+ if setter:
+ if len(arguments) != 2:
+ raise WebIDLError(
+ "setter has wrong number of arguments", [self.getLocation(p, 2)]
+ )
+ argType = arguments[0].type
+ if argType == BuiltinTypes[IDLBuiltinType.Types.domstring]:
+ specialType = IDLMethod.NamedOrIndexed.Named
+ elif argType == BuiltinTypes[IDLBuiltinType.Types.unsigned_long]:
+ specialType = IDLMethod.NamedOrIndexed.Indexed
+ else:
+ raise WebIDLError(
+ "settter has wrong argument type (must be DOMString or UnsignedLong)",
+ [arguments[0].location],
+ )
+ if arguments[0].optional or arguments[0].variadic:
+ raise WebIDLError(
+ "setter cannot have %s argument"
+ % ("optional" if arguments[0].optional else "variadic"),
+ [arguments[0].location],
+ )
+ if arguments[1].optional or arguments[1].variadic:
+ raise WebIDLError(
+ "setter cannot have %s argument"
+ % ("optional" if arguments[1].optional else "variadic"),
+ [arguments[1].location],
+ )
+
+ if stringifier:
+ if len(arguments) != 0:
+ raise WebIDLError(
+ "stringifier has wrong number of arguments",
+ [self.getLocation(p, 2)],
+ )
+ if not returnType.isDOMString():
+ raise WebIDLError(
+ "stringifier must have DOMString return type",
+ [self.getLocation(p, 2)],
+ )
+
+ # identifier might be None. This is only permitted for special methods.
+ if not identifier:
+ if (
+ not getter
+ and not setter
+ and not deleter
+ and not legacycaller
+ and not stringifier
+ ):
+ raise WebIDLError(
+ "Identifier required for non-special methods",
+ [self.getLocation(p, 2)],
+ )
+
+ location = BuiltinLocation("<auto-generated-identifier>")
+ identifier = IDLUnresolvedIdentifier(
+ location,
+ "__%s%s%s%s%s%s"
+ % (
+ "named"
+ if specialType == IDLMethod.NamedOrIndexed.Named
+ else "indexed"
+ if specialType == IDLMethod.NamedOrIndexed.Indexed
+ else "",
+ "getter" if getter else "",
+ "setter" if setter else "",
+ "deleter" if deleter else "",
+ "legacycaller" if legacycaller else "",
+ "stringifier" if stringifier else "",
+ ),
+ allowDoubleUnderscore=True,
+ )
+
+ method = IDLMethod(
+ self.getLocation(p, 2),
+ identifier,
+ returnType,
+ arguments,
+ static=static,
+ getter=getter,
+ setter=setter,
+ deleter=deleter,
+ specialType=specialType,
+ legacycaller=legacycaller,
+ stringifier=stringifier,
+ )
+ p[0] = method
+
+ def p_Stringifier(self, p):
+ """
+ Operation : STRINGIFIER SEMICOLON
+ """
+ identifier = IDLUnresolvedIdentifier(
+ BuiltinLocation("<auto-generated-identifier>"),
+ "__stringifier",
+ allowDoubleUnderscore=True,
+ )
+ method = IDLMethod(
+ self.getLocation(p, 1),
+ identifier,
+ returnType=BuiltinTypes[IDLBuiltinType.Types.domstring],
+ arguments=[],
+ stringifier=True,
+ )
+ p[0] = method
+
+ def p_QualifierStatic(self, p):
+ """
+ Qualifier : STATIC
+ """
+ p[0] = [IDLInterfaceMember.Special.Static]
+
+ def p_QualifierStringifier(self, p):
+ """
+ Qualifier : STRINGIFIER
+ """
+ p[0] = [IDLInterfaceMember.Special.Stringifier]
+
+ def p_Qualifiers(self, p):
+ """
+ Qualifiers : Qualifier
+ | Specials
+ """
+ p[0] = p[1]
+
+ def p_Specials(self, p):
+ """
+ Specials : Special Specials
+ """
+ p[0] = [p[1]]
+ p[0].extend(p[2])
+
+ def p_SpecialsEmpty(self, p):
+ """
+ Specials :
+ """
+ p[0] = []
+
+ def p_SpecialGetter(self, p):
+ """
+ Special : GETTER
+ """
+ p[0] = IDLMethod.Special.Getter
+
+ def p_SpecialSetter(self, p):
+ """
+ Special : SETTER
+ """
+ p[0] = IDLMethod.Special.Setter
+
+ def p_SpecialDeleter(self, p):
+ """
+ Special : DELETER
+ """
+ p[0] = IDLMethod.Special.Deleter
+
+ def p_SpecialLegacyCaller(self, p):
+ """
+ Special : LEGACYCALLER
+ """
+ p[0] = IDLMethod.Special.LegacyCaller
+
+ def p_OperationRest(self, p):
+ """
+ OperationRest : Type OptionalIdentifier LPAREN ArgumentList RPAREN SEMICOLON
+ """
+ p[0] = (p[1], p[2], p[4])
+
+ def p_OptionalIdentifier(self, p):
+ """
+ OptionalIdentifier : IDENTIFIER
+ """
+ p[0] = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1])
+
+ def p_OptionalIdentifierEmpty(self, p):
+ """
+ OptionalIdentifier :
+ """
+ pass
+
+ def p_ArgumentList(self, p):
+ """
+ ArgumentList : Argument Arguments
+ """
+ p[0] = [p[1]] if p[1] else []
+ p[0].extend(p[2])
+
+ def p_ArgumentListEmpty(self, p):
+ """
+ ArgumentList :
+ """
+ p[0] = []
+
+ def p_Arguments(self, p):
+ """
+ Arguments : COMMA Argument Arguments
+ """
+ p[0] = [p[2]] if p[2] else []
+ p[0].extend(p[3])
+
+ def p_ArgumentsEmpty(self, p):
+ """
+ Arguments :
+ """
+ p[0] = []
+
+ def p_Argument(self, p):
+ """
+ Argument : ExtendedAttributeList ArgumentRest
+ """
+ p[0] = p[2]
+ p[0].addExtendedAttributes(p[1])
+
+ def p_ArgumentRestOptional(self, p):
+ """
+ ArgumentRest : OPTIONAL TypeWithExtendedAttributes ArgumentName Default
+ """
+ t = p[2]
+ assert isinstance(t, IDLType)
+ # Arg names can be reserved identifiers
+ identifier = IDLUnresolvedIdentifier(
+ self.getLocation(p, 3), p[3], allowForbidden=True
+ )
+
+ defaultValue = p[4]
+
+ # We can't test t.isAny() here and give it a default value as needed,
+ # since at this point t is not a fully resolved type yet (e.g. it might
+ # be a typedef). We'll handle the 'any' case in IDLArgument.complete.
+
+ p[0] = IDLArgument(
+ self.getLocation(p, 3), identifier, t, True, defaultValue, False
+ )
+
+ def p_ArgumentRest(self, p):
+ """
+ ArgumentRest : Type Ellipsis ArgumentName
+ """
+ t = p[1]
+ assert isinstance(t, IDLType)
+ # Arg names can be reserved identifiers
+ identifier = IDLUnresolvedIdentifier(
+ self.getLocation(p, 3), p[3], allowForbidden=True
+ )
+
+ variadic = p[2]
+
+ # We can't test t.isAny() here and give it a default value as needed,
+ # since at this point t is not a fully resolved type yet (e.g. it might
+ # be a typedef). We'll handle the 'any' case in IDLArgument.complete.
+
+ # variadic implies optional
+ # Any attributes that precede this may apply to the type, so
+ # we configure the argument to forward type attributes down instead of producing
+ # a parse error
+ p[0] = IDLArgument(
+ self.getLocation(p, 3),
+ identifier,
+ t,
+ variadic,
+ None,
+ variadic,
+ allowTypeAttributes=True,
+ )
+
+ def p_ArgumentName(self, p):
+ """
+ ArgumentName : IDENTIFIER
+ | ArgumentNameKeyword
+ """
+ p[0] = p[1]
+
+ def p_ArgumentNameKeyword(self, p):
+ """
+ ArgumentNameKeyword : ASYNC
+ | ATTRIBUTE
+ | CALLBACK
+ | CONST
+ | CONSTRUCTOR
+ | DELETER
+ | DICTIONARY
+ | ENUM
+ | EXCEPTION
+ | GETTER
+ | INCLUDES
+ | INHERIT
+ | INTERFACE
+ | ITERABLE
+ | LEGACYCALLER
+ | MAPLIKE
+ | MIXIN
+ | NAMESPACE
+ | PARTIAL
+ | READONLY
+ | REQUIRED
+ | SERIALIZER
+ | SETLIKE
+ | SETTER
+ | STATIC
+ | STRINGIFIER
+ | TYPEDEF
+ | UNRESTRICTED
+ """
+ p[0] = p[1]
+
+ def p_AttributeName(self, p):
+ """
+ AttributeName : IDENTIFIER
+ | AttributeNameKeyword
+ """
+ p[0] = p[1]
+
+ def p_AttributeNameKeyword(self, p):
+ """
+ AttributeNameKeyword : ASYNC
+ | REQUIRED
+ """
+ p[0] = p[1]
+
+ def p_Ellipsis(self, p):
+ """
+ Ellipsis : ELLIPSIS
+ """
+ p[0] = True
+
+ def p_EllipsisEmpty(self, p):
+ """
+ Ellipsis :
+ """
+ p[0] = False
+
+ def p_ExceptionMember(self, p):
+ """
+ ExceptionMember : Const
+ | ExceptionField
+ """
+ pass
+
+ def p_ExceptionField(self, p):
+ """
+ ExceptionField : Type IDENTIFIER SEMICOLON
+ """
+ pass
+
+ def p_ExtendedAttributeList(self, p):
+ """
+ ExtendedAttributeList : LBRACKET ExtendedAttribute ExtendedAttributes RBRACKET
+ """
+ p[0] = [p[2]]
+ if p[3]:
+ p[0].extend(p[3])
+
+ def p_ExtendedAttributeListEmpty(self, p):
+ """
+ ExtendedAttributeList :
+ """
+ p[0] = []
+
+ def p_ExtendedAttribute(self, p):
+ """
+ ExtendedAttribute : ExtendedAttributeNoArgs
+ | ExtendedAttributeArgList
+ | ExtendedAttributeIdent
+ | ExtendedAttributeWildcard
+ | ExtendedAttributeNamedArgList
+ | ExtendedAttributeIdentList
+ """
+ p[0] = IDLExtendedAttribute(self.getLocation(p, 1), p[1])
+
+ def p_ExtendedAttributeEmpty(self, p):
+ """
+ ExtendedAttribute :
+ """
+ pass
+
+ def p_ExtendedAttributes(self, p):
+ """
+ ExtendedAttributes : COMMA ExtendedAttribute ExtendedAttributes
+ """
+ p[0] = [p[2]] if p[2] else []
+ p[0].extend(p[3])
+
+ def p_ExtendedAttributesEmpty(self, p):
+ """
+ ExtendedAttributes :
+ """
+ p[0] = []
+
+ def p_Other(self, p):
+ """
+ Other : INTEGER
+ | FLOATLITERAL
+ | IDENTIFIER
+ | STRING
+ | OTHER
+ | ELLIPSIS
+ | COLON
+ | SCOPE
+ | SEMICOLON
+ | LT
+ | EQUALS
+ | GT
+ | QUESTIONMARK
+ | ASTERISK
+ | DOMSTRING
+ | BYTESTRING
+ | USVSTRING
+ | UTF8STRING
+ | JSSTRING
+ | PROMISE
+ | ANY
+ | BOOLEAN
+ | BYTE
+ | DOUBLE
+ | FALSE
+ | FLOAT
+ | LONG
+ | NULL
+ | OBJECT
+ | OCTET
+ | OR
+ | OPTIONAL
+ | RECORD
+ | SEQUENCE
+ | SHORT
+ | SYMBOL
+ | TRUE
+ | UNSIGNED
+ | UNDEFINED
+ | ArgumentNameKeyword
+ """
+ pass
+
+ def p_OtherOrComma(self, p):
+ """
+ OtherOrComma : Other
+ | COMMA
+ """
+ pass
+
+ def p_TypeSingleType(self, p):
+ """
+ Type : SingleType
+ """
+ p[0] = p[1]
+
+ def p_TypeUnionType(self, p):
+ """
+ Type : UnionType Null
+ """
+ p[0] = self.handleNullable(p[1], p[2])
+
+ def p_TypeWithExtendedAttributes(self, p):
+ """
+ TypeWithExtendedAttributes : ExtendedAttributeList Type
+ """
+ p[0] = p[2].withExtendedAttributes(p[1])
+
+ def p_SingleTypeDistinguishableType(self, p):
+ """
+ SingleType : DistinguishableType
+ """
+ p[0] = p[1]
+
+ def p_SingleTypeAnyType(self, p):
+ """
+ SingleType : ANY
+ """
+ p[0] = BuiltinTypes[IDLBuiltinType.Types.any]
+
+ def p_SingleTypePromiseType(self, p):
+ """
+ SingleType : PROMISE LT Type GT
+ """
+ p[0] = IDLPromiseType(self.getLocation(p, 1), p[3])
+
+ def p_UnionType(self, p):
+ """
+ UnionType : LPAREN UnionMemberType OR UnionMemberType UnionMemberTypes RPAREN
+ """
+ types = [p[2], p[4]]
+ types.extend(p[5])
+ p[0] = IDLUnionType(self.getLocation(p, 1), types)
+
+ def p_UnionMemberTypeDistinguishableType(self, p):
+ """
+ UnionMemberType : ExtendedAttributeList DistinguishableType
+ """
+ p[0] = p[2].withExtendedAttributes(p[1])
+
+ def p_UnionMemberType(self, p):
+ """
+ UnionMemberType : UnionType Null
+ """
+ p[0] = self.handleNullable(p[1], p[2])
+
+ def p_UnionMemberTypes(self, p):
+ """
+ UnionMemberTypes : OR UnionMemberType UnionMemberTypes
+ """
+ p[0] = [p[2]]
+ p[0].extend(p[3])
+
+ def p_UnionMemberTypesEmpty(self, p):
+ """
+ UnionMemberTypes :
+ """
+ p[0] = []
+
+ def p_DistinguishableType(self, p):
+ """
+ DistinguishableType : PrimitiveType Null
+ | ARRAYBUFFER Null
+ | OBJECT Null
+ | UNDEFINED Null
+ """
+ if p[1] == "object":
+ type = BuiltinTypes[IDLBuiltinType.Types.object]
+ elif p[1] == "ArrayBuffer":
+ type = BuiltinTypes[IDLBuiltinType.Types.ArrayBuffer]
+ elif p[1] == "undefined":
+ type = BuiltinTypes[IDLBuiltinType.Types.undefined]
+ else:
+ type = BuiltinTypes[p[1]]
+
+ p[0] = self.handleNullable(type, p[2])
+
+ def p_DistinguishableTypeStringType(self, p):
+ """
+ DistinguishableType : StringType Null
+ """
+ p[0] = self.handleNullable(p[1], p[2])
+
+ def p_DistinguishableTypeSequenceType(self, p):
+ """
+ DistinguishableType : SEQUENCE LT TypeWithExtendedAttributes GT Null
+ """
+ innerType = p[3]
+ type = IDLSequenceType(self.getLocation(p, 1), innerType)
+ p[0] = self.handleNullable(type, p[5])
+
+ def p_DistinguishableTypeRecordType(self, p):
+ """
+ DistinguishableType : RECORD LT StringType COMMA TypeWithExtendedAttributes GT Null
+ """
+ keyType = p[3]
+ valueType = p[5]
+ type = IDLRecordType(self.getLocation(p, 1), keyType, valueType)
+ p[0] = self.handleNullable(type, p[7])
+
+ def p_DistinguishableTypeObservableArrayType(self, p):
+ """
+ DistinguishableType : OBSERVABLEARRAY LT TypeWithExtendedAttributes GT Null
+ """
+ innerType = p[3]
+ type = IDLObservableArrayType(self.getLocation(p, 1), innerType)
+ p[0] = self.handleNullable(type, p[5])
+
+ def p_DistinguishableTypeScopedName(self, p):
+ """
+ DistinguishableType : ScopedName Null
+ """
+ assert isinstance(p[1], IDLUnresolvedIdentifier)
+
+ if p[1].name == "Promise":
+ raise WebIDLError(
+ "Promise used without saying what it's " "parametrized over",
+ [self.getLocation(p, 1)],
+ )
+
+ type = None
+
+ try:
+ if self.globalScope()._lookupIdentifier(p[1]):
+ obj = self.globalScope()._lookupIdentifier(p[1])
+ assert not obj.isType()
+ if obj.isTypedef():
+ type = IDLTypedefType(
+ self.getLocation(p, 1), obj.innerType, obj.identifier.name
+ )
+ elif obj.isCallback() and not obj.isInterface():
+ type = IDLCallbackType(self.getLocation(p, 1), obj)
+ else:
+ type = IDLWrapperType(self.getLocation(p, 1), p[1])
+ p[0] = self.handleNullable(type, p[2])
+ return
+ except Exception:
+ pass
+
+ type = IDLUnresolvedType(self.getLocation(p, 1), p[1])
+ p[0] = self.handleNullable(type, p[2])
+
+ def p_ConstType(self, p):
+ """
+ ConstType : PrimitiveType
+ """
+ p[0] = BuiltinTypes[p[1]]
+
+ def p_ConstTypeIdentifier(self, p):
+ """
+ ConstType : IDENTIFIER
+ """
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1])
+
+ p[0] = IDLUnresolvedType(self.getLocation(p, 1), identifier)
+
+ def p_PrimitiveTypeUint(self, p):
+ """
+ PrimitiveType : UnsignedIntegerType
+ """
+ p[0] = p[1]
+
+ def p_PrimitiveTypeBoolean(self, p):
+ """
+ PrimitiveType : BOOLEAN
+ """
+ p[0] = IDLBuiltinType.Types.boolean
+
+ def p_PrimitiveTypeByte(self, p):
+ """
+ PrimitiveType : BYTE
+ """
+ p[0] = IDLBuiltinType.Types.byte
+
+ def p_PrimitiveTypeOctet(self, p):
+ """
+ PrimitiveType : OCTET
+ """
+ p[0] = IDLBuiltinType.Types.octet
+
+ def p_PrimitiveTypeFloat(self, p):
+ """
+ PrimitiveType : FLOAT
+ """
+ p[0] = IDLBuiltinType.Types.float
+
+ def p_PrimitiveTypeUnrestictedFloat(self, p):
+ """
+ PrimitiveType : UNRESTRICTED FLOAT
+ """
+ p[0] = IDLBuiltinType.Types.unrestricted_float
+
+ def p_PrimitiveTypeDouble(self, p):
+ """
+ PrimitiveType : DOUBLE
+ """
+ p[0] = IDLBuiltinType.Types.double
+
+ def p_PrimitiveTypeUnrestictedDouble(self, p):
+ """
+ PrimitiveType : UNRESTRICTED DOUBLE
+ """
+ p[0] = IDLBuiltinType.Types.unrestricted_double
+
+ def p_StringType(self, p):
+ """
+ StringType : BuiltinStringType
+ """
+ p[0] = BuiltinTypes[p[1]]
+
+ def p_BuiltinStringTypeDOMString(self, p):
+ """
+ BuiltinStringType : DOMSTRING
+ """
+ p[0] = IDLBuiltinType.Types.domstring
+
+ def p_BuiltinStringTypeBytestring(self, p):
+ """
+ BuiltinStringType : BYTESTRING
+ """
+ p[0] = IDLBuiltinType.Types.bytestring
+
+ def p_BuiltinStringTypeUSVString(self, p):
+ """
+ BuiltinStringType : USVSTRING
+ """
+ p[0] = IDLBuiltinType.Types.usvstring
+
+ def p_BuiltinStringTypeUTF8String(self, p):
+ """
+ BuiltinStringType : UTF8STRING
+ """
+ p[0] = IDLBuiltinType.Types.utf8string
+
+ def p_BuiltinStringTypeJSString(self, p):
+ """
+ BuiltinStringType : JSSTRING
+ """
+ p[0] = IDLBuiltinType.Types.jsstring
+
+ def p_UnsignedIntegerTypeUnsigned(self, p):
+ """
+ UnsignedIntegerType : UNSIGNED IntegerType
+ """
+ # Adding one to a given signed integer type gets you the unsigned type:
+ p[0] = p[2] + 1
+
+ def p_UnsignedIntegerType(self, p):
+ """
+ UnsignedIntegerType : IntegerType
+ """
+ p[0] = p[1]
+
+ def p_IntegerTypeShort(self, p):
+ """
+ IntegerType : SHORT
+ """
+ p[0] = IDLBuiltinType.Types.short
+
+ def p_IntegerTypeLong(self, p):
+ """
+ IntegerType : LONG OptionalLong
+ """
+ if p[2]:
+ p[0] = IDLBuiltinType.Types.long_long
+ else:
+ p[0] = IDLBuiltinType.Types.long
+
+ def p_OptionalLong(self, p):
+ """
+ OptionalLong : LONG
+ """
+ p[0] = True
+
+ def p_OptionalLongEmpty(self, p):
+ """
+ OptionalLong :
+ """
+ p[0] = False
+
+ def p_Null(self, p):
+ """
+ Null : QUESTIONMARK
+ |
+ """
+ if len(p) > 1:
+ p[0] = self.getLocation(p, 1)
+ else:
+ p[0] = None
+
+ def p_ScopedName(self, p):
+ """
+ ScopedName : AbsoluteScopedName
+ | RelativeScopedName
+ """
+ p[0] = p[1]
+
+ def p_AbsoluteScopedName(self, p):
+ """
+ AbsoluteScopedName : SCOPE IDENTIFIER ScopedNameParts
+ """
+ assert False
+ pass
+
+ def p_RelativeScopedName(self, p):
+ """
+ RelativeScopedName : IDENTIFIER ScopedNameParts
+ """
+ assert not p[2] # Not implemented!
+
+ p[0] = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1])
+
+ def p_ScopedNameParts(self, p):
+ """
+ ScopedNameParts : SCOPE IDENTIFIER ScopedNameParts
+ """
+ assert False
+ pass
+
+ def p_ScopedNamePartsEmpty(self, p):
+ """
+ ScopedNameParts :
+ """
+ p[0] = None
+
+ def p_ExtendedAttributeNoArgs(self, p):
+ """
+ ExtendedAttributeNoArgs : IDENTIFIER
+ """
+ p[0] = (p[1],)
+
+ def p_ExtendedAttributeArgList(self, p):
+ """
+ ExtendedAttributeArgList : IDENTIFIER LPAREN ArgumentList RPAREN
+ """
+ p[0] = (p[1], p[3])
+
+ def p_ExtendedAttributeIdent(self, p):
+ """
+ ExtendedAttributeIdent : IDENTIFIER EQUALS STRING
+ | IDENTIFIER EQUALS IDENTIFIER
+ """
+ p[0] = (p[1], p[3])
+
+ def p_ExtendedAttributeWildcard(self, p):
+ """
+ ExtendedAttributeWildcard : IDENTIFIER EQUALS ASTERISK
+ """
+ p[0] = (p[1], p[3])
+
+ def p_ExtendedAttributeNamedArgList(self, p):
+ """
+ ExtendedAttributeNamedArgList : IDENTIFIER EQUALS IDENTIFIER LPAREN ArgumentList RPAREN
+ """
+ p[0] = (p[1], p[3], p[5])
+
+ def p_ExtendedAttributeIdentList(self, p):
+ """
+ ExtendedAttributeIdentList : IDENTIFIER EQUALS LPAREN IdentifierList RPAREN
+ """
+ p[0] = (p[1], p[4])
+
+ def p_IdentifierList(self, p):
+ """
+ IdentifierList : IDENTIFIER Identifiers
+ """
+ idents = list(p[2])
+ # This is only used for identifier-list-valued extended attributes, and if
+ # we're going to restrict to IDENTIFIER here we should at least allow
+ # escaping with leading '_' as usual for identifiers.
+ ident = p[1]
+ if ident[0] == "_":
+ ident = ident[1:]
+ idents.insert(0, ident)
+ p[0] = idents
+
+ def p_IdentifiersList(self, p):
+ """
+ Identifiers : COMMA IDENTIFIER Identifiers
+ """
+ idents = list(p[3])
+ # This is only used for identifier-list-valued extended attributes, and if
+ # we're going to restrict to IDENTIFIER here we should at least allow
+ # escaping with leading '_' as usual for identifiers.
+ ident = p[2]
+ if ident[0] == "_":
+ ident = ident[1:]
+ idents.insert(0, ident)
+ p[0] = idents
+
+ def p_IdentifiersEmpty(self, p):
+ """
+ Identifiers :
+ """
+ p[0] = []
+
+ def p_error(self, p):
+ if not p:
+ raise WebIDLError(
+ (
+ "Syntax Error at end of file. Possibly due to "
+ "missing semicolon(;), braces(}) or both"
+ ),
+ [self._filename],
+ )
+ else:
+ raise WebIDLError(
+ "invalid syntax",
+ [Location(self.lexer, p.lineno, p.lexpos, self._filename)],
+ )
+
+ def __init__(self, outputdir="", lexer=None):
+ Tokenizer.__init__(self, outputdir, lexer)
+
+ logger = SqueakyCleanLogger()
+ try:
+ self.parser = yacc.yacc(
+ module=self,
+ outputdir=outputdir,
+ errorlog=logger,
+ write_tables=False,
+ # Pickling the grammar is a speedup in
+ # some cases (older Python?) but a
+ # significant slowdown in others.
+ # We're not pickling for now, until it
+ # becomes a speedup again.
+ # , picklefile='WebIDLGrammar.pkl'
+ )
+ finally:
+ logger.reportGrammarErrors()
+
+ self._globalScope = IDLScope(BuiltinLocation("<Global Scope>"), None, None)
+ self._installBuiltins(self._globalScope)
+ self._productions = []
+
+ self._filename = "<builtin>"
+ self.lexer.input(Parser._builtins)
+ self._filename = None
+
+ self.parser.parse(lexer=self.lexer, tracking=True)
+
+ def _installBuiltins(self, scope):
+ assert isinstance(scope, IDLScope)
+
+ # range omits the last value.
+ for x in range(
+ IDLBuiltinType.Types.ArrayBuffer, IDLBuiltinType.Types.Float64Array + 1
+ ):
+ builtin = BuiltinTypes[x]
+ name = builtin.name
+ IDLTypedef(BuiltinLocation("<builtin type>"), scope, builtin, name)
+
+ @staticmethod
+ def handleNullable(type, questionMarkLocation):
+ if questionMarkLocation is not None:
+ type = IDLNullableType(questionMarkLocation, type)
+
+ return type
+
+ def parse(self, t, filename=None):
+ self.lexer.input(t)
+
+ # for tok in iter(self.lexer.token, None):
+ # print tok
+
+ self._filename = filename
+ self._productions.extend(self.parser.parse(lexer=self.lexer, tracking=True))
+ self._filename = None
+
+ def finish(self):
+ # If we have interfaces that are iterable, create their
+ # iterator interfaces and add them to the productions array.
+ interfaceStatements = []
+ for p in self._productions:
+ if isinstance(p, IDLInterface):
+ interfaceStatements.append(p)
+
+ for iface in interfaceStatements:
+ iterable = None
+ # We haven't run finish() on the interface yet, so we don't know
+ # whether our interface is maplike/setlike/iterable or not. This
+ # means we have to loop through the members to see if we have an
+ # iterable member.
+ for m in iface.members:
+ if isinstance(m, (IDLIterable, IDLAsyncIterable)):
+ iterable = m
+ break
+ if iterable and (iterable.isPairIterator() or iterable.isAsyncIterable()):
+
+ def simpleExtendedAttr(str):
+ return IDLExtendedAttribute(iface.location, (str,))
+
+ if isinstance(iterable, IDLAsyncIterable):
+ nextReturnType = IDLPromiseType(
+ iterable.location, BuiltinTypes[IDLBuiltinType.Types.any]
+ )
+ else:
+ nextReturnType = BuiltinTypes[IDLBuiltinType.Types.object]
+ nextMethod = IDLMethod(
+ iterable.location,
+ IDLUnresolvedIdentifier(iterable.location, "next"),
+ nextReturnType,
+ [],
+ )
+ nextMethod.addExtendedAttributes([simpleExtendedAttr("Throws")])
+
+ methods = [nextMethod]
+
+ if iterable.getExtendedAttribute("GenerateReturnMethod"):
+ assert isinstance(iterable, IDLAsyncIterable)
+
+ returnMethod = IDLMethod(
+ iterable.location,
+ IDLUnresolvedIdentifier(iterable.location, "return"),
+ IDLPromiseType(
+ iterable.location, BuiltinTypes[IDLBuiltinType.Types.any]
+ ),
+ [
+ IDLArgument(
+ iterable.location,
+ IDLUnresolvedIdentifier(
+ BuiltinLocation("<auto-generated-identifier>"),
+ "value",
+ ),
+ BuiltinTypes[IDLBuiltinType.Types.any],
+ optional=True,
+ ),
+ ],
+ )
+ returnMethod.addExtendedAttributes([simpleExtendedAttr("Throws")])
+ methods.append(returnMethod)
+
+ if iterable.isIterable():
+ itr_suffix = "Iterator"
+ else:
+ itr_suffix = "AsyncIterator"
+ itr_ident = IDLUnresolvedIdentifier(
+ iface.location, iface.identifier.name + itr_suffix
+ )
+ if iterable.isIterable():
+ classNameOverride = iface.identifier.name + " Iterator"
+ elif iterable.isAsyncIterable():
+ classNameOverride = iface.identifier.name + " AsyncIterator"
+ itr_iface = IDLInterface(
+ iface.location,
+ self.globalScope(),
+ itr_ident,
+ None,
+ methods,
+ isKnownNonPartial=True,
+ classNameOverride=classNameOverride,
+ )
+ itr_iface.addExtendedAttributes(
+ [simpleExtendedAttr("LegacyNoInterfaceObject")]
+ )
+ # Make sure the exposure set for the iterator interface is the
+ # same as the exposure set for the iterable interface, because
+ # we're going to generate methods on the iterable that return
+ # instances of the iterator.
+ itr_iface._exposureGlobalNames = set(iface._exposureGlobalNames)
+ # Always append generated iterable interfaces after the
+ # interface they're a member of, otherwise nativeType generation
+ # won't work correctly.
+ if iterable.isIterable():
+ itr_iface.iterableInterface = iface
+ else:
+ itr_iface.asyncIterableInterface = iface
+ self._productions.append(itr_iface)
+ iterable.iteratorType = IDLWrapperType(iface.location, itr_iface)
+
+ # Make sure we finish IDLIncludesStatements before we finish the
+ # IDLInterfaces.
+ # XXX khuey hates this bit and wants to nuke it from orbit.
+ includesStatements = [
+ p for p in self._productions if isinstance(p, IDLIncludesStatement)
+ ]
+ otherStatements = [
+ p for p in self._productions if not isinstance(p, IDLIncludesStatement)
+ ]
+ for production in includesStatements:
+ production.finish(self.globalScope())
+ for production in otherStatements:
+ production.finish(self.globalScope())
+
+ # Do any post-finish validation we need to do
+ for production in self._productions:
+ production.validate()
+
+ # De-duplicate self._productions, without modifying its order.
+ seen = set()
+ result = []
+ for p in self._productions:
+ if p not in seen:
+ seen.add(p)
+ result.append(p)
+ return result
+
+ def reset(self):
+ return Parser(lexer=self.lexer)
+
+ # Builtin IDL defined by WebIDL
+ _builtins = """
+ typedef (ArrayBufferView or ArrayBuffer) BufferSource;
+ """
+
+
+def main():
+ # Parse arguments.
+ from optparse import OptionParser
+
+ usageString = "usage: %prog [options] files"
+ o = OptionParser(usage=usageString)
+ o.add_option(
+ "--cachedir",
+ dest="cachedir",
+ default=None,
+ help="Directory in which to cache lex/parse tables.",
+ )
+ o.add_option(
+ "--verbose-errors",
+ action="store_true",
+ default=False,
+ help="When an error happens, display the Python traceback.",
+ )
+ (options, args) = o.parse_args()
+
+ if len(args) < 1:
+ o.error(usageString)
+
+ fileList = args
+ baseDir = os.getcwd()
+
+ # Parse the WebIDL.
+ parser = Parser(options.cachedir)
+ try:
+ for filename in fileList:
+ fullPath = os.path.normpath(os.path.join(baseDir, filename))
+ f = open(fullPath, "rb")
+ lines = f.readlines()
+ f.close()
+ print(fullPath)
+ parser.parse("".join(lines), fullPath)
+ parser.finish()
+ except WebIDLError as e:
+ if options.verbose_errors:
+ traceback.print_exc()
+ else:
+ print(e)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/dom/bindings/parser/runtests.py b/dom/bindings/parser/runtests.py
new file mode 100644
index 0000000000..6cef297e30
--- /dev/null
+++ b/dom/bindings/parser/runtests.py
@@ -0,0 +1,142 @@
+# 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/.
+
+from __future__ import print_function
+
+import argparse
+import glob
+import os
+import sys
+import traceback
+
+import WebIDL
+
+
+class TestHarness(object):
+ def __init__(self, test, verbose):
+ self.test = test
+ self.verbose = verbose
+ self.printed_intro = False
+ self.passed = 0
+ self.failures = []
+
+ def start(self):
+ if self.verbose:
+ self.maybe_print_intro()
+
+ def finish(self):
+ if self.verbose or self.printed_intro:
+ print("Finished test %s" % self.test)
+
+ def maybe_print_intro(self):
+ if not self.printed_intro:
+ print("Starting test %s" % self.test)
+ self.printed_intro = True
+
+ def test_pass(self, msg):
+ self.passed += 1
+ if self.verbose:
+ print("TEST-PASS | %s" % msg)
+
+ def test_fail(self, msg):
+ self.maybe_print_intro()
+ self.failures.append(msg)
+ print("TEST-UNEXPECTED-FAIL | %s" % msg)
+
+ def ok(self, condition, msg):
+ if condition:
+ self.test_pass(msg)
+ else:
+ self.test_fail(msg)
+
+ def check(self, a, b, msg):
+ if a == b:
+ self.test_pass(msg)
+ else:
+ self.test_fail(msg + " | Got %s expected %s" % (a, b))
+
+ def should_throw(self, parser, code, msg):
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(code)
+ parser.finish()
+ except Exception:
+ threw = True
+
+ self.ok(threw, "Should have thrown: %s" % msg)
+
+
+def run_tests(tests, verbose):
+ testdir = os.path.join(os.path.dirname(__file__), "tests")
+ if not tests:
+ tests = glob.iglob(os.path.join(testdir, "*.py"))
+ sys.path.append(testdir)
+
+ all_passed = 0
+ failed_tests = []
+
+ for test in tests:
+ (testpath, ext) = os.path.splitext(os.path.basename(test))
+ _test = __import__(testpath, globals(), locals(), ["WebIDLTest"])
+
+ harness = TestHarness(test, verbose)
+ harness.start()
+ try:
+ _test.WebIDLTest.__call__(WebIDL.Parser(), harness)
+ except Exception as ex:
+ harness.test_fail("Unhandled exception in test %s: %s" % (testpath, ex))
+ traceback.print_exc()
+ finally:
+ harness.finish()
+ all_passed += harness.passed
+ if harness.failures:
+ failed_tests.append((test, harness.failures))
+
+ if verbose or failed_tests:
+ print()
+ print("Result summary:")
+ print("Successful: %d" % all_passed)
+ print("Unexpected: %d" % sum(len(failures) for _, failures in failed_tests))
+ for test, failures in failed_tests:
+ print("%s:" % test)
+ for failure in failures:
+ print("TEST-UNEXPECTED-FAIL | %s" % failure)
+ return 1 if failed_tests else 0
+
+
+def get_parser():
+ usage = """%(prog)s [OPTIONS] [TESTS]
+ Where TESTS are relative to the tests directory."""
+ parser = argparse.ArgumentParser(usage=usage)
+ parser.add_argument(
+ "-q",
+ "--quiet",
+ action="store_false",
+ dest="verbose",
+ help="Don't print passing tests.",
+ default=None,
+ )
+ parser.add_argument(
+ "-v",
+ "--verbose",
+ action="store_true",
+ dest="verbose",
+ help="Run tests in verbose mode.",
+ )
+ parser.add_argument("tests", nargs="*", help="Tests to run")
+ return parser
+
+
+if __name__ == "__main__":
+ parser = get_parser()
+ args = parser.parse_args()
+ if args.verbose is None:
+ args.verbose = True
+
+ # Make sure the current directory is in the python path so we can cache the
+ # result of the webidlyacc.py generation.
+ sys.path.append(".")
+
+ sys.exit(run_tests(args.tests, verbose=args.verbose))
diff --git a/dom/bindings/parser/tests/test_any_null.py b/dom/bindings/parser/tests/test_any_null.py
new file mode 100644
index 0000000000..d03216ed51
--- /dev/null
+++ b/dom/bindings/parser/tests/test_any_null.py
@@ -0,0 +1,16 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface DoubleNull {
+ attribute any? foo;
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/dom/bindings/parser/tests/test_argument_identifier_conflicts.py b/dom/bindings/parser/tests/test_argument_identifier_conflicts.py
new file mode 100644
index 0000000000..3ee8dc6bd6
--- /dev/null
+++ b/dom/bindings/parser/tests/test_argument_identifier_conflicts.py
@@ -0,0 +1,16 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface ArgumentIdentifierConflict {
+ undefined foo(boolean arg1, boolean arg1);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/dom/bindings/parser/tests/test_argument_keywords.py b/dom/bindings/parser/tests/test_argument_keywords.py
new file mode 100644
index 0000000000..bbed33df92
--- /dev/null
+++ b/dom/bindings/parser/tests/test_argument_keywords.py
@@ -0,0 +1,22 @@
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ interface Foo {
+ undefined foo(object constructor);
+ };
+ """
+ )
+
+ results = parser.finish()
+ harness.check(len(results), 1, "Should have an interface")
+ iface = results[0]
+ harness.check(len(iface.members), 1, "Should have an operation")
+ operation = iface.members[0]
+ harness.check(len(operation.signatures()), 1, "Should have one signature")
+ (retval, args) = operation.signatures()[0]
+ harness.check(len(args), 1, "Should have an argument")
+ harness.check(
+ args[0].identifier.name,
+ "constructor",
+ "Should have an identifier named 'constructor'",
+ )
diff --git a/dom/bindings/parser/tests/test_argument_novoid.py b/dom/bindings/parser/tests/test_argument_novoid.py
new file mode 100644
index 0000000000..571fd0b5eb
--- /dev/null
+++ b/dom/bindings/parser/tests/test_argument_novoid.py
@@ -0,0 +1,16 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface VoidArgument1 {
+ void foo(void arg2);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/dom/bindings/parser/tests/test_arraybuffer.py b/dom/bindings/parser/tests/test_arraybuffer.py
new file mode 100644
index 0000000000..b762d06ac2
--- /dev/null
+++ b/dom/bindings/parser/tests/test_arraybuffer.py
@@ -0,0 +1,95 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ interface TestArrayBuffer {
+ attribute ArrayBuffer bufferAttr;
+ undefined bufferMethod(ArrayBuffer arg1, ArrayBuffer? arg2, sequence<ArrayBuffer> arg3);
+
+ attribute ArrayBufferView viewAttr;
+ undefined viewMethod(ArrayBufferView arg1, ArrayBufferView? arg2, sequence<ArrayBufferView> arg3);
+
+ attribute Int8Array int8ArrayAttr;
+ undefined int8ArrayMethod(Int8Array arg1, Int8Array? arg2, sequence<Int8Array> arg3);
+
+ attribute Uint8Array uint8ArrayAttr;
+ undefined uint8ArrayMethod(Uint8Array arg1, Uint8Array? arg2, sequence<Uint8Array> arg3);
+
+ attribute Uint8ClampedArray uint8ClampedArrayAttr;
+ undefined uint8ClampedArrayMethod(Uint8ClampedArray arg1, Uint8ClampedArray? arg2, sequence<Uint8ClampedArray> arg3);
+
+ attribute Int16Array int16ArrayAttr;
+ undefined int16ArrayMethod(Int16Array arg1, Int16Array? arg2, sequence<Int16Array> arg3);
+
+ attribute Uint16Array uint16ArrayAttr;
+ undefined uint16ArrayMethod(Uint16Array arg1, Uint16Array? arg2, sequence<Uint16Array> arg3);
+
+ attribute Int32Array int32ArrayAttr;
+ undefined int32ArrayMethod(Int32Array arg1, Int32Array? arg2, sequence<Int32Array> arg3);
+
+ attribute Uint32Array uint32ArrayAttr;
+ undefined uint32ArrayMethod(Uint32Array arg1, Uint32Array? arg2, sequence<Uint32Array> arg3);
+
+ attribute Float32Array float32ArrayAttr;
+ undefined float32ArrayMethod(Float32Array arg1, Float32Array? arg2, sequence<Float32Array> arg3);
+
+ attribute Float64Array float64ArrayAttr;
+ undefined float64ArrayMethod(Float64Array arg1, Float64Array? arg2, sequence<Float64Array> arg3);
+ };
+ """
+ )
+
+ results = parser.finish()
+
+ iface = results[0]
+
+ harness.ok(True, "TestArrayBuffer interface parsed without error")
+ harness.check(len(iface.members), 22, "Interface should have twenty two members")
+
+ members = iface.members
+
+ def checkStuff(attr, method, t):
+ harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Expect an IDLAttribute")
+ harness.ok(isinstance(method, WebIDL.IDLMethod), "Expect an IDLMethod")
+
+ harness.check(str(attr.type), t, "Expect an ArrayBuffer type")
+ harness.ok(attr.type.isSpiderMonkeyInterface(), "Should test as a js interface")
+
+ (retType, arguments) = method.signatures()[0]
+ harness.ok(retType.isUndefined(), "Should have an undefined return type")
+ harness.check(len(arguments), 3, "Expect 3 arguments")
+
+ harness.check(str(arguments[0].type), t, "Expect an ArrayBuffer type")
+ harness.ok(
+ arguments[0].type.isSpiderMonkeyInterface(), "Should test as a js interface"
+ )
+
+ harness.check(
+ str(arguments[1].type), t + "OrNull", "Expect an ArrayBuffer type"
+ )
+ harness.ok(
+ arguments[1].type.inner.isSpiderMonkeyInterface(),
+ "Should test as a js interface",
+ )
+
+ harness.check(
+ str(arguments[2].type), t + "Sequence", "Expect an ArrayBuffer type"
+ )
+ harness.ok(
+ arguments[2].type.inner.isSpiderMonkeyInterface(),
+ "Should test as a js interface",
+ )
+
+ checkStuff(members[0], members[1], "ArrayBuffer")
+ checkStuff(members[2], members[3], "ArrayBufferView")
+ checkStuff(members[4], members[5], "Int8Array")
+ checkStuff(members[6], members[7], "Uint8Array")
+ checkStuff(members[8], members[9], "Uint8ClampedArray")
+ checkStuff(members[10], members[11], "Int16Array")
+ checkStuff(members[12], members[13], "Uint16Array")
+ checkStuff(members[14], members[15], "Int32Array")
+ checkStuff(members[16], members[17], "Uint32Array")
+ checkStuff(members[18], members[19], "Float32Array")
+ checkStuff(members[20], members[21], "Float64Array")
diff --git a/dom/bindings/parser/tests/test_attr.py b/dom/bindings/parser/tests/test_attr.py
new file mode 100644
index 0000000000..ebc47f8780
--- /dev/null
+++ b/dom/bindings/parser/tests/test_attr.py
@@ -0,0 +1,199 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ testData = [
+ ("::TestAttr%s::b", "b", "Byte%s", False),
+ ("::TestAttr%s::rb", "rb", "Byte%s", True),
+ ("::TestAttr%s::o", "o", "Octet%s", False),
+ ("::TestAttr%s::ro", "ro", "Octet%s", True),
+ ("::TestAttr%s::s", "s", "Short%s", False),
+ ("::TestAttr%s::rs", "rs", "Short%s", True),
+ ("::TestAttr%s::us", "us", "UnsignedShort%s", False),
+ ("::TestAttr%s::rus", "rus", "UnsignedShort%s", True),
+ ("::TestAttr%s::l", "l", "Long%s", False),
+ ("::TestAttr%s::rl", "rl", "Long%s", True),
+ ("::TestAttr%s::ul", "ul", "UnsignedLong%s", False),
+ ("::TestAttr%s::rul", "rul", "UnsignedLong%s", True),
+ ("::TestAttr%s::ll", "ll", "LongLong%s", False),
+ ("::TestAttr%s::rll", "rll", "LongLong%s", True),
+ ("::TestAttr%s::ull", "ull", "UnsignedLongLong%s", False),
+ ("::TestAttr%s::rull", "rull", "UnsignedLongLong%s", True),
+ ("::TestAttr%s::str", "str", "String%s", False),
+ ("::TestAttr%s::rstr", "rstr", "String%s", True),
+ ("::TestAttr%s::obj", "obj", "Object%s", False),
+ ("::TestAttr%s::robj", "robj", "Object%s", True),
+ ("::TestAttr%s::object", "object", "Object%s", False),
+ ("::TestAttr%s::f", "f", "Float%s", False),
+ ("::TestAttr%s::rf", "rf", "Float%s", True),
+ ]
+
+ parser.parse(
+ """
+ interface TestAttr {
+ attribute byte b;
+ readonly attribute byte rb;
+ attribute octet o;
+ readonly attribute octet ro;
+ attribute short s;
+ readonly attribute short rs;
+ attribute unsigned short us;
+ readonly attribute unsigned short rus;
+ attribute long l;
+ readonly attribute long rl;
+ attribute unsigned long ul;
+ readonly attribute unsigned long rul;
+ attribute long long ll;
+ readonly attribute long long rll;
+ attribute unsigned long long ull;
+ readonly attribute unsigned long long rull;
+ attribute DOMString str;
+ readonly attribute DOMString rstr;
+ attribute object obj;
+ readonly attribute object robj;
+ attribute object _object;
+ attribute float f;
+ readonly attribute float rf;
+ };
+
+ interface TestAttrNullable {
+ attribute byte? b;
+ readonly attribute byte? rb;
+ attribute octet? o;
+ readonly attribute octet? ro;
+ attribute short? s;
+ readonly attribute short? rs;
+ attribute unsigned short? us;
+ readonly attribute unsigned short? rus;
+ attribute long? l;
+ readonly attribute long? rl;
+ attribute unsigned long? ul;
+ readonly attribute unsigned long? rul;
+ attribute long long? ll;
+ readonly attribute long long? rll;
+ attribute unsigned long long? ull;
+ readonly attribute unsigned long long? rull;
+ attribute DOMString? str;
+ readonly attribute DOMString? rstr;
+ attribute object? obj;
+ readonly attribute object? robj;
+ attribute object? _object;
+ attribute float? f;
+ readonly attribute float? rf;
+ };
+ """
+ )
+
+ results = parser.finish()
+
+ def checkAttr(attr, QName, name, type, readonly):
+ harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Should be an IDLAttribute")
+ harness.ok(attr.isAttr(), "Attr is an Attr")
+ harness.ok(not attr.isMethod(), "Attr is not an method")
+ harness.ok(not attr.isConst(), "Attr is not a const")
+ harness.check(attr.identifier.QName(), QName, "Attr has the right QName")
+ harness.check(attr.identifier.name, name, "Attr has the right name")
+ harness.check(str(attr.type), type, "Attr has the right type")
+ harness.check(attr.readonly, readonly, "Attr's readonly state is correct")
+
+ harness.ok(True, "TestAttr interface parsed without error.")
+ harness.check(len(results), 2, "Should be two productions.")
+ iface = results[0]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should be an IDLInterface")
+ harness.check(
+ iface.identifier.QName(), "::TestAttr", "Interface has the right QName"
+ )
+ harness.check(iface.identifier.name, "TestAttr", "Interface has the right name")
+ harness.check(
+ len(iface.members), len(testData), "Expect %s members" % len(testData)
+ )
+
+ attrs = iface.members
+
+ for i in range(len(attrs)):
+ data = testData[i]
+ attr = attrs[i]
+ (QName, name, type, readonly) = data
+ checkAttr(attr, QName % "", name, type % "", readonly)
+
+ iface = results[1]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should be an IDLInterface")
+ harness.check(
+ iface.identifier.QName(), "::TestAttrNullable", "Interface has the right QName"
+ )
+ harness.check(
+ iface.identifier.name, "TestAttrNullable", "Interface has the right name"
+ )
+ harness.check(
+ len(iface.members), len(testData), "Expect %s members" % len(testData)
+ )
+
+ attrs = iface.members
+
+ for i in range(len(attrs)):
+ data = testData[i]
+ attr = attrs[i]
+ (QName, name, type, readonly) = data
+ checkAttr(attr, QName % "Nullable", name, type % "OrNull", readonly)
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ [SetterThrows] readonly attribute boolean foo;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow [SetterThrows] on readonly attributes")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ [Throw] readonly attribute boolean foo;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should spell [Throws] correctly")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ [SameObject] readonly attribute boolean foo;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw, "Should not allow [SameObject] on attributes not of interface type"
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ [SameObject] readonly attribute A foo;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(not threw, "Should allow [SameObject] on attributes of interface type")
diff --git a/dom/bindings/parser/tests/test_attr_sequence_type.py b/dom/bindings/parser/tests/test_attr_sequence_type.py
new file mode 100644
index 0000000000..afbc732974
--- /dev/null
+++ b/dom/bindings/parser/tests/test_attr_sequence_type.py
@@ -0,0 +1,77 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface AttrSequenceType {
+ attribute sequence<object> foo;
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Attribute type must not be a sequence type")
+
+ parser.reset()
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface AttrUnionWithSequenceType {
+ attribute (sequence<object> or DOMString) foo;
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Attribute type must not be a union with a sequence member type")
+
+ parser.reset()
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface AttrNullableUnionWithSequenceType {
+ attribute (sequence<object>? or DOMString) foo;
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Attribute type must not be a union with a nullable sequence " "member type",
+ )
+
+ parser.reset()
+
+ threw = False
+ try:
+ parser.parse(
+ "\n"
+ " interface AttrUnionWithUnionWithSequenceType {\n"
+ " attribute ((sequence<object> or DOMString) or "
+ "AttrUnionWithUnionWithSequenceType) foo;\n"
+ " };\n"
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Attribute type must not be a union type with a union member "
+ "type that has a sequence member type",
+ )
diff --git a/dom/bindings/parser/tests/test_attributes_on_types.py b/dom/bindings/parser/tests/test_attributes_on_types.py
new file mode 100644
index 0000000000..d0a0c7a72c
--- /dev/null
+++ b/dom/bindings/parser/tests/test_attributes_on_types.py
@@ -0,0 +1,567 @@
+def WebIDLTest(parser, harness):
+ # Basic functionality
+ threw = False
+ try:
+ parser.parse(
+ """
+ typedef [EnforceRange] long Foo;
+ typedef [Clamp] long Bar;
+ typedef [LegacyNullToEmptyString] DOMString Baz;
+ dictionary A {
+ required [EnforceRange] long a;
+ required [Clamp] long b;
+ [ChromeOnly, EnforceRange] long c;
+ Foo d;
+ };
+ interface B {
+ attribute Foo typedefFoo;
+ attribute [EnforceRange] long foo;
+ attribute [Clamp] long bar;
+ attribute [LegacyNullToEmptyString] DOMString baz;
+ undefined method([EnforceRange] long foo, [Clamp] long bar,
+ [LegacyNullToEmptyString] DOMString baz);
+ undefined method2(optional [EnforceRange] long foo, optional [Clamp] long bar,
+ optional [LegacyNullToEmptyString] DOMString baz);
+ undefined method3(optional [LegacyNullToEmptyString] UTF8String foo = "");
+ };
+ interface C {
+ attribute [EnforceRange] long? foo;
+ attribute [Clamp] long? bar;
+ undefined method([EnforceRange] long? foo, [Clamp] long? bar);
+ undefined method2(optional [EnforceRange] long? foo, optional [Clamp] long? bar);
+ };
+ interface Setlike {
+ setlike<[Clamp] long>;
+ };
+ interface Maplike {
+ maplike<[Clamp] long, [EnforceRange] long>;
+ };
+ interface Iterable {
+ iterable<[Clamp] long, [EnforceRange] long>;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(not threw, "Should not have thrown on parsing normal")
+ if not threw:
+ harness.check(
+ results[0].innerType.hasEnforceRange(), True, "Foo is [EnforceRange]"
+ )
+ harness.check(results[1].innerType.hasClamp(), True, "Bar is [Clamp]")
+ harness.check(
+ results[2].innerType.legacyNullToEmptyString,
+ True,
+ "Baz is [LegacyNullToEmptyString]",
+ )
+ A = results[3]
+ harness.check(
+ A.members[0].type.hasEnforceRange(), True, "A.a is [EnforceRange]"
+ )
+ harness.check(A.members[1].type.hasClamp(), True, "A.b is [Clamp]")
+ harness.check(
+ A.members[2].type.hasEnforceRange(), True, "A.c is [EnforceRange]"
+ )
+ harness.check(
+ A.members[3].type.hasEnforceRange(), True, "A.d is [EnforceRange]"
+ )
+ B = results[4]
+ harness.check(
+ B.members[0].type.hasEnforceRange(), True, "B.typedefFoo is [EnforceRange]"
+ )
+ harness.check(
+ B.members[1].type.hasEnforceRange(), True, "B.foo is [EnforceRange]"
+ )
+ harness.check(B.members[2].type.hasClamp(), True, "B.bar is [Clamp]")
+ harness.check(
+ B.members[3].type.legacyNullToEmptyString,
+ True,
+ "B.baz is [LegacyNullToEmptyString]",
+ )
+ method = B.members[4].signatures()[0][1]
+ harness.check(
+ method[0].type.hasEnforceRange(),
+ True,
+ "foo argument of method is [EnforceRange]",
+ )
+ harness.check(
+ method[1].type.hasClamp(), True, "bar argument of method is [Clamp]"
+ )
+ harness.check(
+ method[2].type.legacyNullToEmptyString,
+ True,
+ "baz argument of method is [LegacyNullToEmptyString]",
+ )
+ method2 = B.members[5].signatures()[0][1]
+ harness.check(
+ method2[0].type.hasEnforceRange(),
+ True,
+ "foo argument of method2 is [EnforceRange]",
+ )
+ harness.check(
+ method2[1].type.hasClamp(), True, "bar argument of method2 is [Clamp]"
+ )
+ harness.check(
+ method2[2].type.legacyNullToEmptyString,
+ True,
+ "baz argument of method2 is [LegacyNullToEmptyString]",
+ )
+
+ method3 = B.members[6].signatures()[0][1]
+ harness.check(
+ method3[0].type.legacyNullToEmptyString,
+ True,
+ "bar argument of method2 is [LegacyNullToEmptyString]",
+ )
+ harness.check(
+ method3[0].defaultValue.type.isUTF8String(),
+ True,
+ "default value of bar argument of method2 is correctly coerced to UTF8String",
+ )
+
+ C = results[5]
+ harness.ok(C.members[0].type.nullable(), "C.foo is nullable")
+ harness.ok(C.members[0].type.hasEnforceRange(), "C.foo has [EnforceRange]")
+ harness.ok(C.members[1].type.nullable(), "C.bar is nullable")
+ harness.ok(C.members[1].type.hasClamp(), "C.bar has [Clamp]")
+ method = C.members[2].signatures()[0][1]
+ harness.ok(method[0].type.nullable(), "foo argument of method is nullable")
+ harness.ok(
+ method[0].type.hasEnforceRange(),
+ "foo argument of method has [EnforceRange]",
+ )
+ harness.ok(method[1].type.nullable(), "bar argument of method is nullable")
+ harness.ok(method[1].type.hasClamp(), "bar argument of method has [Clamp]")
+ method2 = C.members[3].signatures()[0][1]
+ harness.ok(method2[0].type.nullable(), "foo argument of method2 is nullable")
+ harness.ok(
+ method2[0].type.hasEnforceRange(),
+ "foo argument of method2 has [EnforceRange]",
+ )
+ harness.ok(method2[1].type.nullable(), "bar argument of method2 is nullable")
+ harness.ok(method2[1].type.hasClamp(), "bar argument of method2 has [Clamp]")
+
+ # Test [AllowShared]
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ typedef [AllowShared] ArrayBufferView Foo;
+ dictionary A {
+ required [AllowShared] ArrayBufferView a;
+ [ChromeOnly, AllowShared] ArrayBufferView b;
+ Foo c;
+ };
+ interface B {
+ attribute Foo typedefFoo;
+ attribute [AllowShared] ArrayBufferView foo;
+ undefined method([AllowShared] ArrayBufferView foo);
+ undefined method2(optional [AllowShared] ArrayBufferView foo);
+ };
+ interface C {
+ attribute [AllowShared] ArrayBufferView? foo;
+ undefined method([AllowShared] ArrayBufferView? foo);
+ undefined method2(optional [AllowShared] ArrayBufferView? foo);
+ };
+ interface Setlike {
+ setlike<[AllowShared] ArrayBufferView>;
+ };
+ interface Maplike {
+ maplike<[Clamp] long, [AllowShared] ArrayBufferView>;
+ };
+ interface Iterable {
+ iterable<[Clamp] long, [AllowShared] ArrayBufferView>;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(not threw, "Should not have thrown on parsing normal")
+ if not threw:
+ harness.ok(results[0].innerType.hasAllowShared(), "Foo is [AllowShared]")
+ A = results[1]
+ harness.ok(A.members[0].type.hasAllowShared(), "A.a is [AllowShared]")
+ harness.ok(A.members[1].type.hasAllowShared(), "A.b is [AllowShared]")
+ harness.ok(A.members[2].type.hasAllowShared(), "A.c is [AllowShared]")
+ B = results[2]
+ harness.ok(B.members[0].type.hasAllowShared(), "B.typedefFoo is [AllowShared]")
+ harness.ok(B.members[1].type.hasAllowShared(), "B.foo is [AllowShared]")
+ method = B.members[2].signatures()[0][1]
+ harness.ok(
+ method[0].type.hasAllowShared(), "foo argument of method is [AllowShared]"
+ )
+ method2 = B.members[3].signatures()[0][1]
+ harness.ok(
+ method2[0].type.hasAllowShared(), "foo argument of method2 is [AllowShared]"
+ )
+ C = results[3]
+ harness.ok(C.members[0].type.nullable(), "C.foo is nullable")
+ harness.ok(C.members[0].type.hasAllowShared(), "C.foo is [AllowShared]")
+ method = C.members[1].signatures()[0][1]
+ harness.ok(method[0].type.nullable(), "foo argument of method is nullable")
+ harness.ok(
+ method[0].type.hasAllowShared(), "foo argument of method is [AllowShared]"
+ )
+ method2 = C.members[2].signatures()[0][1]
+ harness.ok(method2[0].type.nullable(), "foo argument of method2 is nullable")
+ harness.ok(
+ method2[0].type.hasAllowShared(), "foo argument of method2 is [AllowShared]"
+ )
+
+ ATTRIBUTES = [
+ ("[Clamp]", "long"),
+ ("[EnforceRange]", "long"),
+ ("[LegacyNullToEmptyString]", "DOMString"),
+ ("[AllowShared]", "ArrayBufferView"),
+ ]
+ TEMPLATES = [
+ (
+ "required dictionary members",
+ """
+ dictionary Foo {
+ %s required %s foo;
+ };
+ """,
+ ),
+ (
+ "optional arguments",
+ """
+ interface Foo {
+ undefined foo(%s optional %s foo);
+ };
+ """,
+ ),
+ (
+ "typedefs",
+ """
+ %s typedef %s foo;
+ """,
+ ),
+ (
+ "attributes",
+ """
+ interface Foo {
+ %s attribute %s foo;
+ };
+ """,
+ ),
+ (
+ "readonly attributes",
+ """
+ interface Foo {
+ readonly attribute %s %s foo;
+ };
+ """,
+ ),
+ (
+ "readonly unresolved attributes",
+ """
+ interface Foo {
+ readonly attribute Bar baz;
+ };
+ typedef %s %s Bar;
+ """,
+ ),
+ (
+ "method",
+ """
+ interface Foo {
+ %s %s foo();
+ };
+ """,
+ ),
+ (
+ "interface",
+ """
+ %s
+ interface Foo {
+ attribute %s foo;
+ };
+ """,
+ ),
+ (
+ "partial interface",
+ """
+ interface Foo {
+ undefined foo();
+ };
+ %s
+ partial interface Foo {
+ attribute %s bar;
+ };
+ """,
+ ),
+ (
+ "interface mixin",
+ """
+ %s
+ interface mixin Foo {
+ attribute %s foo;
+ };
+ """,
+ ),
+ (
+ "namespace",
+ """
+ %s
+ namespace Foo {
+ attribute %s foo;
+ };
+ """,
+ ),
+ (
+ "partial namespace",
+ """
+ namespace Foo {
+ undefined foo();
+ };
+ %s
+ partial namespace Foo {
+ attribute %s bar;
+ };
+ """,
+ ),
+ (
+ "dictionary",
+ """
+ %s
+ dictionary Foo {
+ %s foo;
+ };
+ """,
+ ),
+ ]
+
+ for (name, template) in TEMPLATES:
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(template % ("", "long"))
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(not threw, "Template for %s parses without attributes" % name)
+ for (attribute, type) in ATTRIBUTES:
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(template % (attribute, type))
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow %s on %s" % (attribute, name))
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ typedef [Clamp, EnforceRange] long Foo;
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should not allow mixing [Clamp] and [EnforceRange]")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ typedef [EnforceRange, Clamp] long Foo;
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should not allow mixing [Clamp] and [EnforceRange]")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ typedef [Clamp] long Foo;
+ typedef [EnforceRange] Foo bar;
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should not allow mixing [Clamp] and [EnforceRange] via typedefs")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ typedef [EnforceRange] long Foo;
+ typedef [Clamp] Foo bar;
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should not allow mixing [Clamp] and [EnforceRange] via typedefs")
+
+ TYPES = [
+ "DOMString",
+ "unrestricted float",
+ "float",
+ "unrestricted double",
+ "double",
+ ]
+
+ for type in TYPES:
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ typedef [Clamp] %s Foo;
+ """
+ % type
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should not allow [Clamp] on %s" % type)
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ typedef [EnforceRange] %s Foo;
+ """
+ % type
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should not allow [EnforceRange] on %s" % type)
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ typedef [LegacyNullToEmptyString] long Foo;
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should not allow [LegacyNullToEmptyString] on long")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ typedef [LegacyNullToEmptyString] JSString Foo;
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should not allow [LegacyNullToEmptyString] on JSString")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ typedef [LegacyNullToEmptyString] DOMString? Foo;
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw, "Should not allow [LegacyNullToEmptyString] on nullable DOMString"
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ typedef [AllowShared] DOMString Foo;
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "[AllowShared] only allowed on buffer source types")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ typedef [AllowShared=something] ArrayBufferView Foo;
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "[AllowShared] must take no arguments")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Foo {
+ undefined foo([Clamp] Bar arg);
+ };
+ typedef long Bar;
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(not threw, "Should allow type attributes on unresolved types")
+ harness.check(
+ results[0].members[0].signatures()[0][1][0].type.hasClamp(),
+ True,
+ "Unresolved types with type attributes should correctly resolve with attributes",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Foo {
+ undefined foo(Bar arg);
+ };
+ typedef [Clamp] long Bar;
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(not threw, "Should allow type attributes on typedefs")
+ harness.check(
+ results[0].members[0].signatures()[0][1][0].type.hasClamp(),
+ True,
+ "Unresolved types that resolve to typedefs with attributes should correctly resolve with "
+ "attributes",
+ )
diff --git a/dom/bindings/parser/tests/test_builtin_filename.py b/dom/bindings/parser/tests/test_builtin_filename.py
new file mode 100644
index 0000000000..25ed32befc
--- /dev/null
+++ b/dom/bindings/parser/tests/test_builtin_filename.py
@@ -0,0 +1,11 @@
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ interface Test {
+ attribute long b;
+ };
+ """
+ )
+
+ attr = parser.finish()[0].members[0]
+ harness.check(attr.type.filename(), "<builtin>", "Filename on builtin type")
diff --git a/dom/bindings/parser/tests/test_builtins.py b/dom/bindings/parser/tests/test_builtins.py
new file mode 100644
index 0000000000..a75a12e814
--- /dev/null
+++ b/dom/bindings/parser/tests/test_builtins.py
@@ -0,0 +1,59 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ interface TestBuiltins {
+ attribute boolean b;
+ attribute byte s8;
+ attribute octet u8;
+ attribute short s16;
+ attribute unsigned short u16;
+ attribute long s32;
+ attribute unsigned long u32;
+ attribute long long s64;
+ attribute unsigned long long u64;
+ };
+ """
+ )
+
+ results = parser.finish()
+
+ harness.ok(True, "TestBuiltins interface parsed without error.")
+ harness.check(len(results), 1, "Should be one production")
+ harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface")
+ iface = results[0]
+ harness.check(
+ iface.identifier.QName(), "::TestBuiltins", "Interface has the right QName"
+ )
+ harness.check(iface.identifier.name, "TestBuiltins", "Interface has the right name")
+ harness.check(iface.parent, None, "Interface has no parent")
+
+ members = iface.members
+ harness.check(len(members), 9, "Should be one production")
+
+ names = ["b", "s8", "u8", "s16", "u16", "s32", "u32", "s64", "u64", "ts"]
+ types = [
+ "Boolean",
+ "Byte",
+ "Octet",
+ "Short",
+ "UnsignedShort",
+ "Long",
+ "UnsignedLong",
+ "LongLong",
+ "UnsignedLongLong",
+ "UnsignedLongLong",
+ ]
+ for i in range(9):
+ attr = members[i]
+ harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Should be an IDLAttribute")
+ harness.check(
+ attr.identifier.QName(),
+ "::TestBuiltins::" + names[i],
+ "Attr has correct QName",
+ )
+ harness.check(attr.identifier.name, names[i], "Attr has correct name")
+ harness.check(str(attr.type), types[i], "Attr type is the correct name")
+ harness.ok(attr.type.isPrimitive(), "Should be a primitive type")
diff --git a/dom/bindings/parser/tests/test_bytestring.py b/dom/bindings/parser/tests/test_bytestring.py
new file mode 100644
index 0000000000..248d2716f3
--- /dev/null
+++ b/dom/bindings/parser/tests/test_bytestring.py
@@ -0,0 +1,125 @@
+# -*- coding: UTF-8 -*-
+
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ interface TestByteString {
+ attribute ByteString bs;
+ attribute DOMString ds;
+ };
+ """
+ )
+
+ results = parser.finish()
+
+ harness.ok(True, "TestByteString interface parsed without error.")
+
+ harness.check(len(results), 1, "Should be one production")
+ harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface")
+ iface = results[0]
+ harness.check(
+ iface.identifier.QName(), "::TestByteString", "Interface has the right QName"
+ )
+ harness.check(
+ iface.identifier.name, "TestByteString", "Interface has the right name"
+ )
+ harness.check(iface.parent, None, "Interface has no parent")
+
+ members = iface.members
+ harness.check(len(members), 2, "Should be two productions")
+
+ attr = members[0]
+ harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Should be an IDLAttribute")
+ harness.check(
+ attr.identifier.QName(), "::TestByteString::bs", "Attr has correct QName"
+ )
+ harness.check(attr.identifier.name, "bs", "Attr has correct name")
+ harness.check(str(attr.type), "ByteString", "Attr type is the correct name")
+ harness.ok(attr.type.isByteString(), "Should be ByteString type")
+ harness.ok(attr.type.isString(), "Should be String collective type")
+ harness.ok(not attr.type.isDOMString(), "Should be not be DOMString type")
+
+ # now check we haven't broken DOMStrings in the process.
+ attr = members[1]
+ harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Should be an IDLAttribute")
+ harness.check(
+ attr.identifier.QName(), "::TestByteString::ds", "Attr has correct QName"
+ )
+ harness.check(attr.identifier.name, "ds", "Attr has correct name")
+ harness.check(str(attr.type), "String", "Attr type is the correct name")
+ harness.ok(attr.type.isDOMString(), "Should be DOMString type")
+ harness.ok(attr.type.isString(), "Should be String collective type")
+ harness.ok(not attr.type.isByteString(), "Should be not be ByteString type")
+
+ # Cannot represent constant ByteString in IDL.
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface ConstByteString {
+ const ByteString foo = "hello"
+ };
+ """
+ )
+ except WebIDL.WebIDLError:
+ threw = True
+ harness.ok(
+ threw, "Should have thrown a WebIDL error for ByteString default in interface"
+ )
+
+ # Can have optional ByteStrings with default values
+ try:
+ parser.parse(
+ """
+ interface OptionalByteString {
+ undefined passByteString(optional ByteString arg = "hello");
+ };
+ """
+ )
+ parser.finish()
+ except WebIDL.WebIDLError as e:
+ harness.ok(
+ False,
+ "Should not have thrown a WebIDL error for ByteString "
+ "default in dictionary. " + str(e),
+ )
+
+ # Can have a default ByteString value in a dictionary
+ try:
+ parser.parse(
+ """
+ dictionary OptionalByteStringDict {
+ ByteString item = "some string";
+ };
+ """
+ )
+ parser.finish()
+ except WebIDL.WebIDLError as e:
+ harness.ok(
+ False,
+ "Should not have thrown a WebIDL error for ByteString "
+ "default in dictionary. " + str(e),
+ )
+
+ # Don't allow control characters in ByteString literals
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary OptionalByteStringDict2 {
+ ByteString item = "\x03";
+ };
+ """
+ )
+ parser.finish()
+ except WebIDL.WebIDLError:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Should have thrown a WebIDL error for invalid ByteString "
+ "default in dictionary",
+ )
diff --git a/dom/bindings/parser/tests/test_callback.py b/dom/bindings/parser/tests/test_callback.py
new file mode 100644
index 0000000000..407644a6a8
--- /dev/null
+++ b/dom/bindings/parser/tests/test_callback.py
@@ -0,0 +1,42 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ interface TestCallback {
+ attribute CallbackType? listener;
+ };
+
+ callback CallbackType = boolean (unsigned long arg);
+ """
+ )
+
+ results = parser.finish()
+
+ harness.ok(True, "TestCallback interface parsed without error.")
+ harness.check(len(results), 2, "Should be two productions.")
+ iface = results[0]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should be an IDLInterface")
+ harness.check(
+ iface.identifier.QName(), "::TestCallback", "Interface has the right QName"
+ )
+ harness.check(iface.identifier.name, "TestCallback", "Interface has the right name")
+ harness.check(len(iface.members), 1, "Expect %s members" % 1)
+
+ attr = iface.members[0]
+ harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Should be an IDLAttribute")
+ harness.ok(attr.isAttr(), "Should be an attribute")
+ harness.ok(not attr.isMethod(), "Attr is not an method")
+ harness.ok(not attr.isConst(), "Attr is not a const")
+ harness.check(
+ attr.identifier.QName(), "::TestCallback::listener", "Attr has the right QName"
+ )
+ harness.check(attr.identifier.name, "listener", "Attr has the right name")
+ t = attr.type
+ harness.ok(not isinstance(t, WebIDL.IDLWrapperType), "Attr has the right type")
+ harness.ok(isinstance(t, WebIDL.IDLNullableType), "Attr has the right type")
+ harness.ok(t.isCallback(), "Attr has the right type")
+
+ callback = results[1]
+ harness.ok(not callback.isConstructor(), "callback is not constructor")
diff --git a/dom/bindings/parser/tests/test_callback_constructor.py b/dom/bindings/parser/tests/test_callback_constructor.py
new file mode 100644
index 0000000000..ea2f46c436
--- /dev/null
+++ b/dom/bindings/parser/tests/test_callback_constructor.py
@@ -0,0 +1,84 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ interface TestCallbackConstructor {
+ attribute CallbackConstructorType? constructorAttribute;
+ };
+
+ callback constructor CallbackConstructorType = TestCallbackConstructor (unsigned long arg);
+ """
+ )
+
+ results = parser.finish()
+
+ harness.ok(True, "TestCallbackConstructor interface parsed without error.")
+ harness.check(len(results), 2, "Should be two productions.")
+ iface = results[0]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should be an IDLInterface")
+ harness.check(
+ iface.identifier.QName(),
+ "::TestCallbackConstructor",
+ "Interface has the right QName",
+ )
+ harness.check(
+ iface.identifier.name, "TestCallbackConstructor", "Interface has the right name"
+ )
+ harness.check(len(iface.members), 1, "Expect %s members" % 1)
+
+ attr = iface.members[0]
+ harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Should be an IDLAttribute")
+ harness.ok(attr.isAttr(), "Should be an attribute")
+ harness.ok(not attr.isMethod(), "Attr is not an method")
+ harness.ok(not attr.isConst(), "Attr is not a const")
+ harness.check(
+ attr.identifier.QName(),
+ "::TestCallbackConstructor::constructorAttribute",
+ "Attr has the right QName",
+ )
+ harness.check(
+ attr.identifier.name, "constructorAttribute", "Attr has the right name"
+ )
+ t = attr.type
+ harness.ok(not isinstance(t, WebIDL.IDLWrapperType), "Attr has the right type")
+ harness.ok(isinstance(t, WebIDL.IDLNullableType), "Attr has the right type")
+ harness.ok(t.isCallback(), "Attr has the right type")
+
+ callback = results[1]
+ harness.ok(callback.isConstructor(), "Callback is constructor")
+
+ parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [LegacyTreatNonObjectAsNull]
+ callback constructor CallbackConstructorType = object ();
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw, "Should throw on LegacyTreatNonObjectAsNull callback constructors"
+ )
+
+ parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [MOZ_CAN_RUN_SCRIPT_BOUNDARY]
+ callback constructor CallbackConstructorType = object ();
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw, "Should not permit MOZ_CAN_RUN_SCRIPT_BOUNDARY callback constructors"
+ )
diff --git a/dom/bindings/parser/tests/test_callback_interface.py b/dom/bindings/parser/tests/test_callback_interface.py
new file mode 100644
index 0000000000..1396eb8a24
--- /dev/null
+++ b/dom/bindings/parser/tests/test_callback_interface.py
@@ -0,0 +1,103 @@
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ callback interface TestCallbackInterface {
+ attribute boolean bool;
+ };
+ """
+ )
+
+ results = parser.finish()
+
+ iface = results[0]
+
+ harness.ok(iface.isCallback(), "Interface should be a callback")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestInterface {
+ };
+ callback interface TestCallbackInterface : TestInterface {
+ attribute boolean bool;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should not allow non-callback parent of callback interface")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestInterface : TestCallbackInterface {
+ };
+ callback interface TestCallbackInterface {
+ attribute boolean bool;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should not allow callback parent of non-callback interface")
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ callback interface TestCallbackInterface1 {
+ undefined foo();
+ };
+ callback interface TestCallbackInterface2 {
+ undefined foo(DOMString arg);
+ undefined foo(TestCallbackInterface1 arg);
+ };
+ callback interface TestCallbackInterface3 {
+ undefined foo(DOMString arg);
+ undefined foo(TestCallbackInterface1 arg);
+ static undefined bar();
+ };
+ callback interface TestCallbackInterface4 {
+ undefined foo(DOMString arg);
+ undefined foo(TestCallbackInterface1 arg);
+ static undefined bar();
+ const long baz = 5;
+ };
+ callback interface TestCallbackInterface5 {
+ static attribute boolean bool;
+ undefined foo();
+ };
+ callback interface TestCallbackInterface6 {
+ undefined foo(DOMString arg);
+ undefined foo(TestCallbackInterface1 arg);
+ undefined bar();
+ };
+ callback interface TestCallbackInterface7 {
+ static attribute boolean bool;
+ };
+ callback interface TestCallbackInterface8 {
+ attribute boolean bool;
+ };
+ callback interface TestCallbackInterface9 : TestCallbackInterface1 {
+ undefined foo();
+ };
+ callback interface TestCallbackInterface10 : TestCallbackInterface1 {
+ undefined bar();
+ };
+ """
+ )
+ results = parser.finish()
+ for (i, iface) in enumerate(results):
+ harness.check(
+ iface.isSingleOperationInterface(),
+ i < 4,
+ "Interface %s should be a single operation interface"
+ % iface.identifier.name,
+ )
diff --git a/dom/bindings/parser/tests/test_cereactions.py b/dom/bindings/parser/tests/test_cereactions.py
new file mode 100644
index 0000000000..376a32b4c4
--- /dev/null
+++ b/dom/bindings/parser/tests/test_cereactions.py
@@ -0,0 +1,157 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Foo {
+ [CEReactions(DOMString a)] undefined foo(boolean arg2);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown for [CEReactions] with an argument")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Foo {
+ [CEReactions(DOMString b)] readonly attribute boolean bar;
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown for [CEReactions] with an argument")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Foo {
+ [CEReactions] attribute boolean bar;
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception as e:
+ harness.ok(
+ False,
+ "Shouldn't have thrown for [CEReactions] used on writable attribute. %s"
+ % e,
+ )
+ threw = True
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Foo {
+ [CEReactions] undefined foo(boolean arg2);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception as e:
+ harness.ok(
+ False,
+ "Shouldn't have thrown for [CEReactions] used on regular operations. %s"
+ % e,
+ )
+ threw = True
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Foo {
+ [CEReactions] readonly attribute boolean A;
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw, "Should have thrown for [CEReactions] used on a readonly attribute"
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [CEReactions]
+ interface Foo {
+ }
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown for [CEReactions] used on a interface")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Foo {
+ [CEReactions] getter any(DOMString name);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown for [CEReactions] used on a named getter")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Foo {
+ [CEReactions] legacycaller double compute(double x);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown for [CEReactions] used on a legacycaller")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Foo {
+ [CEReactions] stringifier DOMString ();
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown for [CEReactions] used on a stringifier")
diff --git a/dom/bindings/parser/tests/test_conditional_dictionary_member.py b/dom/bindings/parser/tests/test_conditional_dictionary_member.py
new file mode 100644
index 0000000000..2aef8ebe8f
--- /dev/null
+++ b/dom/bindings/parser/tests/test_conditional_dictionary_member.py
@@ -0,0 +1,128 @@
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ dictionary Dict {
+ any foo;
+ [ChromeOnly] any bar;
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(len(results), 1, "Should have a dictionary")
+ members = results[0].members
+ harness.check(len(members), 2, "Should have two members")
+ # Note that members are ordered lexicographically, so "bar" comes
+ # before "foo".
+ harness.ok(
+ members[0].getExtendedAttribute("ChromeOnly"), "First member is not ChromeOnly"
+ )
+ harness.ok(
+ not members[1].getExtendedAttribute("ChromeOnly"), "Second member is ChromeOnly"
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ dictionary Dict {
+ any foo;
+ any bar;
+ };
+
+ interface Iface {
+ [Constant, Cached] readonly attribute Dict dict;
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(len(results), 2, "Should have a dictionary and an interface")
+
+ parser = parser.reset()
+ exception = None
+ try:
+ parser.parse(
+ """
+ dictionary Dict {
+ any foo;
+ [ChromeOnly] any bar;
+ };
+
+ interface Iface {
+ [Constant, Cached] readonly attribute Dict dict;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception as e:
+ exception = e
+
+ harness.ok(exception, "Should have thrown.")
+ harness.check(
+ exception.message,
+ "[Cached] and [StoreInSlot] must not be used on an attribute "
+ "whose type contains a [ChromeOnly] dictionary member",
+ "Should have thrown the right exception",
+ )
+
+ parser = parser.reset()
+ exception = None
+ try:
+ parser.parse(
+ """
+ dictionary ParentDict {
+ [ChromeOnly] any bar;
+ };
+
+ dictionary Dict : ParentDict {
+ any foo;
+ };
+
+ interface Iface {
+ [Constant, Cached] readonly attribute Dict dict;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception as e:
+ exception = e
+
+ harness.ok(exception, "Should have thrown (2).")
+ harness.check(
+ exception.message,
+ "[Cached] and [StoreInSlot] must not be used on an attribute "
+ "whose type contains a [ChromeOnly] dictionary member",
+ "Should have thrown the right exception (2)",
+ )
+
+ parser = parser.reset()
+ exception = None
+ try:
+ parser.parse(
+ """
+ dictionary GrandParentDict {
+ [ChromeOnly] any baz;
+ };
+
+ dictionary ParentDict : GrandParentDict {
+ any bar;
+ };
+
+ dictionary Dict : ParentDict {
+ any foo;
+ };
+
+ interface Iface {
+ [Constant, Cached] readonly attribute Dict dict;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception as e:
+ exception = e
+
+ harness.ok(exception, "Should have thrown (3).")
+ harness.check(
+ exception.message,
+ "[Cached] and [StoreInSlot] must not be used on an attribute "
+ "whose type contains a [ChromeOnly] dictionary member",
+ "Should have thrown the right exception (3)",
+ )
diff --git a/dom/bindings/parser/tests/test_const.py b/dom/bindings/parser/tests/test_const.py
new file mode 100644
index 0000000000..ea597d1ed2
--- /dev/null
+++ b/dom/bindings/parser/tests/test_const.py
@@ -0,0 +1,96 @@
+import WebIDL
+
+expected = [
+ ("::TestConsts::zero", "zero", "Byte", 0),
+ ("::TestConsts::b", "b", "Byte", -1),
+ ("::TestConsts::o", "o", "Octet", 2),
+ ("::TestConsts::s", "s", "Short", -3),
+ ("::TestConsts::us", "us", "UnsignedShort", 4),
+ ("::TestConsts::l", "l", "Long", -5),
+ ("::TestConsts::ul", "ul", "UnsignedLong", 6),
+ ("::TestConsts::ull", "ull", "UnsignedLongLong", 7),
+ ("::TestConsts::ll", "ll", "LongLong", -8),
+ ("::TestConsts::t", "t", "Boolean", True),
+ ("::TestConsts::f", "f", "Boolean", False),
+ ("::TestConsts::fl", "fl", "Float", 0.2),
+ ("::TestConsts::db", "db", "Double", 0.2),
+ ("::TestConsts::ufl", "ufl", "UnrestrictedFloat", 0.2),
+ ("::TestConsts::udb", "udb", "UnrestrictedDouble", 0.2),
+ ("::TestConsts::fli", "fli", "Float", 2),
+ ("::TestConsts::dbi", "dbi", "Double", 2),
+ ("::TestConsts::ufli", "ufli", "UnrestrictedFloat", 2),
+ ("::TestConsts::udbi", "udbi", "UnrestrictedDouble", 2),
+]
+
+
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ interface TestConsts {
+ const byte zero = 0;
+ const byte b = -1;
+ const octet o = 2;
+ const short s = -3;
+ const unsigned short us = 0x4;
+ const long l = -0X5;
+ const unsigned long ul = 6;
+ const unsigned long long ull = 7;
+ const long long ll = -010;
+ const boolean t = true;
+ const boolean f = false;
+ const float fl = 0.2;
+ const double db = 0.2;
+ const unrestricted float ufl = 0.2;
+ const unrestricted double udb = 0.2;
+ const float fli = 2;
+ const double dbi = 2;
+ const unrestricted float ufli = 2;
+ const unrestricted double udbi = 2;
+ };
+ """
+ )
+
+ results = parser.finish()
+
+ harness.ok(True, "TestConsts interface parsed without error.")
+ harness.check(len(results), 1, "Should be one production.")
+ iface = results[0]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should be an IDLInterface")
+ harness.check(
+ iface.identifier.QName(), "::TestConsts", "Interface has the right QName"
+ )
+ harness.check(iface.identifier.name, "TestConsts", "Interface has the right name")
+ harness.check(
+ len(iface.members), len(expected), "Expect %s members" % len(expected)
+ )
+
+ for (const, (QName, name, type, value)) in zip(iface.members, expected):
+ harness.ok(isinstance(const, WebIDL.IDLConst), "Should be an IDLConst")
+ harness.ok(const.isConst(), "Const is a const")
+ harness.ok(not const.isAttr(), "Const is not an attr")
+ harness.ok(not const.isMethod(), "Const is not a method")
+ harness.check(const.identifier.QName(), QName, "Const has the right QName")
+ harness.check(const.identifier.name, name, "Const has the right name")
+ harness.check(str(const.type), type, "Const has the right type")
+ harness.ok(const.type.isPrimitive(), "All consts should be primitive")
+ harness.check(
+ str(const.value.type),
+ str(const.type),
+ "Const's value has the same type as the type",
+ )
+ harness.check(const.value.value, value, "Const value has the right value.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestConsts {
+ const boolean? zero = 0;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Nullable types are not allowed for consts.")
diff --git a/dom/bindings/parser/tests/test_constructor.py b/dom/bindings/parser/tests/test_constructor.py
new file mode 100644
index 0000000000..1e1382336c
--- /dev/null
+++ b/dom/bindings/parser/tests/test_constructor.py
@@ -0,0 +1,596 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ def checkArgument(argument, QName, name, type, optional, variadic):
+ harness.ok(isinstance(argument, WebIDL.IDLArgument), "Should be an IDLArgument")
+ harness.check(
+ argument.identifier.QName(), QName, "Argument has the right QName"
+ )
+ harness.check(argument.identifier.name, name, "Argument has the right name")
+ harness.check(str(argument.type), type, "Argument has the right return type")
+ harness.check(
+ argument.optional, optional, "Argument has the right optional value"
+ )
+ harness.check(
+ argument.variadic, variadic, "Argument has the right variadic value"
+ )
+
+ def checkMethod(
+ method,
+ QName,
+ name,
+ signatures,
+ static=True,
+ getter=False,
+ setter=False,
+ deleter=False,
+ legacycaller=False,
+ stringifier=False,
+ chromeOnly=False,
+ htmlConstructor=False,
+ secureContext=False,
+ pref=None,
+ func=None,
+ ):
+ harness.ok(isinstance(method, WebIDL.IDLMethod), "Should be an IDLMethod")
+ harness.ok(method.isMethod(), "Method is a method")
+ harness.ok(not method.isAttr(), "Method is not an attr")
+ harness.ok(not method.isConst(), "Method is not a const")
+ harness.check(method.identifier.QName(), QName, "Method has the right QName")
+ harness.check(method.identifier.name, name, "Method has the right name")
+ harness.check(method.isStatic(), static, "Method has the correct static value")
+ harness.check(method.isGetter(), getter, "Method has the correct getter value")
+ harness.check(method.isSetter(), setter, "Method has the correct setter value")
+ harness.check(
+ method.isDeleter(), deleter, "Method has the correct deleter value"
+ )
+ harness.check(
+ method.isLegacycaller(),
+ legacycaller,
+ "Method has the correct legacycaller value",
+ )
+ harness.check(
+ method.isStringifier(),
+ stringifier,
+ "Method has the correct stringifier value",
+ )
+ harness.check(
+ method.getExtendedAttribute("ChromeOnly") is not None,
+ chromeOnly,
+ "Method has the correct value for ChromeOnly",
+ )
+ harness.check(
+ method.isHTMLConstructor(),
+ htmlConstructor,
+ "Method has the correct htmlConstructor value",
+ )
+ harness.check(
+ len(method.signatures()),
+ len(signatures),
+ "Method has the correct number of signatures",
+ )
+ harness.check(
+ method.getExtendedAttribute("Pref"),
+ pref,
+ "Method has the correct pref value",
+ )
+ harness.check(
+ method.getExtendedAttribute("Func"),
+ func,
+ "Method has the correct func value",
+ )
+ harness.check(
+ method.getExtendedAttribute("SecureContext") is not None,
+ secureContext,
+ "Method has the correct SecureContext value",
+ )
+
+ sigpairs = zip(method.signatures(), signatures)
+ for (gotSignature, expectedSignature) in sigpairs:
+ (gotRetType, gotArgs) = gotSignature
+ (expectedRetType, expectedArgs) = expectedSignature
+
+ harness.check(
+ str(gotRetType), expectedRetType, "Method has the expected return type."
+ )
+
+ for i in range(0, len(gotArgs)):
+ (QName, name, type, optional, variadic) = expectedArgs[i]
+ checkArgument(gotArgs[i], QName, name, type, optional, variadic)
+
+ def checkResults(results):
+ harness.check(len(results), 3, "Should be three productions")
+ harness.ok(
+ isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface"
+ )
+ harness.ok(
+ isinstance(results[1], WebIDL.IDLInterface), "Should be an IDLInterface"
+ )
+ harness.ok(
+ isinstance(results[2], WebIDL.IDLInterface), "Should be an IDLInterface"
+ )
+
+ checkMethod(
+ results[0].ctor(),
+ "::TestConstructorNoArgs::constructor",
+ "constructor",
+ [("TestConstructorNoArgs (Wrapper)", [])],
+ )
+ harness.check(
+ len(results[0].members), 0, "TestConstructorNoArgs should not have members"
+ )
+ checkMethod(
+ results[1].ctor(),
+ "::TestConstructorWithArgs::constructor",
+ "constructor",
+ [
+ (
+ "TestConstructorWithArgs (Wrapper)",
+ [
+ (
+ "::TestConstructorWithArgs::constructor::name",
+ "name",
+ "String",
+ False,
+ False,
+ )
+ ],
+ )
+ ],
+ )
+ harness.check(
+ len(results[1].members),
+ 0,
+ "TestConstructorWithArgs should not have members",
+ )
+ checkMethod(
+ results[2].ctor(),
+ "::TestConstructorOverloads::constructor",
+ "constructor",
+ [
+ (
+ "TestConstructorOverloads (Wrapper)",
+ [
+ (
+ "::TestConstructorOverloads::constructor::foo",
+ "foo",
+ "Object",
+ False,
+ False,
+ )
+ ],
+ ),
+ (
+ "TestConstructorOverloads (Wrapper)",
+ [
+ (
+ "::TestConstructorOverloads::constructor::bar",
+ "bar",
+ "Boolean",
+ False,
+ False,
+ )
+ ],
+ ),
+ ],
+ )
+ harness.check(
+ len(results[2].members),
+ 0,
+ "TestConstructorOverloads should not have members",
+ )
+
+ parser.parse(
+ """
+ interface TestConstructorNoArgs {
+ constructor();
+ };
+
+ interface TestConstructorWithArgs {
+ constructor(DOMString name);
+ };
+
+ interface TestConstructorOverloads {
+ constructor(object foo);
+ constructor(boolean bar);
+ };
+ """
+ )
+ results = parser.finish()
+ checkResults(results)
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface TestPrefConstructor {
+ [Pref="dom.webidl.test1"] constructor();
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(len(results), 1, "Should be one production")
+ harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface")
+
+ checkMethod(
+ results[0].ctor(),
+ "::TestPrefConstructor::constructor",
+ "constructor",
+ [("TestPrefConstructor (Wrapper)", [])],
+ pref=["dom.webidl.test1"],
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface TestChromeOnlyConstructor {
+ [ChromeOnly] constructor();
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(len(results), 1, "Should be one production")
+ harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface")
+
+ checkMethod(
+ results[0].ctor(),
+ "::TestChromeOnlyConstructor::constructor",
+ "constructor",
+ [("TestChromeOnlyConstructor (Wrapper)", [])],
+ chromeOnly=True,
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface TestSCConstructor {
+ [SecureContext] constructor();
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(len(results), 1, "Should be one production")
+ harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface")
+
+ checkMethod(
+ results[0].ctor(),
+ "::TestSCConstructor::constructor",
+ "constructor",
+ [("TestSCConstructor (Wrapper)", [])],
+ secureContext=True,
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface TestFuncConstructor {
+ [Func="Document::IsWebAnimationsEnabled"] constructor();
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(len(results), 1, "Should be one production")
+ harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface")
+
+ checkMethod(
+ results[0].ctor(),
+ "::TestFuncConstructor::constructor",
+ "constructor",
+ [("TestFuncConstructor (Wrapper)", [])],
+ func=["Document::IsWebAnimationsEnabled"],
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ (
+ "\n"
+ " interface TestPrefChromeOnlySCFuncConstructor {\n"
+ ' [ChromeOnly, Pref="dom.webidl.test1", SecureContext, '
+ 'Func="Document::IsWebAnimationsEnabled"]\n'
+ " constructor();\n"
+ " };\n"
+ )
+ )
+ results = parser.finish()
+ harness.check(len(results), 1, "Should be one production")
+ harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface")
+
+ checkMethod(
+ results[0].ctor(),
+ "::TestPrefChromeOnlySCFuncConstructor::constructor",
+ "constructor",
+ [("TestPrefChromeOnlySCFuncConstructor (Wrapper)", [])],
+ func=["Document::IsWebAnimationsEnabled"],
+ pref=["dom.webidl.test1"],
+ chromeOnly=True,
+ secureContext=True,
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface TestHTMLConstructor {
+ [HTMLConstructor] constructor();
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(len(results), 1, "Should be one production")
+ harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface")
+
+ checkMethod(
+ results[0].ctor(),
+ "::TestHTMLConstructor::constructor",
+ "constructor",
+ [("TestHTMLConstructor (Wrapper)", [])],
+ htmlConstructor=True,
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestChromeOnlyConstructor {
+ constructor()
+ [ChromeOnly] constructor(DOMString a);
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Can't have both a constructor and a ChromeOnly constructor")
+
+ # Test HTMLConstructor with argument
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestHTMLConstructorWithArgs {
+ [HTMLConstructor] constructor(DOMString a);
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "HTMLConstructor should take no argument")
+
+ # Test HTMLConstructor on a callback interface
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ callback interface TestHTMLConstructorOnCallbackInterface {
+ [HTMLConstructor] constructor();
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "HTMLConstructor can't be used on a callback interface")
+
+ # Test HTMLConstructor and constructor operation
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestHTMLConstructorAndConstructor {
+ constructor();
+ [HTMLConstructor] constructor();
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Can't have both a constructor and a HTMLConstructor")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestHTMLConstructorAndConstructor {
+ [Throws]
+ constructor();
+ [HTMLConstructor] constructor();
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Can't have both a throwing constructor and a HTMLConstructor")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestHTMLConstructorAndConstructor {
+ constructor(DOMString a);
+ [HTMLConstructor] constructor();
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Can't have both a HTMLConstructor and a constructor operation")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestHTMLConstructorAndConstructor {
+ [Throws]
+ constructor(DOMString a);
+ [HTMLConstructor] constructor();
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Can't have both a HTMLConstructor and a throwing constructor " "operation",
+ )
+
+ # Test HTMLConstructor and [ChromeOnly] constructor operation
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestHTMLConstructorAndConstructor {
+ [ChromeOnly]
+ constructor();
+ [HTMLConstructor] constructor();
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Can't have both a ChromeOnly constructor and a HTMLConstructor")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestHTMLConstructorAndConstructor {
+ [Throws, ChromeOnly]
+ constructor();
+ [HTMLConstructor] constructor();
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Can't have both a throwing chromeonly constructor and a " "HTMLConstructor",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestHTMLConstructorAndConstructor {
+ [ChromeOnly]
+ constructor(DOMString a);
+ [HTMLConstructor] constructor();
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Can't have both a HTMLConstructor and a chromeonly constructor " "operation",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestHTMLConstructorAndConstructor {
+ [Throws, ChromeOnly]
+ constructor(DOMString a);
+ [HTMLConstructor] constructor();
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Can't have both a HTMLConstructor and a throwing chromeonly "
+ "constructor operation",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [LegacyNoInterfaceObject]
+ interface InterfaceWithoutInterfaceObject {
+ constructor();
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Can't have a constructor operation on a [LegacyNoInterfaceObject] "
+ "interface",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface InterfaceWithPartial {
+ };
+
+ partial interface InterfaceWithPartial {
+ constructor();
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Can't have a constructor operation on a partial interface")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface InterfaceWithMixin {
+ };
+
+ interface mixin Mixin {
+ constructor();
+ };
+
+ InterfaceWithMixin includes Mixin
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Can't have a constructor operation on a mixin")
diff --git a/dom/bindings/parser/tests/test_constructor_global.py b/dom/bindings/parser/tests/test_constructor_global.py
new file mode 100644
index 0000000000..74c81699e3
--- /dev/null
+++ b/dom/bindings/parser/tests/test_constructor_global.py
@@ -0,0 +1,69 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ [Global, Exposed=TestConstructorGlobal]
+ interface TestConstructorGlobal {
+ constructor();
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [Global, Exposed=TestLegacyFactoryFunctionGlobal,
+ LegacyFactoryFunction=FooBar]
+ interface TestLegacyFactoryFunctionGlobal {
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [LegacyFactoryFunction=FooBar, Global,
+ Exposed=TestLegacyFactoryFunctionGlobal]
+ interface TestLegacyFactoryFunctionGlobal {
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [Global, Exposed=TestHTMLConstructorGlobal]
+ interface TestHTMLConstructorGlobal {
+ [HTMLConstructor] constructor();
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/dom/bindings/parser/tests/test_constructor_no_interface_object.py b/dom/bindings/parser/tests/test_constructor_no_interface_object.py
new file mode 100644
index 0000000000..513a9ab3f8
--- /dev/null
+++ b/dom/bindings/parser/tests/test_constructor_no_interface_object.py
@@ -0,0 +1,47 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ [LegacyNoInterfaceObject]
+ interface TestConstructorLegacyNoInterfaceObject {
+ constructor();
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+
+ parser.parse(
+ """
+ [LegacyNoInterfaceObject, LegacyFactoryFunction=FooBar]
+ interface TestLegacyFactoryFunctionLegacyNoInterfaceObject {
+ };
+ """
+ )
+
+ # Test HTMLConstructor and LegacyNoInterfaceObject
+ parser = parser.reset()
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ [LegacyNoInterfaceObject]
+ interface TestHTMLConstructorLegacyNoInterfaceObject {
+ [HTMLConstructor] constructor();
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/dom/bindings/parser/tests/test_deduplicate.py b/dom/bindings/parser/tests/test_deduplicate.py
new file mode 100644
index 0000000000..2308f6201e
--- /dev/null
+++ b/dom/bindings/parser/tests/test_deduplicate.py
@@ -0,0 +1,17 @@
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ interface Foo;
+ interface Bar;
+ interface Foo;
+ """
+ )
+
+ results = parser.finish()
+
+ # There should be no duplicate interfaces in the result.
+ expectedNames = sorted(["Foo", "Bar"])
+ actualNames = sorted(map(lambda iface: iface.identifier.name, results))
+ harness.check(
+ actualNames, expectedNames, "Parser shouldn't output duplicate names."
+ )
diff --git a/dom/bindings/parser/tests/test_dictionary.py b/dom/bindings/parser/tests/test_dictionary.py
new file mode 100644
index 0000000000..26411b96d7
--- /dev/null
+++ b/dom/bindings/parser/tests/test_dictionary.py
@@ -0,0 +1,875 @@
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ dictionary Dict2 : Dict1 {
+ long child = 5;
+ Dict1 aaandAnother;
+ };
+ dictionary Dict1 {
+ long parent;
+ double otherParent;
+ };
+ """
+ )
+ results = parser.finish()
+
+ dict1 = results[1]
+ dict2 = results[0]
+
+ harness.check(len(dict1.members), 2, "Dict1 has two members")
+ harness.check(len(dict2.members), 2, "Dict2 has four members")
+
+ harness.check(
+ dict1.members[0].identifier.name, "otherParent", "'o' comes before 'p'"
+ )
+ harness.check(
+ dict1.members[1].identifier.name, "parent", "'o' really comes before 'p'"
+ )
+ harness.check(
+ dict2.members[0].identifier.name, "aaandAnother", "'a' comes before 'c'"
+ )
+ harness.check(
+ dict2.members[1].identifier.name, "child", "'a' really comes before 'c'"
+ )
+
+ # Test partial dictionary.
+ parser = parser.reset()
+ parser.parse(
+ """
+ dictionary A {
+ long c;
+ long g;
+ };
+ partial dictionary A {
+ long h;
+ long d;
+ };
+ """
+ )
+ results = parser.finish()
+
+ dict1 = results[0]
+ harness.check(len(dict1.members), 4, "Dict1 has four members")
+ harness.check(dict1.members[0].identifier.name, "c", "c should be first")
+ harness.check(dict1.members[1].identifier.name, "d", "d should come after c")
+ harness.check(dict1.members[2].identifier.name, "g", "g should come after d")
+ harness.check(dict1.members[3].identifier.name, "h", "h should be last")
+
+ # Now reset our parser
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary Dict {
+ long prop = 5;
+ long prop;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should not allow name duplication in a dictionary")
+
+ # Test no name duplication across normal and partial dictionary.
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ long prop = 5;
+ };
+ partial dictionary A {
+ long prop;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw, "Should not allow name duplication across normal and partial dictionary"
+ )
+
+ # Now reset our parser again
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary Dict1 : Dict2 {
+ long prop = 5;
+ };
+ dictionary Dict2 : Dict3 {
+ long prop2;
+ };
+ dictionary Dict3 {
+ double prop;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw, "Should not allow name duplication in a dictionary and " "its ancestor"
+ )
+
+ # More reset
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Iface {};
+ dictionary Dict : Iface {
+ long prop;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should not allow non-dictionary parents for dictionaries")
+
+ # Even more reset
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A : B {};
+ dictionary B : A {};
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should not allow cycles in dictionary inheritance chains")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ [LegacyNullToEmptyString] DOMString foo;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw, "Should not allow [LegacyNullToEmptyString] on dictionary members"
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ };
+ interface X {
+ undefined doFoo(A arg);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Trailing dictionary arg must be optional")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ };
+ interface X {
+ undefined doFoo(optional A arg);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Trailing dictionary arg must have a default value")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ };
+ interface X {
+ undefined doFoo((A or DOMString) arg);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Trailing union arg containing a dictionary must be optional")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ };
+ interface X {
+ undefined doFoo(optional (A or DOMString) arg);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw, "Trailing union arg containing a dictionary must have a default value"
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ };
+ interface X {
+ undefined doFoo(A arg1, optional long arg2);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Dictionary arg followed by optional arg must be optional")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ };
+ interface X {
+ undefined doFoo(optional A arg1, optional long arg2);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Dictionary arg followed by optional arg must have default value")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ };
+ interface X {
+ undefined doFoo(A arg1, optional long arg2, long arg3);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ not threw,
+ "Dictionary arg followed by non-optional arg doesn't have to be optional",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ };
+ interface X {
+ undefined doFoo((A or DOMString) arg1, optional long arg2);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Union arg containing dictionary followed by optional arg must " "be optional",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ };
+ interface X {
+ undefined doFoo(optional (A or DOMString) arg1, optional long arg2);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Union arg containing dictionary followed by optional arg must "
+ "have a default value",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ dictionary A {
+ };
+ interface X {
+ undefined doFoo(A arg1, long arg2);
+ };
+ """
+ )
+ parser.finish()
+ harness.ok(True, "Dictionary arg followed by required arg can be required")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ };
+ interface X {
+ undefined doFoo(optional A? arg1 = {});
+ };
+ """
+ )
+ parser.finish()
+ except Exception as x:
+ threw = x
+
+ harness.ok(threw, "Optional dictionary arg must not be nullable")
+ harness.ok(
+ "nullable" in str(threw),
+ "Must have the expected exception for optional nullable dictionary arg",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ required long x;
+ };
+ interface X {
+ undefined doFoo(A? arg1);
+ };
+ """
+ )
+ parser.finish()
+ except Exception as x:
+ threw = x
+
+ harness.ok(threw, "Required dictionary arg must not be nullable")
+ harness.ok(
+ "nullable" in str(threw),
+ "Must have the expected exception for required nullable " "dictionary arg",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ };
+ interface X {
+ undefined doFoo(optional (A or long)? arg1 = {});
+ };
+ """
+ )
+ parser.finish()
+ except Exception as x:
+ threw = x
+
+ harness.ok(threw, "Dictionary arg must not be in an optional nullable union")
+ harness.ok(
+ "nullable" in str(threw),
+ "Must have the expected exception for optional nullable union "
+ "arg containing dictionary",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ required long x;
+ };
+ interface X {
+ undefined doFoo((A or long)? arg1);
+ };
+ """
+ )
+ parser.finish()
+ except Exception as x:
+ threw = x
+
+ harness.ok(threw, "Dictionary arg must not be in a required nullable union")
+ harness.ok(
+ "nullable" in str(threw),
+ "Must have the expected exception for required nullable union "
+ "arg containing dictionary",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ };
+ interface X {
+ undefined doFoo(sequence<A?> arg1);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(not threw, "Nullable union should be allowed in a sequence argument")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ };
+ interface X {
+ undefined doFoo(optional (A or long?) arg1);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Dictionary must not be in a union with a nullable type")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ };
+ interface X {
+ undefined doFoo(optional (long? or A) arg1);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "A nullable type must not be in a union with a dictionary")
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ dictionary A {
+ };
+ interface X {
+ A? doFoo();
+ };
+ """
+ )
+ parser.finish()
+ harness.ok(True, "Dictionary return value can be nullable")
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ dictionary A {
+ };
+ interface X {
+ undefined doFoo(optional A arg = {});
+ };
+ """
+ )
+ parser.finish()
+ harness.ok(True, "Dictionary arg should actually parse")
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ dictionary A {
+ };
+ interface X {
+ undefined doFoo(optional (A or DOMString) arg = {});
+ };
+ """
+ )
+ parser.finish()
+ harness.ok(True, "Union arg containing a dictionary should actually parse")
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ dictionary A {
+ };
+ interface X {
+ undefined doFoo(optional (A or DOMString) arg = "abc");
+ };
+ """
+ )
+ parser.finish()
+ harness.ok(
+ True,
+ "Union arg containing a dictionary with string default should actually parse",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary Foo {
+ Foo foo;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Member type must not be its Dictionary.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary Foo3 : Foo {
+ short d;
+ };
+
+ dictionary Foo2 : Foo3 {
+ boolean c;
+ };
+
+ dictionary Foo1 : Foo2 {
+ long a;
+ };
+
+ dictionary Foo {
+ Foo1 b;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Member type must not be a Dictionary that " "inherits from its Dictionary.",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary Foo {
+ (Foo or DOMString)[]? b;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Member type must not be a Nullable type "
+ "whose inner type includes its Dictionary.",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary Foo {
+ (DOMString or Foo) b;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Member type must not be a Union type, one of "
+ "whose member types includes its Dictionary.",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary Foo {
+ sequence<sequence<sequence<Foo>>> c;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Member type must not be a Sequence type "
+ "whose element type includes its Dictionary.",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary Foo {
+ (DOMString or Foo)[] d;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Member type must not be an Array type "
+ "whose element type includes its Dictionary.",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary Foo {
+ Foo1 b;
+ };
+
+ dictionary Foo3 {
+ Foo d;
+ };
+
+ dictionary Foo2 : Foo3 {
+ short c;
+ };
+
+ dictionary Foo1 : Foo2 {
+ long a;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Member type must not be a Dictionary, one of whose "
+ "members or inherited members has a type that includes "
+ "its Dictionary.",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary Foo {
+ };
+
+ dictionary Bar {
+ Foo? d;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Member type must not be a nullable dictionary")
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ dictionary Foo {
+ unrestricted float urFloat = 0;
+ unrestricted float urFloat2 = 1.1;
+ unrestricted float urFloat3 = -1.1;
+ unrestricted float? urFloat4 = null;
+ unrestricted float infUrFloat = Infinity;
+ unrestricted float negativeInfUrFloat = -Infinity;
+ unrestricted float nanUrFloat = NaN;
+
+ unrestricted double urDouble = 0;
+ unrestricted double urDouble2 = 1.1;
+ unrestricted double urDouble3 = -1.1;
+ unrestricted double? urDouble4 = null;
+ unrestricted double infUrDouble = Infinity;
+ unrestricted double negativeInfUrDouble = -Infinity;
+ unrestricted double nanUrDouble = NaN;
+ };
+ """
+ )
+ parser.finish()
+ harness.ok(True, "Parsing default values for unrestricted types succeeded.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary Foo {
+ double f = Infinity;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Only unrestricted values can be initialized to Infinity")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary Foo {
+ double f = -Infinity;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Only unrestricted values can be initialized to -Infinity")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary Foo {
+ double f = NaN;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Only unrestricted values can be initialized to NaN")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary Foo {
+ float f = Infinity;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Only unrestricted values can be initialized to Infinity")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary Foo {
+ float f = -Infinity;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Only unrestricted values can be initialized to -Infinity")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary Foo {
+ float f = NaN;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Only unrestricted values can be initialized to NaN")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary Foo {
+ long module;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(not threw, "Should be able to use 'module' as a dictionary member name")
diff --git a/dom/bindings/parser/tests/test_distinguishability.py b/dom/bindings/parser/tests/test_distinguishability.py
new file mode 100644
index 0000000000..8c938d88a3
--- /dev/null
+++ b/dom/bindings/parser/tests/test_distinguishability.py
@@ -0,0 +1,421 @@
+def firstArgType(method):
+ return method.signatures()[0][1][0].type
+
+
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ // Give our dictionary a required member so we don't need to
+ // mess with optional and default values.
+ dictionary Dict {
+ required long member;
+ };
+ callback interface Foo {
+ };
+ interface Bar {
+ // Bit of a pain to get things that have dictionary types
+ undefined passDict(Dict arg);
+ undefined passFoo(Foo arg);
+ undefined passNullableUnion((object? or DOMString) arg);
+ undefined passNullable(Foo? arg);
+ };
+ """
+ )
+ results = parser.finish()
+
+ iface = results[2]
+ harness.ok(iface.isInterface(), "Should have interface")
+ dictMethod = iface.members[0]
+ ifaceMethod = iface.members[1]
+ nullableUnionMethod = iface.members[2]
+ nullableIfaceMethod = iface.members[3]
+
+ dictType = firstArgType(dictMethod)
+ ifaceType = firstArgType(ifaceMethod)
+
+ harness.ok(dictType.isDictionary(), "Should have dictionary type")
+ harness.ok(ifaceType.isInterface(), "Should have interface type")
+ harness.ok(ifaceType.isCallbackInterface(), "Should have callback interface type")
+
+ harness.ok(
+ not dictType.isDistinguishableFrom(ifaceType),
+ "Dictionary not distinguishable from callback interface",
+ )
+ harness.ok(
+ not ifaceType.isDistinguishableFrom(dictType),
+ "Callback interface not distinguishable from dictionary",
+ )
+
+ nullableUnionType = firstArgType(nullableUnionMethod)
+ nullableIfaceType = firstArgType(nullableIfaceMethod)
+
+ harness.ok(nullableUnionType.isUnion(), "Should have union type")
+ harness.ok(nullableIfaceType.isInterface(), "Should have interface type")
+ harness.ok(nullableIfaceType.nullable(), "Should have nullable type")
+
+ harness.ok(
+ not nullableUnionType.isDistinguishableFrom(nullableIfaceType),
+ "Nullable type not distinguishable from union with nullable " "member type",
+ )
+ harness.ok(
+ not nullableIfaceType.isDistinguishableFrom(nullableUnionType),
+ "Union with nullable member type not distinguishable from " "nullable type",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface TestIface {
+ undefined passKid(Kid arg);
+ undefined passParent(Parent arg);
+ undefined passGrandparent(Grandparent arg);
+ undefined passUnrelated1(Unrelated1 arg);
+ undefined passUnrelated2(Unrelated2 arg);
+ undefined passArrayBuffer(ArrayBuffer arg);
+ undefined passArrayBuffer(ArrayBufferView arg);
+ };
+
+ interface Kid : Parent {};
+ interface Parent : Grandparent {};
+ interface Grandparent {};
+ interface Unrelated1 {};
+ interface Unrelated2 {};
+ """
+ )
+ results = parser.finish()
+
+ iface = results[0]
+ harness.ok(iface.isInterface(), "Should have interface")
+ argTypes = [firstArgType(method) for method in iface.members]
+ unrelatedTypes = [firstArgType(method) for method in iface.members[-3:]]
+
+ for type1 in argTypes:
+ for type2 in argTypes:
+ distinguishable = type1 is not type2 and (
+ type1 in unrelatedTypes or type2 in unrelatedTypes
+ )
+
+ harness.check(
+ type1.isDistinguishableFrom(type2),
+ distinguishable,
+ "Type %s should %sbe distinguishable from type %s"
+ % (type1, "" if distinguishable else "not ", type2),
+ )
+ harness.check(
+ type2.isDistinguishableFrom(type1),
+ distinguishable,
+ "Type %s should %sbe distinguishable from type %s"
+ % (type2, "" if distinguishable else "not ", type1),
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface Dummy {};
+ interface TestIface {
+ undefined method(long arg1, TestIface arg2);
+ undefined method(long arg1, long arg2);
+ undefined method(long arg1, Dummy arg2);
+ undefined method(DOMString arg1, DOMString arg2, DOMString arg3);
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(len(results[1].members), 1, "Should look like we have one method")
+ harness.check(
+ len(results[1].members[0].signatures()), 4, "Should have four signatures"
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Dummy {};
+ interface TestIface {
+ undefined method(long arg1, TestIface arg2);
+ undefined method(long arg1, long arg2);
+ undefined method(any arg1, Dummy arg2);
+ undefined method(DOMString arg1, DOMString arg2, DOMString arg3);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Should throw when args before the distinguishing arg are not "
+ "all the same type",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Dummy {};
+ interface TestIface {
+ undefined method(long arg1, TestIface arg2);
+ undefined method(long arg1, long arg2);
+ undefined method(any arg1, DOMString arg2);
+ undefined method(DOMString arg1, DOMString arg2, DOMString arg3);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should throw when there is no distinguishing index")
+
+ # Now let's test our whole distinguishability table
+ argTypes = [
+ "long",
+ "short",
+ "long?",
+ "short?",
+ "boolean",
+ "boolean?",
+ "undefined",
+ "undefined?",
+ "DOMString",
+ "ByteString",
+ "UTF8String",
+ "Enum",
+ "Enum2",
+ "Interface",
+ "Interface?",
+ "AncestorInterface",
+ "UnrelatedInterface",
+ "CallbackInterface",
+ "CallbackInterface?",
+ "CallbackInterface2",
+ "object",
+ "Callback",
+ "Callback2",
+ "Dict",
+ "Dict2",
+ "sequence<long>",
+ "sequence<short>",
+ "record<DOMString, object>",
+ "record<USVString, Dict>",
+ "record<ByteString, long>",
+ "record<UTF8String, long>",
+ "any",
+ "Promise<any>",
+ "Promise<any>?",
+ "USVString",
+ "JSString",
+ "ArrayBuffer",
+ "ArrayBufferView",
+ "Uint8Array",
+ "Uint16Array",
+ "(long or Callback)",
+ "(long or Dict)",
+ ]
+
+ # Try to categorize things a bit to keep list lengths down
+ def allBut(list1, list2):
+ return [
+ a
+ for a in list1
+ if a not in list2
+ and (a != "any" and a != "Promise<any>" and a != "Promise<any>?")
+ ]
+
+ unions = ["(long or Callback)", "(long or Dict)"]
+ numerics = ["long", "short", "long?", "short?"]
+ booleans = ["boolean", "boolean?"]
+ undefineds = ["undefined", "undefined?"]
+ primitives = numerics + booleans
+ nonNumerics = allBut(argTypes, numerics + unions)
+ nonBooleans = allBut(argTypes, booleans)
+ strings = [
+ "DOMString",
+ "ByteString",
+ "Enum",
+ "Enum2",
+ "USVString",
+ "JSString",
+ "UTF8String",
+ ]
+ nonStrings = allBut(argTypes, strings)
+ nonObjects = undefineds + primitives + strings
+ bufferSourceTypes = ["ArrayBuffer", "ArrayBufferView", "Uint8Array", "Uint16Array"]
+ interfaces = [
+ "Interface",
+ "Interface?",
+ "AncestorInterface",
+ "UnrelatedInterface",
+ ] + bufferSourceTypes
+ nullables = [
+ "long?",
+ "short?",
+ "boolean?",
+ "undefined?",
+ "Interface?",
+ "CallbackInterface?",
+ "Dict",
+ "Dict2",
+ "Date?",
+ "any",
+ "Promise<any>?",
+ ] + allBut(unions, ["(long or Callback)"])
+ sequences = ["sequence<long>", "sequence<short>"]
+ nonUserObjects = nonObjects + interfaces + sequences
+ otherObjects = allBut(argTypes, nonUserObjects + ["object"])
+ notRelatedInterfaces = (
+ nonObjects
+ + ["UnrelatedInterface"]
+ + otherObjects
+ + sequences
+ + bufferSourceTypes
+ )
+ records = [
+ "record<DOMString, object>",
+ "record<USVString, Dict>",
+ "record<ByteString, long>",
+ "record<UTF8String, long>",
+ ] # JSString not supported in records
+ dictionaryLike = (
+ [
+ "Dict",
+ "Dict2",
+ "CallbackInterface",
+ "CallbackInterface?",
+ "CallbackInterface2",
+ ]
+ + records
+ + allBut(unions, ["(long or Callback)"])
+ )
+
+ # Build a representation of the distinguishability table as a dict
+ # of dicts, holding True values where needed, holes elsewhere.
+ data = dict()
+ for type in argTypes:
+ data[type] = dict()
+
+ def setDistinguishable(type, types):
+ for other in types:
+ data[type][other] = True
+
+ setDistinguishable("long", nonNumerics)
+ setDistinguishable("short", nonNumerics)
+ setDistinguishable("long?", allBut(nonNumerics, nullables))
+ setDistinguishable("short?", allBut(nonNumerics, nullables))
+ setDistinguishable("boolean", nonBooleans)
+ setDistinguishable("boolean?", allBut(nonBooleans, nullables))
+ setDistinguishable("undefined", allBut(argTypes, undefineds + dictionaryLike))
+ setDistinguishable(
+ "undefined?", allBut(argTypes, undefineds + dictionaryLike + nullables)
+ )
+ setDistinguishable("DOMString", nonStrings)
+ setDistinguishable("ByteString", nonStrings)
+ setDistinguishable("UTF8String", nonStrings)
+ setDistinguishable("USVString", nonStrings)
+ setDistinguishable("JSString", nonStrings)
+ setDistinguishable("Enum", nonStrings)
+ setDistinguishable("Enum2", nonStrings)
+ setDistinguishable("Interface", notRelatedInterfaces)
+ setDistinguishable("Interface?", allBut(notRelatedInterfaces, nullables))
+ setDistinguishable("AncestorInterface", notRelatedInterfaces)
+ setDistinguishable(
+ "UnrelatedInterface", allBut(argTypes, ["object", "UnrelatedInterface"])
+ )
+ setDistinguishable("CallbackInterface", allBut(nonUserObjects, undefineds))
+ setDistinguishable(
+ "CallbackInterface?", allBut(nonUserObjects, nullables + undefineds)
+ )
+ setDistinguishable("CallbackInterface2", allBut(nonUserObjects, undefineds))
+ setDistinguishable("object", nonObjects)
+ setDistinguishable("Callback", nonUserObjects)
+ setDistinguishable("Callback2", nonUserObjects)
+ setDistinguishable("Dict", allBut(nonUserObjects, nullables + undefineds))
+ setDistinguishable("Dict2", allBut(nonUserObjects, nullables + undefineds))
+ setDistinguishable("sequence<long>", allBut(argTypes, sequences + ["object"]))
+ setDistinguishable("sequence<short>", allBut(argTypes, sequences + ["object"]))
+ setDistinguishable("record<DOMString, object>", allBut(nonUserObjects, undefineds))
+ setDistinguishable("record<USVString, Dict>", allBut(nonUserObjects, undefineds))
+ # JSString not supported in records
+ setDistinguishable("record<ByteString, long>", allBut(nonUserObjects, undefineds))
+ setDistinguishable("record<UTF8String, long>", allBut(nonUserObjects, undefineds))
+ setDistinguishable("any", [])
+ setDistinguishable("Promise<any>", [])
+ setDistinguishable("Promise<any>?", [])
+ setDistinguishable("ArrayBuffer", allBut(argTypes, ["ArrayBuffer", "object"]))
+ setDistinguishable(
+ "ArrayBufferView",
+ allBut(argTypes, ["ArrayBufferView", "Uint8Array", "Uint16Array", "object"]),
+ )
+ setDistinguishable(
+ "Uint8Array", allBut(argTypes, ["ArrayBufferView", "Uint8Array", "object"])
+ )
+ setDistinguishable(
+ "Uint16Array", allBut(argTypes, ["ArrayBufferView", "Uint16Array", "object"])
+ )
+ setDistinguishable("(long or Callback)", allBut(nonUserObjects, numerics))
+ setDistinguishable(
+ "(long or Dict)", allBut(nonUserObjects, numerics + nullables + undefineds)
+ )
+
+ def areDistinguishable(type1, type2):
+ return data[type1].get(type2, False)
+
+ def checkDistinguishability(parser, type1, type2):
+ idlTemplate = """
+ enum Enum { "a", "b" };
+ enum Enum2 { "c", "d" };
+ interface Interface : AncestorInterface {};
+ interface AncestorInterface {};
+ interface UnrelatedInterface {};
+ callback interface CallbackInterface {};
+ callback interface CallbackInterface2 {};
+ callback Callback = any();
+ callback Callback2 = long(short arg);
+ // Give our dictionaries required members so we don't need to
+ // mess with optional and default values.
+ dictionary Dict { required long member; };
+ dictionary Dict2 { required long member; };
+ interface TestInterface {%s
+ };
+ """
+ if type1 in undefineds or type2 in undefineds:
+ methods = """
+ (%s or %s) myMethod();""" % (
+ type1,
+ type2,
+ )
+ else:
+ methodTemplate = """
+ undefined myMethod(%s arg);"""
+ methods = (methodTemplate % type1) + (methodTemplate % type2)
+ idl = idlTemplate % methods
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(idl)
+ parser.finish()
+ except Exception:
+ threw = True
+
+ if areDistinguishable(type1, type2):
+ harness.ok(
+ not threw,
+ "Should not throw for '%s' and '%s' because they are distinguishable"
+ % (type1, type2),
+ )
+ else:
+ harness.ok(
+ threw,
+ "Should throw for '%s' and '%s' because they are not distinguishable"
+ % (type1, type2),
+ )
+
+ # Enumerate over everything in both orders, since order matters in
+ # terms of our implementation of distinguishability checks
+ for type1 in argTypes:
+ for type2 in argTypes:
+ checkDistinguishability(parser, type1, type2)
diff --git a/dom/bindings/parser/tests/test_double_null.py b/dom/bindings/parser/tests/test_double_null.py
new file mode 100644
index 0000000000..5160a53727
--- /dev/null
+++ b/dom/bindings/parser/tests/test_double_null.py
@@ -0,0 +1,16 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface DoubleNull {
+ attribute byte?? foo;
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/dom/bindings/parser/tests/test_duplicate_qualifiers.py b/dom/bindings/parser/tests/test_duplicate_qualifiers.py
new file mode 100644
index 0000000000..2b37cdc0fb
--- /dev/null
+++ b/dom/bindings/parser/tests/test_duplicate_qualifiers.py
@@ -0,0 +1,64 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface DuplicateQualifiers1 {
+ getter getter byte foo(unsigned long index);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface DuplicateQualifiers2 {
+ setter setter byte foo(unsigned long index, byte value);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface DuplicateQualifiers4 {
+ deleter deleter byte foo(unsigned long index);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface DuplicateQualifiers5 {
+ getter deleter getter byte foo(unsigned long index);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/dom/bindings/parser/tests/test_empty_enum.py b/dom/bindings/parser/tests/test_empty_enum.py
new file mode 100644
index 0000000000..cd6b985dc9
--- /dev/null
+++ b/dom/bindings/parser/tests/test_empty_enum.py
@@ -0,0 +1,14 @@
+def WebIDLTest(parser, harness):
+ try:
+ parser.parse(
+ """
+ enum TestEmptyEnum {
+ };
+ """
+ )
+
+ harness.ok(False, "Should have thrown!")
+ except Exception:
+ harness.ok(True, "Parsing TestEmptyEnum enum should fail")
+
+ parser.finish()
diff --git a/dom/bindings/parser/tests/test_empty_sequence_default_value.py b/dom/bindings/parser/tests/test_empty_sequence_default_value.py
new file mode 100644
index 0000000000..da0b3add82
--- /dev/null
+++ b/dom/bindings/parser/tests/test_empty_sequence_default_value.py
@@ -0,0 +1,54 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface X {
+ const sequence<long> foo = [];
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Constant cannot have [] as a default value")
+
+ parser = parser.reset()
+
+ parser.parse(
+ """
+ interface X {
+ undefined foo(optional sequence<long> arg = []);
+ };
+ """
+ )
+ results = parser.finish()
+
+ harness.ok(
+ isinstance(
+ results[0].members[0].signatures()[0][1][0].defaultValue,
+ WebIDL.IDLEmptySequenceValue,
+ ),
+ "Should have IDLEmptySequenceValue as default value of argument",
+ )
+
+ parser = parser.reset()
+
+ parser.parse(
+ """
+ dictionary X {
+ sequence<long> foo = [];
+ };
+ """
+ )
+ results = parser.finish()
+
+ harness.ok(
+ isinstance(results[0].members[0].defaultValue, WebIDL.IDLEmptySequenceValue),
+ "Should have IDLEmptySequenceValue as default value of " "dictionary member",
+ )
diff --git a/dom/bindings/parser/tests/test_enum.py b/dom/bindings/parser/tests/test_enum.py
new file mode 100644
index 0000000000..d7305a708f
--- /dev/null
+++ b/dom/bindings/parser/tests/test_enum.py
@@ -0,0 +1,107 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ enum TestEnum {
+ "",
+ "foo",
+ "bar"
+ };
+
+ interface TestEnumInterface {
+ TestEnum doFoo(boolean arg);
+ readonly attribute TestEnum foo;
+ };
+ """
+ )
+
+ results = parser.finish()
+
+ harness.ok(True, "TestEnumInterfaces interface parsed without error.")
+ harness.check(len(results), 2, "Should be one production")
+ harness.ok(isinstance(results[0], WebIDL.IDLEnum), "Should be an IDLEnum")
+ harness.ok(isinstance(results[1], WebIDL.IDLInterface), "Should be an IDLInterface")
+
+ enum = results[0]
+ harness.check(enum.identifier.QName(), "::TestEnum", "Enum has the right QName")
+ harness.check(enum.identifier.name, "TestEnum", "Enum has the right name")
+ harness.check(enum.values(), ["", "foo", "bar"], "Enum has the right values")
+
+ iface = results[1]
+
+ harness.check(
+ iface.identifier.QName(), "::TestEnumInterface", "Interface has the right QName"
+ )
+ harness.check(
+ iface.identifier.name, "TestEnumInterface", "Interface has the right name"
+ )
+ harness.check(iface.parent, None, "Interface has no parent")
+
+ members = iface.members
+ harness.check(len(members), 2, "Should be one production")
+ harness.ok(isinstance(members[0], WebIDL.IDLMethod), "Should be an IDLMethod")
+ method = members[0]
+ harness.check(
+ method.identifier.QName(),
+ "::TestEnumInterface::doFoo",
+ "Method has correct QName",
+ )
+ harness.check(method.identifier.name, "doFoo", "Method has correct name")
+
+ signatures = method.signatures()
+ harness.check(len(signatures), 1, "Expect one signature")
+
+ (returnType, arguments) = signatures[0]
+ harness.check(
+ str(returnType), "TestEnum (Wrapper)", "Method type is the correct name"
+ )
+ harness.check(len(arguments), 1, "Method has the right number of arguments")
+ arg = arguments[0]
+ harness.ok(isinstance(arg, WebIDL.IDLArgument), "Should be an IDLArgument")
+ harness.check(str(arg.type), "Boolean", "Argument has the right type")
+
+ attr = members[1]
+ harness.check(
+ attr.identifier.QName(), "::TestEnumInterface::foo", "Attr has correct QName"
+ )
+ harness.check(attr.identifier.name, "foo", "Attr has correct name")
+
+ harness.check(str(attr.type), "TestEnum (Wrapper)", "Attr type is the correct name")
+
+ # Now reset our parser
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ enum Enum {
+ "a",
+ "b",
+ "c"
+ };
+ interface TestInterface {
+ undefined foo(optional Enum e = "d");
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should not allow a bogus default value for an enum")
+
+ # Now reset our parser
+ parser = parser.reset()
+ parser.parse(
+ """
+ enum Enum {
+ "a",
+ "b",
+ "c",
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(len(results), 1, "Should allow trailing comma in enum")
diff --git a/dom/bindings/parser/tests/test_enum_duplicate_values.py b/dom/bindings/parser/tests/test_enum_duplicate_values.py
new file mode 100644
index 0000000000..aec28c49e8
--- /dev/null
+++ b/dom/bindings/parser/tests/test_enum_duplicate_values.py
@@ -0,0 +1,13 @@
+def WebIDLTest(parser, harness):
+ try:
+ parser.parse(
+ """
+ enum TestEnumDuplicateValue {
+ "",
+ ""
+ };
+ """
+ )
+ harness.ok(False, "Should have thrown!")
+ except Exception:
+ harness.ok(True, "Enum TestEnumDuplicateValue should throw")
diff --git a/dom/bindings/parser/tests/test_error_colno.py b/dom/bindings/parser/tests/test_error_colno.py
new file mode 100644
index 0000000000..b08ba7cc17
--- /dev/null
+++ b/dom/bindings/parser/tests/test_error_colno.py
@@ -0,0 +1,24 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ # Check that error messages put the '^' in the right place.
+
+ threw = False
+ input = "interface ?"
+ try:
+ parser.parse(input)
+ parser.finish()
+ except WebIDL.WebIDLError as e:
+ threw = True
+ lines = str(e).split("\n")
+
+ harness.check(len(lines), 3, "Expected number of lines in error message")
+ harness.check(lines[1], input, "Second line shows error")
+ harness.check(
+ lines[2],
+ " " * (len(input) - 1) + "^",
+ "Correct column pointer in error message",
+ )
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/dom/bindings/parser/tests/test_error_lineno.py b/dom/bindings/parser/tests/test_error_lineno.py
new file mode 100644
index 0000000000..30629be30c
--- /dev/null
+++ b/dom/bindings/parser/tests/test_error_lineno.py
@@ -0,0 +1,38 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ # Check that error messages put the '^' in the right place.
+
+ threw = False
+ input = """\
+// This is a comment.
+interface Foo {
+};
+
+/* This is also a comment. */
+interface ?"""
+ try:
+ parser.parse(input)
+ parser.finish()
+ except WebIDL.WebIDLError as e:
+ threw = True
+ lines = str(e).split("\n")
+
+ harness.check(len(lines), 3, "Expected number of lines in error message")
+ harness.ok(
+ lines[0].endswith("line 6:10"),
+ 'First line of error should end with "line 6:10", but was "%s".' % lines[0],
+ )
+ harness.check(
+ lines[1],
+ "interface ?",
+ "Second line of error message is the line which caused the error.",
+ )
+ harness.check(
+ lines[2],
+ " " * (len("interface ?") - 1) + "^",
+ "Correct column pointer in error message.",
+ )
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/dom/bindings/parser/tests/test_exposed_extended_attribute.py b/dom/bindings/parser/tests/test_exposed_extended_attribute.py
new file mode 100644
index 0000000000..0bee74a0db
--- /dev/null
+++ b/dom/bindings/parser/tests/test_exposed_extended_attribute.py
@@ -0,0 +1,383 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ [Global, Exposed=Foo] interface Foo {};
+ [Global=(Bar, Bar1,Bar2), Exposed=Bar] interface Bar {};
+ [Global=(Baz, Baz2), Exposed=Baz] interface Baz {};
+
+ [Exposed=(Foo,Bar1)]
+ interface Iface {
+ undefined method1();
+
+ [Exposed=Bar1]
+ readonly attribute any attr;
+ };
+
+ [Exposed=Foo]
+ partial interface Iface {
+ undefined method2();
+ };
+ """
+ )
+
+ results = parser.finish()
+
+ harness.check(len(results), 5, "Should know about five things")
+ iface = results[3]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should have an interface here")
+ members = iface.members
+ harness.check(len(members), 3, "Should have three members")
+
+ harness.ok(
+ members[0].exposureSet == set(["Foo", "Bar"]),
+ "method1 should have the right exposure set",
+ )
+ harness.ok(
+ members[0]._exposureGlobalNames == set(["Foo", "Bar1"]),
+ "method1 should have the right exposure global names",
+ )
+
+ harness.ok(
+ members[1].exposureSet == set(["Bar"]),
+ "attr should have the right exposure set",
+ )
+ harness.ok(
+ members[1]._exposureGlobalNames == set(["Bar1"]),
+ "attr should have the right exposure global names",
+ )
+
+ harness.ok(
+ members[2].exposureSet == set(["Foo"]),
+ "method2 should have the right exposure set",
+ )
+ harness.ok(
+ members[2]._exposureGlobalNames == set(["Foo"]),
+ "method2 should have the right exposure global names",
+ )
+
+ harness.ok(
+ iface.exposureSet == set(["Foo", "Bar"]),
+ "Iface should have the right exposure set",
+ )
+ harness.ok(
+ iface._exposureGlobalNames == set(["Foo", "Bar1"]),
+ "Iface should have the right exposure global names",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ [Global, Exposed=Foo] interface Foo {};
+ [Global=(Bar, Bar1, Bar2), Exposed=Bar] interface Bar {};
+ [Global=(Baz, Baz2), Exposed=Baz] interface Baz {};
+
+ [Exposed=Foo]
+ interface Iface2 {
+ undefined method3();
+ };
+ """
+ )
+ results = parser.finish()
+
+ harness.check(len(results), 4, "Should know about four things")
+ iface = results[3]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should have an interface here")
+ members = iface.members
+ harness.check(len(members), 1, "Should have one member")
+
+ harness.ok(
+ members[0].exposureSet == set(["Foo"]),
+ "method3 should have the right exposure set",
+ )
+ harness.ok(
+ members[0]._exposureGlobalNames == set(["Foo"]),
+ "method3 should have the right exposure global names",
+ )
+
+ harness.ok(
+ iface.exposureSet == set(["Foo"]), "Iface2 should have the right exposure set"
+ )
+ harness.ok(
+ iface._exposureGlobalNames == set(["Foo"]),
+ "Iface2 should have the right exposure global names",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ [Global, Exposed=Foo] interface Foo {};
+ [Global=(Bar, Bar1, Bar2), Exposed=Bar] interface Bar {};
+ [Global=(Baz, Baz2), Exposed=Baz] interface Baz {};
+
+ [Exposed=Foo]
+ interface Iface3 {
+ undefined method4();
+ };
+
+ [Exposed=(Foo,Bar1)]
+ interface mixin Mixin {
+ undefined method5();
+ };
+
+ Iface3 includes Mixin;
+ """
+ )
+ results = parser.finish()
+ harness.check(len(results), 6, "Should know about six things")
+ iface = results[3]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should have an interface here")
+ members = iface.members
+ harness.check(len(members), 2, "Should have two members")
+
+ harness.ok(
+ members[0].exposureSet == set(["Foo"]),
+ "method4 should have the right exposure set",
+ )
+ harness.ok(
+ members[0]._exposureGlobalNames == set(["Foo"]),
+ "method4 should have the right exposure global names",
+ )
+
+ harness.ok(
+ members[1].exposureSet == set(["Foo", "Bar"]),
+ "method5 should have the right exposure set",
+ )
+ harness.ok(
+ members[1]._exposureGlobalNames == set(["Foo", "Bar1"]),
+ "method5 should have the right exposure global names",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [Exposed=Foo]
+ interface Bar {
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown on invalid Exposed value on interface.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Bar {
+ [Exposed=Foo]
+ readonly attribute bool attr;
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown on invalid Exposed value on attribute.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Bar {
+ [Exposed=Foo]
+ undefined operation();
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown on invalid Exposed value on operation.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Bar {
+ [Exposed=Foo]
+ const long constant = 5;
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown on invalid Exposed value on constant.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [Global, Exposed=Foo] interface Foo {};
+ [Global, Exposed=Bar] interface Bar {};
+
+ [Exposed=Foo]
+ interface Baz {
+ [Exposed=Bar]
+ undefined method();
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw, "Should have thrown on member exposed where its interface is not."
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ [Global, Exposed=Foo] interface Foo {};
+ [Global, Exposed=Bar] interface Bar {};
+
+ [Exposed=Foo]
+ interface Baz {
+ undefined method();
+ };
+
+ [Exposed=Bar]
+ interface mixin Mixin {
+ undefined otherMethod();
+ };
+
+ Baz includes Mixin;
+ """
+ )
+
+ results = parser.finish()
+
+ harness.check(len(results), 5, "Should know about five things")
+ iface = results[2]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should have an interface here")
+ members = iface.members
+ harness.check(len(members), 2, "Should have two members")
+
+ harness.ok(
+ members[0].exposureSet == set(["Foo"]),
+ "method should have the right exposure set",
+ )
+ harness.ok(
+ members[0]._exposureGlobalNames == set(["Foo"]),
+ "method should have the right exposure global names",
+ )
+
+ harness.ok(
+ members[1].exposureSet == set(["Bar"]),
+ "otherMethod should have the right exposure set",
+ )
+ harness.ok(
+ members[1]._exposureGlobalNames == set(["Bar"]),
+ "otherMethod should have the right exposure global names",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ [Global, Exposed=Foo] interface Foo {};
+ [Global, Exposed=Bar] interface Bar {};
+
+ [Exposed=*]
+ interface Baz {
+ undefined methodWild();
+ };
+
+ [Exposed=Bar]
+ interface mixin Mixin {
+ undefined methodNotWild();
+ };
+
+ Baz includes Mixin;
+ """
+ )
+
+ results = parser.finish()
+
+ harness.check(len(results), 5, "Should know about five things")
+ iface = results[2]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should have an interface here")
+ members = iface.members
+ harness.check(len(members), 2, "Should have two members")
+
+ harness.ok(
+ members[0].exposureSet == set(["Foo", "Bar"]),
+ "methodWild should have the right exposure set",
+ )
+ harness.ok(
+ members[0]._exposureGlobalNames == set(["Foo", "Bar"]),
+ "methodWild should have the right exposure global names",
+ )
+
+ harness.ok(
+ members[1].exposureSet == set(["Bar"]),
+ "methodNotWild should have the right exposure set",
+ )
+ harness.ok(
+ members[1]._exposureGlobalNames == set(["Bar"]),
+ "methodNotWild should have the right exposure global names",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [Global, Exposed=Foo] interface Foo {};
+ [Global, Exposed=Bar] interface Bar {};
+
+ [Exposed=Foo]
+ interface Baz {
+ [Exposed=*]
+ undefined method();
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw, "Should have thrown on member exposed where its interface is not."
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [Global, Exposed=Foo] interface Foo {};
+ [Global, Exposed=Bar] interface Bar {};
+
+ [Exposed=(Foo,*)]
+ interface Baz {
+ undefined method();
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown on a wildcard in an identifier list.")
diff --git a/dom/bindings/parser/tests/test_extended_attributes.py b/dom/bindings/parser/tests/test_extended_attributes.py
new file mode 100644
index 0000000000..b39ebd1029
--- /dev/null
+++ b/dom/bindings/parser/tests/test_extended_attributes.py
@@ -0,0 +1,128 @@
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ [LegacyNoInterfaceObject]
+ interface TestExtendedAttr {
+ [LegacyUnforgeable] readonly attribute byte b;
+ };
+ """
+ )
+
+ parser.finish()
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ [Pref="foo.bar",Pref=flop]
+ interface TestExtendedAttr {
+ [Pref="foo.bar"] attribute byte b;
+ };
+ """
+ )
+
+ parser.finish()
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface TestLegacyLenientThis {
+ [LegacyLenientThis] attribute byte b;
+ };
+ """
+ )
+
+ results = parser.finish()
+ harness.ok(
+ results[0].members[0].hasLegacyLenientThis(), "Should have a lenient this"
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestLegacyLenientThis2 {
+ [LegacyLenientThis=something] attribute byte b;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "[LegacyLenientThis] must take no arguments")
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface TestClamp {
+ undefined testClamp([Clamp] long foo);
+ undefined testNotClamp(long foo);
+ };
+ """
+ )
+
+ results = parser.finish()
+ # Pull out the first argument out of the arglist of the first (and
+ # only) signature.
+ harness.ok(
+ results[0].members[0].signatures()[0][1][0].type.hasClamp(), "Should be clamped"
+ )
+ harness.ok(
+ not results[0].members[1].signatures()[0][1][0].type.hasClamp(),
+ "Should not be clamped",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestClamp2 {
+ undefined testClamp([Clamp=something] long foo);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "[Clamp] must take no arguments")
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface TestEnforceRange {
+ undefined testEnforceRange([EnforceRange] long foo);
+ undefined testNotEnforceRange(long foo);
+ };
+ """
+ )
+
+ results = parser.finish()
+ # Pull out the first argument out of the arglist of the first (and
+ # only) signature.
+ harness.ok(
+ results[0].members[0].signatures()[0][1][0].type.hasEnforceRange(),
+ "Should be enforceRange",
+ )
+ harness.ok(
+ not results[0].members[1].signatures()[0][1][0].type.hasEnforceRange(),
+ "Should not be enforceRange",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestEnforceRange2 {
+ undefined testEnforceRange([EnforceRange=something] long foo);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "[EnforceRange] must take no arguments")
diff --git a/dom/bindings/parser/tests/test_float_types.py b/dom/bindings/parser/tests/test_float_types.py
new file mode 100644
index 0000000000..5cb9404699
--- /dev/null
+++ b/dom/bindings/parser/tests/test_float_types.py
@@ -0,0 +1,145 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ typedef float myFloat;
+ typedef unrestricted float myUnrestrictedFloat;
+ interface FloatTypes {
+ attribute float f;
+ attribute unrestricted float uf;
+ attribute double d;
+ attribute unrestricted double ud;
+ [LenientFloat]
+ attribute float lf;
+ [LenientFloat]
+ attribute double ld;
+
+ undefined m1(float arg1, double arg2, float? arg3, double? arg4,
+ myFloat arg5, unrestricted float arg6,
+ unrestricted double arg7, unrestricted float? arg8,
+ unrestricted double? arg9, myUnrestrictedFloat arg10);
+ [LenientFloat]
+ undefined m2(float arg1, double arg2, float? arg3, double? arg4,
+ myFloat arg5, unrestricted float arg6,
+ unrestricted double arg7, unrestricted float? arg8,
+ unrestricted double? arg9, myUnrestrictedFloat arg10);
+ [LenientFloat]
+ undefined m3(float arg);
+ [LenientFloat]
+ undefined m4(double arg);
+ [LenientFloat]
+ undefined m5((float or FloatTypes) arg);
+ [LenientFloat]
+ undefined m6(sequence<float> arg);
+ };
+ """
+ )
+
+ results = parser.finish()
+
+ harness.check(len(results), 3, "Should be two typedefs and one interface.")
+ iface = results[2]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should be an IDLInterface")
+ types = [a.type for a in iface.members if a.isAttr()]
+ harness.ok(types[0].isFloat(), "'float' is a float")
+ harness.ok(not types[0].isUnrestricted(), "'float' is not unrestricted")
+ harness.ok(types[1].isFloat(), "'unrestricted float' is a float")
+ harness.ok(types[1].isUnrestricted(), "'unrestricted float' is unrestricted")
+ harness.ok(types[2].isFloat(), "'double' is a float")
+ harness.ok(not types[2].isUnrestricted(), "'double' is not unrestricted")
+ harness.ok(types[3].isFloat(), "'unrestricted double' is a float")
+ harness.ok(types[3].isUnrestricted(), "'unrestricted double' is unrestricted")
+
+ method = iface.members[6]
+ harness.ok(isinstance(method, WebIDL.IDLMethod), "Should be an IDLMethod")
+ argtypes = [a.type for a in method.signatures()[0][1]]
+ for (idx, type) in enumerate(argtypes):
+ harness.ok(type.isFloat(), "Type %d should be float" % idx)
+ harness.check(
+ type.isUnrestricted(),
+ idx >= 5,
+ "Type %d should %sbe unrestricted" % (idx, "" if idx >= 4 else "not "),
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface FloatTypes {
+ [LenientFloat]
+ long m(float arg);
+ };
+ """
+ )
+ except Exception:
+ threw = True
+ harness.ok(threw, "[LenientFloat] only allowed on methods returning undefined")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface FloatTypes {
+ [LenientFloat]
+ undefined m(unrestricted float arg);
+ };
+ """
+ )
+ except Exception:
+ threw = True
+ harness.ok(
+ threw, "[LenientFloat] only allowed on methods with unrestricted float args"
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface FloatTypes {
+ [LenientFloat]
+ undefined m(sequence<unrestricted float> arg);
+ };
+ """
+ )
+ except Exception:
+ threw = True
+ harness.ok(
+ threw, "[LenientFloat] only allowed on methods with unrestricted float args (2)"
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface FloatTypes {
+ [LenientFloat]
+ undefined m((unrestricted float or FloatTypes) arg);
+ };
+ """
+ )
+ except Exception:
+ threw = True
+ harness.ok(
+ threw, "[LenientFloat] only allowed on methods with unrestricted float args (3)"
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface FloatTypes {
+ [LenientFloat]
+ readonly attribute float foo;
+ };
+ """
+ )
+ except Exception:
+ threw = True
+ harness.ok(threw, "[LenientFloat] only allowed on writable attributes")
diff --git a/dom/bindings/parser/tests/test_forward_decl.py b/dom/bindings/parser/tests/test_forward_decl.py
new file mode 100644
index 0000000000..1ba19e42fc
--- /dev/null
+++ b/dom/bindings/parser/tests/test_forward_decl.py
@@ -0,0 +1,15 @@
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ interface ForwardDeclared;
+ interface ForwardDeclared;
+
+ interface TestForwardDecl {
+ attribute ForwardDeclared foo;
+ };
+ """
+ )
+
+ parser.finish()
+
+ harness.ok(True, "TestForwardDeclared interface parsed without error.")
diff --git a/dom/bindings/parser/tests/test_global_extended_attr.py b/dom/bindings/parser/tests/test_global_extended_attr.py
new file mode 100644
index 0000000000..2de4aa68bd
--- /dev/null
+++ b/dom/bindings/parser/tests/test_global_extended_attr.py
@@ -0,0 +1,129 @@
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ [Global, Exposed=Foo]
+ interface Foo : Bar {
+ getter any(DOMString name);
+ };
+ [Exposed=Foo]
+ interface Bar {};
+ """
+ )
+
+ results = parser.finish()
+
+ harness.ok(
+ results[0].isOnGlobalProtoChain(),
+ "[Global] interface should be on global's proto chain",
+ )
+ harness.ok(
+ results[1].isOnGlobalProtoChain(),
+ "[Global] interface should be on global's proto chain",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [Global, Exposed=Foo]
+ interface Foo {
+ getter any(DOMString name);
+ setter undefined(DOMString name, any arg);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Should have thrown for [Global] used on an interface with a " "named setter",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [Global, Exposed=Foo]
+ interface Foo {
+ getter any(DOMString name);
+ deleter undefined(DOMString name);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Should have thrown for [Global] used on an interface with a " "named deleter",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [Global, LegacyOverrideBuiltIns, Exposed=Foo]
+ interface Foo {
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Should have thrown for [Global] used on an interface with a "
+ "[LegacyOverrideBuiltIns]",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [Global, Exposed=Foo]
+ interface Foo : Bar {
+ };
+ [LegacyOverrideBuiltIns, Exposed=Foo]
+ interface Bar {
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Should have thrown for [Global] used on an interface with an "
+ "[LegacyOverrideBuiltIns] ancestor",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [Global, Exposed=Foo]
+ interface Foo {
+ };
+ [Exposed=Foo]
+ interface Bar : Foo {
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Should have thrown for [Global] used on an interface with a " "descendant",
+ )
diff --git a/dom/bindings/parser/tests/test_identifier_conflict.py b/dom/bindings/parser/tests/test_identifier_conflict.py
new file mode 100644
index 0000000000..424f4d6285
--- /dev/null
+++ b/dom/bindings/parser/tests/test_identifier_conflict.py
@@ -0,0 +1,45 @@
+def WebIDLTest(parser, harness):
+ try:
+ parser.parse(
+ """
+ enum Foo { "a" };
+ interface Foo;
+ """
+ )
+ parser.finish()
+ harness.ok(False, "Should fail to parse")
+ except Exception as e:
+ harness.ok(
+ "Name collision" in str(e), "Should have name collision for interface"
+ )
+
+ parser = parser.reset()
+ try:
+ parser.parse(
+ """
+ dictionary Foo { long x; };
+ enum Foo { "a" };
+ """
+ )
+ parser.finish()
+ harness.ok(False, "Should fail to parse")
+ except Exception as e:
+ harness.ok(
+ "Name collision" in str(e), "Should have name collision for dictionary"
+ )
+
+ parser = parser.reset()
+ try:
+ parser.parse(
+ """
+ enum Foo { "a" };
+ enum Foo { "b" };
+ """
+ )
+ parser.finish()
+ harness.ok(False, "Should fail to parse")
+ except Exception as e:
+ harness.ok(
+ "Multiple unresolvable definitions" in str(e),
+ "Should have name collision for dictionary",
+ )
diff --git a/dom/bindings/parser/tests/test_incomplete_parent.py b/dom/bindings/parser/tests/test_incomplete_parent.py
new file mode 100644
index 0000000000..80662a7848
--- /dev/null
+++ b/dom/bindings/parser/tests/test_incomplete_parent.py
@@ -0,0 +1,18 @@
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ interface TestIncompleteParent : NotYetDefined {
+ undefined foo();
+ };
+
+ interface NotYetDefined : EvenHigherOnTheChain {
+ };
+
+ interface EvenHigherOnTheChain {
+ };
+ """
+ )
+
+ parser.finish()
+
+ harness.ok(True, "TestIncompleteParent interface parsed without error.")
diff --git a/dom/bindings/parser/tests/test_incomplete_types.py b/dom/bindings/parser/tests/test_incomplete_types.py
new file mode 100644
index 0000000000..0d54f708bb
--- /dev/null
+++ b/dom/bindings/parser/tests/test_incomplete_types.py
@@ -0,0 +1,61 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ interface TestIncompleteTypes {
+ attribute FooInterface attr1;
+
+ FooInterface method1(FooInterface arg);
+ };
+
+ interface FooInterface {
+ };
+ """
+ )
+
+ results = parser.finish()
+
+ harness.ok(True, "TestIncompleteTypes interface parsed without error.")
+ harness.check(len(results), 2, "Should be two productions.")
+ iface = results[0]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should be an IDLInterface")
+ harness.check(
+ iface.identifier.QName(),
+ "::TestIncompleteTypes",
+ "Interface has the right QName",
+ )
+ harness.check(
+ iface.identifier.name, "TestIncompleteTypes", "Interface has the right name"
+ )
+ harness.check(len(iface.members), 2, "Expect 2 members")
+
+ attr = iface.members[0]
+ harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Should be an IDLAttribute")
+ method = iface.members[1]
+ harness.ok(isinstance(method, WebIDL.IDLMethod), "Should be an IDLMethod")
+
+ harness.check(
+ attr.identifier.QName(),
+ "::TestIncompleteTypes::attr1",
+ "Attribute has the right QName",
+ )
+ harness.check(
+ attr.type.name, "FooInterface", "Previously unresolved type has the right name"
+ )
+
+ harness.check(
+ method.identifier.QName(),
+ "::TestIncompleteTypes::method1",
+ "Attribute has the right QName",
+ )
+ (returnType, args) = method.signatures()[0]
+ harness.check(
+ returnType.name, "FooInterface", "Previously unresolved type has the right name"
+ )
+ harness.check(
+ args[0].type.name,
+ "FooInterface",
+ "Previously unresolved type has the right name",
+ )
diff --git a/dom/bindings/parser/tests/test_interface.py b/dom/bindings/parser/tests/test_interface.py
new file mode 100644
index 0000000000..c078a593bb
--- /dev/null
+++ b/dom/bindings/parser/tests/test_interface.py
@@ -0,0 +1,459 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ parser.parse("interface Foo { };")
+ results = parser.finish()
+ harness.ok(True, "Empty interface parsed without error.")
+ harness.check(len(results), 1, "Should be one production")
+ harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface")
+ iface = results[0]
+ harness.check(iface.identifier.QName(), "::Foo", "Interface has the right QName")
+ harness.check(iface.identifier.name, "Foo", "Interface has the right name")
+ harness.check(iface.parent, None, "Interface has no parent")
+
+ parser.parse("interface Bar : Foo { };")
+ results = parser.finish()
+ harness.ok(True, "Empty interface parsed without error.")
+ harness.check(len(results), 2, "Should be two productions")
+ harness.ok(isinstance(results[1], WebIDL.IDLInterface), "Should be an IDLInterface")
+ iface = results[1]
+ harness.check(iface.identifier.QName(), "::Bar", "Interface has the right QName")
+ harness.check(iface.identifier.name, "Bar", "Interface has the right name")
+ harness.ok(isinstance(iface.parent, WebIDL.IDLInterface), "Interface has a parent")
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface QNameBase {
+ attribute long foo;
+ };
+
+ interface QNameDerived : QNameBase {
+ attribute long long foo;
+ attribute byte bar;
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(len(results), 2, "Should be two productions")
+ harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface")
+ harness.ok(isinstance(results[1], WebIDL.IDLInterface), "Should be an IDLInterface")
+ harness.check(results[1].parent, results[0], "Inheritance chain is right")
+ harness.check(len(results[0].members), 1, "Expect 1 productions")
+ harness.check(len(results[1].members), 2, "Expect 2 productions")
+ base = results[0]
+ derived = results[1]
+ harness.check(
+ base.members[0].identifier.QName(),
+ "::QNameBase::foo",
+ "Member has the right QName",
+ )
+ harness.check(
+ derived.members[0].identifier.QName(),
+ "::QNameDerived::foo",
+ "Member has the right QName",
+ )
+ harness.check(
+ derived.members[1].identifier.QName(),
+ "::QNameDerived::bar",
+ "Member has the right QName",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A : B {};
+ interface B : A {};
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should not allow cycles in interface inheritance chains")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A : C {};
+ interface C : B {};
+ interface B : A {};
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw, "Should not allow indirect cycles in interface inheritance chains"
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A;
+ interface B : A {};
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Should not allow inheriting from an interface that is only forward declared",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface A {
+ constructor();
+ constructor(long arg);
+ readonly attribute boolean x;
+ undefined foo();
+ };
+ partial interface A {
+ readonly attribute boolean y;
+ undefined foo(long arg);
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(len(results), 2, "Should have two results with partial interface")
+ iface = results[0]
+ harness.check(
+ len(iface.members), 3, "Should have three members with partial interface"
+ )
+ harness.check(
+ iface.members[0].identifier.name,
+ "x",
+ "First member should be x with partial interface",
+ )
+ harness.check(
+ iface.members[1].identifier.name,
+ "foo",
+ "Second member should be foo with partial interface",
+ )
+ harness.check(
+ len(iface.members[1].signatures()),
+ 2,
+ "Should have two foo signatures with partial interface",
+ )
+ harness.check(
+ iface.members[2].identifier.name,
+ "y",
+ "Third member should be y with partial interface",
+ )
+ harness.check(
+ len(iface.ctor().signatures()),
+ 2,
+ "Should have two constructors with partial interface",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ partial interface A {
+ readonly attribute boolean y;
+ undefined foo(long arg);
+ };
+ interface A {
+ constructor();
+ constructor(long arg);
+ readonly attribute boolean x;
+ undefined foo();
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(
+ len(results), 2, "Should have two results with reversed partial interface"
+ )
+ iface = results[1]
+ harness.check(
+ len(iface.members),
+ 3,
+ "Should have three members with reversed partial interface",
+ )
+ harness.check(
+ iface.members[0].identifier.name,
+ "x",
+ "First member should be x with reversed partial interface",
+ )
+ harness.check(
+ iface.members[1].identifier.name,
+ "foo",
+ "Second member should be foo with reversed partial interface",
+ )
+ harness.check(
+ len(iface.members[1].signatures()),
+ 2,
+ "Should have two foo signatures with reversed partial interface",
+ )
+ harness.check(
+ iface.members[2].identifier.name,
+ "y",
+ "Third member should be y with reversed partial interface",
+ )
+ harness.check(
+ len(iface.ctor().signatures()),
+ 2,
+ "Should have two constructors with reversed partial interface",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ readonly attribute boolean x;
+ };
+ interface A {
+ readonly attribute boolean y;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow two non-partial interfaces with the same name")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ partial interface A {
+ readonly attribute boolean x;
+ };
+ partial interface A {
+ readonly attribute boolean y;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Must have a non-partial interface for a given name")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ boolean x;
+ };
+ partial interface A {
+ readonly attribute boolean y;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw,
+ "Should not allow a name collision between partial interface "
+ "and other object",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ boolean x;
+ };
+ interface A {
+ readonly attribute boolean y;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw, "Should not allow a name collision between interface " "and other object"
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ boolean x;
+ };
+ interface A;
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw,
+ "Should not allow a name collision between external interface "
+ "and other object",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ readonly attribute boolean x;
+ };
+ interface A;
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw,
+ "Should not allow a name collision between external interface " "and interface",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface A;
+ interface A;
+ """
+ )
+ results = parser.finish()
+ harness.ok(
+ len(results) == 1 and isinstance(results[0], WebIDL.IDLExternalInterface),
+ "Should allow name collisions between external interface " "declarations",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [SomeRandomAnnotation]
+ interface A {
+ readonly attribute boolean y;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow unknown extended attributes on interfaces")
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ [Global, Exposed=Window] interface Window {};
+ [Exposed=Window, LegacyWindowAlias=A]
+ interface B {};
+ [Exposed=Window, LegacyWindowAlias=(C, D)]
+ interface E {};
+ """
+ )
+ results = parser.finish()
+ harness.check(
+ results[1].legacyWindowAliases, ["A"], "Should support a single identifier"
+ )
+ harness.check(
+ results[2].legacyWindowAliases, ["C", "D"], "Should support an identifier list"
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [LegacyWindowAlias]
+ interface A {};
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow [LegacyWindowAlias] with no value")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [Exposed=Worker, LegacyWindowAlias=B]
+ interface A {};
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow [LegacyWindowAlias] without Window exposure")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [Global, Exposed=Window] interface Window {};
+ [Exposed=Window]
+ interface A {};
+ [Exposed=Window, LegacyWindowAlias=A]
+ interface B {};
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw, "Should not allow [LegacyWindowAlias] to conflict with other identifiers"
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [Global, Exposed=Window] interface Window {};
+ [Exposed=Window, LegacyWindowAlias=A]
+ interface B {};
+ [Exposed=Window]
+ interface A {};
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw, "Should not allow [LegacyWindowAlias] to conflict with other identifiers"
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [Global, Exposed=Window] interface Window {};
+ [Exposed=Window, LegacyWindowAlias=A]
+ interface B {};
+ [Exposed=Window, LegacyWindowAlias=A]
+ interface C {};
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw, "Should not allow [LegacyWindowAlias] to conflict with other identifiers"
+ )
diff --git a/dom/bindings/parser/tests/test_interface_const_identifier_conflicts.py b/dom/bindings/parser/tests/test_interface_const_identifier_conflicts.py
new file mode 100644
index 0000000000..6388af2139
--- /dev/null
+++ b/dom/bindings/parser/tests/test_interface_const_identifier_conflicts.py
@@ -0,0 +1,17 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface IdentifierConflict {
+ const byte thing1 = 1;
+ const unsigned long thing1 = 1;
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/dom/bindings/parser/tests/test_interface_identifier_conflicts_across_members.py b/dom/bindings/parser/tests/test_interface_identifier_conflicts_across_members.py
new file mode 100644
index 0000000000..d8398d46ba
--- /dev/null
+++ b/dom/bindings/parser/tests/test_interface_identifier_conflicts_across_members.py
@@ -0,0 +1,168 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface IdentifierConflictAcrossMembers1 {
+ const byte thing1 = 1;
+ readonly attribute long thing1;
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers1.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface IdentifierConflictAcrossMembers2 {
+ readonly attribute long thing1;
+ const byte thing1 = 1;
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers2.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface IdentifierConflictAcrossMembers3 {
+ getter boolean thing1(DOMString name);
+ readonly attribute long thing1;
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers3.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface IdentifierConflictAcrossMembers4 {
+ const byte thing1 = 1;
+ long thing1();
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers4.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface IdentifierConflictAcrossMembers5 {
+ static long thing1();
+ undefined thing1();
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ not threw, "Should not have thrown for IdentifierConflictAcrossMembers5."
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface mixin IdentifierConflictAcrossMembers6Mixin {
+ undefined thing1();
+ };
+ interface IdentifierConflictAcrossMembers6 {
+ static long thing1();
+ };
+ IdentifierConflictAcrossMembers6 includes IdentifierConflictAcrossMembers6Mixin;
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ not threw, "Should not have thrown for IdentifierConflictAcrossMembers6."
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface IdentifierConflictAcrossMembers7 {
+ const byte thing1 = 1;
+ static readonly attribute long thing1;
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers7.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface IdentifierConflictAcrossMembers8 {
+ readonly attribute long thing1 = 1;
+ static readonly attribute long thing1;
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers8.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface IdentifierConflictAcrossMembers9 {
+ void thing1();
+ static readonly attribute long thing1;
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers9.")
diff --git a/dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py b/dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py
new file mode 100644
index 0000000000..fdd9c00965
--- /dev/null
+++ b/dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py
@@ -0,0 +1,935 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ def shouldPass(prefix, iface, expectedMembers, numProductions=1):
+ p = parser.reset()
+ p.parse(iface)
+ results = p.finish()
+ harness.check(
+ len(results),
+ numProductions,
+ "%s - Should have production count %d" % (prefix, numProductions),
+ )
+ harness.ok(
+ isinstance(results[0], WebIDL.IDLInterface),
+ "%s - Should be an IDLInterface" % (prefix),
+ )
+ # Make a copy, since we plan to modify it
+ expectedMembers = list(expectedMembers)
+ for m in results[0].members:
+ name = m.identifier.name
+ if m.isMethod() and m.isStatic():
+ # None of the expected members are static methods, so ignore those.
+ harness.ok(True, "%s - %s - Should be a %s" % (prefix, name, type(m)))
+ elif (name, type(m)) in expectedMembers:
+ harness.ok(True, "%s - %s - Should be a %s" % (prefix, name, type(m)))
+ expectedMembers.remove((name, type(m)))
+ else:
+ harness.ok(
+ False,
+ "%s - %s - Unknown symbol of type %s" % (prefix, name, type(m)),
+ )
+ # A bit of a hoop because we can't generate the error string if we pass
+ if len(expectedMembers) == 0:
+ harness.ok(True, "Found all the members")
+ else:
+ harness.ok(
+ False,
+ "Expected member not found: %s of type %s"
+ % (expectedMembers[0][0], expectedMembers[0][1]),
+ )
+ return results
+
+ def shouldFail(prefix, iface):
+ try:
+ p = parser.reset()
+ p.parse(iface)
+ p.finish()
+ harness.ok(False, prefix + " - Interface passed when should've failed")
+ except WebIDL.WebIDLError:
+ harness.ok(True, prefix + " - Interface failed as expected")
+ except Exception as e:
+ harness.ok(
+ False,
+ prefix
+ + " - Interface failed but not as a WebIDLError exception: %s" % e,
+ )
+
+ iterableMembers = [
+ (x, WebIDL.IDLMethod) for x in ["entries", "keys", "values", "forEach"]
+ ]
+ setROMembers = (
+ [(x, WebIDL.IDLMethod) for x in ["has"]]
+ + [("__setlike", WebIDL.IDLMaplikeOrSetlike)]
+ + iterableMembers
+ )
+ setROMembers.extend([("size", WebIDL.IDLAttribute)])
+ setRWMembers = [
+ (x, WebIDL.IDLMethod) for x in ["add", "clear", "delete"]
+ ] + setROMembers
+ mapROMembers = (
+ [(x, WebIDL.IDLMethod) for x in ["get", "has"]]
+ + [("__maplike", WebIDL.IDLMaplikeOrSetlike)]
+ + iterableMembers
+ )
+ mapROMembers.extend([("size", WebIDL.IDLAttribute)])
+ mapRWMembers = [
+ (x, WebIDL.IDLMethod) for x in ["set", "clear", "delete"]
+ ] + mapROMembers
+
+ # OK, now that we've used iterableMembers to set up the above, append
+ # __iterable to it for the iterable<> case.
+ iterableMembers.append(("__iterable", WebIDL.IDLIterable))
+
+ asyncIterableMembers = [
+ (x, WebIDL.IDLMethod) for x in ["entries", "keys", "values"]
+ ]
+ asyncIterableMembers.append(("__iterable", WebIDL.IDLAsyncIterable))
+
+ valueIterableMembers = [("__iterable", WebIDL.IDLIterable)]
+ valueIterableMembers.append(("__indexedgetter", WebIDL.IDLMethod))
+ valueIterableMembers.append(("length", WebIDL.IDLAttribute))
+
+ valueAsyncIterableMembers = [("__iterable", WebIDL.IDLAsyncIterable)]
+ valueAsyncIterableMembers.append(("values", WebIDL.IDLMethod))
+
+ disallowedIterableNames = [
+ ("keys", WebIDL.IDLMethod),
+ ("entries", WebIDL.IDLMethod),
+ ("values", WebIDL.IDLMethod),
+ ]
+ disallowedMemberNames = [
+ ("forEach", WebIDL.IDLMethod),
+ ("has", WebIDL.IDLMethod),
+ ("size", WebIDL.IDLAttribute),
+ ] + disallowedIterableNames
+ mapDisallowedMemberNames = [("get", WebIDL.IDLMethod)] + disallowedMemberNames
+ disallowedNonMethodNames = [
+ ("clear", WebIDL.IDLMethod),
+ ("delete", WebIDL.IDLMethod),
+ ]
+ mapDisallowedNonMethodNames = [("set", WebIDL.IDLMethod)] + disallowedNonMethodNames
+ setDisallowedNonMethodNames = [("add", WebIDL.IDLMethod)] + disallowedNonMethodNames
+ unrelatedMembers = [
+ ("unrelatedAttribute", WebIDL.IDLAttribute),
+ ("unrelatedMethod", WebIDL.IDLMethod),
+ ]
+
+ #
+ # Simple Usage Tests
+ #
+
+ shouldPass(
+ "Iterable (key only)",
+ """
+ interface Foo1 {
+ iterable<long>;
+ readonly attribute unsigned long length;
+ getter long(unsigned long index);
+ attribute long unrelatedAttribute;
+ long unrelatedMethod();
+ };
+ """,
+ valueIterableMembers + unrelatedMembers,
+ )
+
+ shouldPass(
+ "Iterable (key only) inheriting from parent",
+ """
+ interface Foo1 : Foo2 {
+ iterable<long>;
+ readonly attribute unsigned long length;
+ getter long(unsigned long index);
+ };
+ interface Foo2 {
+ attribute long unrelatedAttribute;
+ long unrelatedMethod();
+ };
+ """,
+ valueIterableMembers,
+ numProductions=2,
+ )
+
+ shouldPass(
+ "Iterable (key and value)",
+ """
+ interface Foo1 {
+ iterable<long, long>;
+ attribute long unrelatedAttribute;
+ long unrelatedMethod();
+ };
+ """,
+ iterableMembers + unrelatedMembers,
+ # numProductions == 2 because of the generated iterator iface,
+ numProductions=2,
+ )
+
+ shouldPass(
+ "Iterable (key and value) inheriting from parent",
+ """
+ interface Foo1 : Foo2 {
+ iterable<long, long>;
+ };
+ interface Foo2 {
+ attribute long unrelatedAttribute;
+ long unrelatedMethod();
+ };
+ """,
+ iterableMembers,
+ # numProductions == 3 because of the generated iterator iface,
+ numProductions=3,
+ )
+
+ shouldPass(
+ "Async iterable (key only)",
+ """
+ interface Foo1 {
+ async iterable<long>;
+ attribute long unrelatedAttribute;
+ long unrelatedMethod();
+ };
+ """,
+ valueAsyncIterableMembers + unrelatedMembers,
+ # numProductions == 2 because of the generated iterator iface,
+ numProductions=2,
+ )
+
+ shouldPass(
+ "Async iterable (key only) inheriting from parent",
+ """
+ interface Foo1 : Foo2 {
+ async iterable<long>;
+ };
+ interface Foo2 {
+ attribute long unrelatedAttribute;
+ long unrelatedMethod();
+ };
+ """,
+ valueAsyncIterableMembers,
+ # numProductions == 3 because of the generated iterator iface,
+ numProductions=3,
+ )
+
+ shouldPass(
+ "Async iterable with argument (key only)",
+ """
+ interface Foo1 {
+ async iterable<long>(optional long foo);
+ attribute long unrelatedAttribute;
+ long unrelatedMethod();
+ };
+ """,
+ valueAsyncIterableMembers + unrelatedMembers,
+ # numProductions == 2 because of the generated iterator iface,
+ numProductions=2,
+ )
+
+ shouldPass(
+ "Async iterable (key and value)",
+ """
+ interface Foo1 {
+ async iterable<long, long>;
+ attribute long unrelatedAttribute;
+ long unrelatedMethod();
+ };
+ """,
+ asyncIterableMembers + unrelatedMembers,
+ # numProductions == 2 because of the generated iterator iface,
+ numProductions=2,
+ )
+
+ shouldPass(
+ "Async iterable (key and value) inheriting from parent",
+ """
+ interface Foo1 : Foo2 {
+ async iterable<long, long>;
+ };
+ interface Foo2 {
+ attribute long unrelatedAttribute;
+ long unrelatedMethod();
+ };
+ """,
+ asyncIterableMembers,
+ # numProductions == 3 because of the generated iterator iface,
+ numProductions=3,
+ )
+
+ shouldPass(
+ "Async iterable with argument (key and value)",
+ """
+ interface Foo1 {
+ async iterable<long, long>(optional long foo);
+ attribute long unrelatedAttribute;
+ long unrelatedMethod();
+ };
+ """,
+ asyncIterableMembers + unrelatedMembers,
+ # numProductions == 2 because of the generated iterator iface,
+ numProductions=2,
+ )
+
+ shouldPass(
+ "Maplike (readwrite)",
+ """
+ interface Foo1 {
+ maplike<long, long>;
+ attribute long unrelatedAttribute;
+ long unrelatedMethod();
+ };
+ """,
+ mapRWMembers + unrelatedMembers,
+ )
+
+ shouldPass(
+ "Maplike (readwrite) inheriting from parent",
+ """
+ interface Foo1 : Foo2 {
+ maplike<long, long>;
+ };
+ interface Foo2 {
+ attribute long unrelatedAttribute;
+ long unrelatedMethod();
+ };
+ """,
+ mapRWMembers,
+ numProductions=2,
+ )
+
+ shouldPass(
+ "Maplike (readwrite)",
+ """
+ interface Foo1 {
+ maplike<long, long>;
+ attribute long unrelatedAttribute;
+ long unrelatedMethod();
+ };
+ """,
+ mapRWMembers + unrelatedMembers,
+ )
+
+ shouldPass(
+ "Maplike (readwrite) inheriting from parent",
+ """
+ interface Foo1 : Foo2 {
+ maplike<long, long>;
+ };
+ interface Foo2 {
+ attribute long unrelatedAttribute;
+ long unrelatedMethod();
+ };
+ """,
+ mapRWMembers,
+ numProductions=2,
+ )
+
+ shouldPass(
+ "Maplike (readonly)",
+ """
+ interface Foo1 {
+ readonly maplike<long, long>;
+ attribute long unrelatedAttribute;
+ long unrelatedMethod();
+ };
+ """,
+ mapROMembers + unrelatedMembers,
+ )
+
+ shouldPass(
+ "Maplike (readonly) inheriting from parent",
+ """
+ interface Foo1 : Foo2 {
+ readonly maplike<long, long>;
+ };
+ interface Foo2 {
+ attribute long unrelatedAttribute;
+ long unrelatedMethod();
+ };
+ """,
+ mapROMembers,
+ numProductions=2,
+ )
+
+ shouldPass(
+ "Setlike (readwrite)",
+ """
+ interface Foo1 {
+ setlike<long>;
+ attribute long unrelatedAttribute;
+ long unrelatedMethod();
+ };
+ """,
+ setRWMembers + unrelatedMembers,
+ )
+
+ shouldPass(
+ "Setlike (readwrite) inheriting from parent",
+ """
+ interface Foo1 : Foo2 {
+ setlike<long>;
+ };
+ interface Foo2 {
+ attribute long unrelatedAttribute;
+ long unrelatedMethod();
+ };
+ """,
+ setRWMembers,
+ numProductions=2,
+ )
+
+ shouldPass(
+ "Setlike (readonly)",
+ """
+ interface Foo1 {
+ readonly setlike<long>;
+ attribute long unrelatedAttribute;
+ long unrelatedMethod();
+ };
+ """,
+ setROMembers + unrelatedMembers,
+ )
+
+ shouldPass(
+ "Setlike (readonly) inheriting from parent",
+ """
+ interface Foo1 : Foo2 {
+ readonly setlike<long>;
+ };
+ interface Foo2 {
+ attribute long unrelatedAttribute;
+ long unrelatedMethod();
+ };
+ """,
+ setROMembers,
+ numProductions=2,
+ )
+
+ shouldPass(
+ "Inheritance of maplike/setlike",
+ """
+ interface Foo1 {
+ maplike<long, long>;
+ };
+ interface Foo2 : Foo1 {
+ };
+ """,
+ mapRWMembers,
+ numProductions=2,
+ )
+
+ shouldFail(
+ "JS Implemented maplike interface",
+ """
+ [JSImplementation="@mozilla.org/dom/test-interface-js-maplike;1"]
+ interface Foo1 {
+ constructor();
+ setlike<long>;
+ };
+ """,
+ )
+
+ shouldFail(
+ "JS Implemented maplike interface",
+ """
+ [JSImplementation="@mozilla.org/dom/test-interface-js-maplike;1"]
+ interface Foo1 {
+ constructor();
+ maplike<long, long>;
+ };
+ """,
+ )
+
+ #
+ # Multiple maplike/setlike tests
+ #
+
+ shouldFail(
+ "Two maplike/setlikes on same interface",
+ """
+ interface Foo1 {
+ setlike<long>;
+ maplike<long, long>;
+ };
+ """,
+ )
+
+ shouldFail(
+ "Two iterable/setlikes on same interface",
+ """
+ interface Foo1 {
+ iterable<long>;
+ maplike<long, long>;
+ };
+ """,
+ )
+
+ shouldFail(
+ "Two iterables on same interface",
+ """
+ interface Foo1 {
+ iterable<long>;
+ iterable<long, long>;
+ };
+ """,
+ )
+
+ shouldFail(
+ "Two iterables on same interface",
+ """
+ interface Foo1 {
+ iterable<long>;
+ async iterable<long>;
+ };
+ """,
+ )
+
+ shouldFail(
+ "Two iterables on same interface",
+ """
+ interface Foo1 {
+ async iterable<long>;
+ async iterable<long, long>;
+ };
+ """,
+ )
+
+ shouldFail(
+ "Async iterable with non-optional arguments",
+ """
+ interface Foo1 {
+ async iterable<long>(long foo);
+ };
+ """,
+ )
+
+ shouldFail(
+ "Async iterable with non-optional arguments",
+ """
+ interface Foo1 {
+ async iterable<long>(optional long foo, long bar);
+ };
+ """,
+ )
+
+ shouldFail(
+ "Async iterable with non-optional arguments",
+ """
+ interface Foo1 {
+ async iterable<long, long>(long foo);
+ };
+ """,
+ )
+
+ shouldFail(
+ "Two maplike/setlikes in partials",
+ """
+ interface Foo1 {
+ maplike<long, long>;
+ };
+ partial interface Foo1 {
+ setlike<long>;
+ };
+ """,
+ )
+
+ shouldFail(
+ "Conflicting maplike/setlikes across inheritance",
+ """
+ interface Foo1 {
+ maplike<long, long>;
+ };
+ interface Foo2 : Foo1 {
+ setlike<long>;
+ };
+ """,
+ )
+
+ shouldFail(
+ "Conflicting maplike/iterable across inheritance",
+ """
+ interface Foo1 {
+ maplike<long, long>;
+ };
+ interface Foo2 : Foo1 {
+ iterable<long>;
+ };
+ """,
+ )
+
+ shouldFail(
+ "Conflicting maplike/setlikes across multistep inheritance",
+ """
+ interface Foo1 {
+ maplike<long, long>;
+ };
+ interface Foo2 : Foo1 {
+ };
+ interface Foo3 : Foo2 {
+ setlike<long>;
+ };
+ """,
+ )
+
+ #
+ # Member name collision tests
+ #
+
+ def testConflictingMembers(
+ likeMember, conflict, expectedMembers, methodPasses, numProductions=1
+ ):
+ """
+ Tests for maplike/setlike member generation against conflicting member
+ names. If methodPasses is True, this means we expect the interface to
+ pass in the case of method shadowing, and expectedMembers should be the
+ list of interface members to check against on the passing interface.
+
+ """
+ (conflictName, conflictType) = conflict
+ if methodPasses:
+ shouldPass(
+ "Conflicting method: %s and %s" % (likeMember, conflictName),
+ """
+ interface Foo1 {
+ %s;
+ [Throws]
+ undefined %s(long test1, double test2, double test3);
+ };
+ """
+ % (likeMember, conflictName),
+ expectedMembers,
+ )
+ else:
+ shouldFail(
+ "Conflicting method: %s and %s" % (likeMember, conflictName),
+ """
+ interface Foo1 {
+ %s;
+ [Throws]
+ undefined %s(long test1, double test2, double test3);
+ };
+ """
+ % (likeMember, conflictName),
+ )
+ # Inherited conflicting methods should ALWAYS fail
+ shouldFail(
+ "Conflicting inherited method: %s and %s" % (likeMember, conflictName),
+ """
+ interface Foo1 {
+ undefined %s(long test1, double test2, double test3);
+ };
+ interface Foo2 : Foo1 {
+ %s;
+ };
+ """
+ % (conflictName, likeMember),
+ )
+ if conflictType == WebIDL.IDLAttribute:
+ shouldFail(
+ "Conflicting static method: %s and %s" % (likeMember, conflictName),
+ """
+ interface Foo1 {
+ %s;
+ static undefined %s(long test1, double test2, double test3);
+ };
+ """
+ % (likeMember, conflictName),
+ )
+ else:
+ shouldPass(
+ "Conflicting static method: %s and %s" % (likeMember, conflictName),
+ """
+ interface Foo1 {
+ %s;
+ static undefined %s(long test1, double test2, double test3);
+ };
+ """
+ % (likeMember, conflictName),
+ expectedMembers,
+ numProductions=numProductions,
+ )
+ shouldFail(
+ "Conflicting attribute: %s and %s" % (likeMember, conflictName),
+ """
+ interface Foo1 {
+ %s
+ attribute double %s;
+ };
+ """
+ % (likeMember, conflictName),
+ )
+ shouldFail(
+ "Conflicting const: %s and %s" % (likeMember, conflictName),
+ """
+ interface Foo1 {
+ %s;
+ const double %s = 0;
+ };
+ """
+ % (likeMember, conflictName),
+ )
+ shouldFail(
+ "Conflicting static attribute: %s and %s" % (likeMember, conflictName),
+ """
+ interface Foo1 {
+ %s;
+ static attribute long %s;
+ };
+ """
+ % (likeMember, conflictName),
+ )
+
+ for member in disallowedIterableNames:
+ testConflictingMembers(
+ "iterable<long, long>", member, iterableMembers, False, numProductions=2
+ )
+ for member in mapDisallowedMemberNames:
+ testConflictingMembers("maplike<long, long>", member, mapRWMembers, False)
+ for member in disallowedMemberNames:
+ testConflictingMembers("setlike<long>", member, setRWMembers, False)
+ for member in mapDisallowedNonMethodNames:
+ testConflictingMembers("maplike<long, long>", member, mapRWMembers, True)
+ for member in setDisallowedNonMethodNames:
+ testConflictingMembers("setlike<long>", member, setRWMembers, True)
+
+ shouldPass(
+ "Inheritance of maplike/setlike with child member collision",
+ """
+ interface Foo1 {
+ maplike<long, long>;
+ };
+ interface Foo2 : Foo1 {
+ undefined entries();
+ };
+ """,
+ mapRWMembers,
+ numProductions=2,
+ )
+
+ shouldPass(
+ "Inheritance of multi-level maplike/setlike with child member collision",
+ """
+ interface Foo1 {
+ maplike<long, long>;
+ };
+ interface Foo2 : Foo1 {
+ };
+ interface Foo3 : Foo2 {
+ undefined entries();
+ };
+ """,
+ mapRWMembers,
+ numProductions=3,
+ )
+
+ shouldFail(
+ "Maplike interface with mixin member collision",
+ """
+ interface Foo1 {
+ maplike<long, long>;
+ };
+ interface mixin Foo2 {
+ undefined entries();
+ };
+ Foo1 includes Foo2;
+ """,
+ )
+
+ shouldPass(
+ "Inherited Maplike interface with consequential interface member collision",
+ """
+ interface Foo1 {
+ maplike<long, long>;
+ };
+ interface mixin Foo2 {
+ undefined entries();
+ };
+ interface Foo3 : Foo1 {
+ };
+ Foo3 includes Foo2;
+ """,
+ mapRWMembers,
+ numProductions=4,
+ )
+
+ shouldFail(
+ "Inheritance of name collision with child maplike/setlike",
+ """
+ interface Foo1 {
+ undefined entries();
+ };
+ interface Foo2 : Foo1 {
+ maplike<long, long>;
+ };
+ """,
+ )
+
+ shouldFail(
+ "Inheritance of multi-level name collision with child maplike/setlike",
+ """
+ interface Foo1 {
+ undefined entries();
+ };
+ interface Foo2 : Foo1 {
+ };
+ interface Foo3 : Foo2 {
+ maplike<long, long>;
+ };
+ """,
+ )
+
+ shouldPass(
+ "Inheritance of attribute collision with parent maplike/setlike",
+ """
+ interface Foo1 {
+ maplike<long, long>;
+ };
+ interface Foo2 : Foo1 {
+ attribute double size;
+ };
+ """,
+ mapRWMembers,
+ numProductions=2,
+ )
+
+ shouldPass(
+ "Inheritance of multi-level attribute collision with parent maplike/setlike",
+ """
+ interface Foo1 {
+ maplike<long, long>;
+ };
+ interface Foo2 : Foo1 {
+ };
+ interface Foo3 : Foo2 {
+ attribute double size;
+ };
+ """,
+ mapRWMembers,
+ numProductions=3,
+ )
+
+ shouldFail(
+ "Inheritance of attribute collision with child maplike/setlike",
+ """
+ interface Foo1 {
+ attribute double size;
+ };
+ interface Foo2 : Foo1 {
+ maplike<long, long>;
+ };
+ """,
+ )
+
+ shouldFail(
+ "Inheritance of multi-level attribute collision with child maplike/setlike",
+ """
+ interface Foo1 {
+ attribute double size;
+ };
+ interface Foo2 : Foo1 {
+ };
+ interface Foo3 : Foo2 {
+ maplike<long, long>;
+ };
+ """,
+ )
+
+ shouldFail(
+ "Inheritance of attribute/rw function collision with child maplike/setlike",
+ """
+ interface Foo1 {
+ attribute double set;
+ };
+ interface Foo2 : Foo1 {
+ maplike<long, long>;
+ };
+ """,
+ )
+
+ shouldFail(
+ "Inheritance of const/rw function collision with child maplike/setlike",
+ """
+ interface Foo1 {
+ const double set = 0;
+ };
+ interface Foo2 : Foo1 {
+ maplike<long, long>;
+ };
+ """,
+ )
+
+ shouldPass(
+ "Inheritance of rw function with same name in child maplike/setlike",
+ """
+ interface Foo1 {
+ maplike<long, long>;
+ };
+ interface Foo2 : Foo1 {
+ undefined clear();
+ };
+ """,
+ mapRWMembers,
+ numProductions=2,
+ )
+
+ shouldFail(
+ "Inheritance of unforgeable attribute collision with child maplike/setlike",
+ """
+ interface Foo1 {
+ [LegacyUnforgeable]
+ attribute double size;
+ };
+ interface Foo2 : Foo1 {
+ maplike<long, long>;
+ };
+ """,
+ )
+
+ shouldFail(
+ "Inheritance of multi-level unforgeable attribute collision with child maplike/setlike",
+ """
+ interface Foo1 {
+ [LegacyUnforgeable]
+ attribute double size;
+ };
+ interface Foo2 : Foo1 {
+ };
+ interface Foo3 : Foo2 {
+ maplike<long, long>;
+ };
+ """,
+ )
+
+ shouldPass(
+ "Interface with readonly allowable overrides",
+ """
+ interface Foo1 {
+ readonly setlike<long>;
+ readonly attribute boolean clear;
+ };
+ """,
+ setROMembers + [("clear", WebIDL.IDLAttribute)],
+ )
+
+ r = shouldPass(
+ "Check proper override of clear/delete/set",
+ """
+ interface Foo1 {
+ maplike<long, long>;
+ long clear(long a, long b, double c, double d);
+ long set(long a, long b, double c, double d);
+ long delete(long a, long b, double c, double d);
+ };
+ """,
+ mapRWMembers,
+ )
+
+ for m in r[0].members:
+ if m.identifier.name in ["clear", "set", "delete"]:
+ harness.ok(m.isMethod(), "%s should be a method" % m.identifier.name)
+ harness.check(
+ m.maxArgCount, 4, "%s should have 4 arguments" % m.identifier.name
+ )
+ harness.ok(
+ not m.isMaplikeOrSetlikeOrIterableMethod(),
+ "%s should not be a maplike/setlike function" % m.identifier.name,
+ )
diff --git a/dom/bindings/parser/tests/test_interfacemixin.py b/dom/bindings/parser/tests/test_interfacemixin.py
new file mode 100644
index 0000000000..4723991937
--- /dev/null
+++ b/dom/bindings/parser/tests/test_interfacemixin.py
@@ -0,0 +1,534 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ parser.parse("interface mixin Foo { };")
+ results = parser.finish()
+ harness.ok(True, "Empty interface mixin parsed without error.")
+ harness.check(len(results), 1, "Should be one production")
+ harness.ok(
+ isinstance(results[0], WebIDL.IDLInterfaceMixin),
+ "Should be an IDLInterfaceMixin",
+ )
+ mixin = results[0]
+ harness.check(
+ mixin.identifier.QName(), "::Foo", "Interface mixin has the right QName"
+ )
+ harness.check(mixin.identifier.name, "Foo", "Interface mixin has the right name")
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface mixin QNameBase {
+ const long foo = 3;
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(len(results), 1, "Should be one productions")
+ harness.ok(
+ isinstance(results[0], WebIDL.IDLInterfaceMixin),
+ "Should be an IDLInterfaceMixin",
+ )
+ harness.check(len(results[0].members), 1, "Expect 1 productions")
+ mixin = results[0]
+ harness.check(
+ mixin.members[0].identifier.QName(),
+ "::QNameBase::foo",
+ "Member has the right QName",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface mixin A {
+ readonly attribute boolean x;
+ undefined foo();
+ };
+ partial interface mixin A {
+ readonly attribute boolean y;
+ undefined foo(long arg);
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(
+ len(results), 2, "Should have two results with partial interface mixin"
+ )
+ mixin = results[0]
+ harness.check(
+ len(mixin.members), 3, "Should have three members with partial interface mixin"
+ )
+ harness.check(
+ mixin.members[0].identifier.name,
+ "x",
+ "First member should be x with partial interface mixin",
+ )
+ harness.check(
+ mixin.members[1].identifier.name,
+ "foo",
+ "Second member should be foo with partial interface mixin",
+ )
+ harness.check(
+ len(mixin.members[1].signatures()),
+ 2,
+ "Should have two foo signatures with partial interface mixin",
+ )
+ harness.check(
+ mixin.members[2].identifier.name,
+ "y",
+ "Third member should be y with partial interface mixin",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ partial interface mixin A {
+ readonly attribute boolean y;
+ undefined foo(long arg);
+ };
+ interface mixin A {
+ readonly attribute boolean x;
+ undefined foo();
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(
+ len(results), 2, "Should have two results with reversed partial interface mixin"
+ )
+ mixin = results[1]
+ harness.check(
+ len(mixin.members),
+ 3,
+ "Should have three members with reversed partial interface mixin",
+ )
+ harness.check(
+ mixin.members[0].identifier.name,
+ "x",
+ "First member should be x with reversed partial interface mixin",
+ )
+ harness.check(
+ mixin.members[1].identifier.name,
+ "foo",
+ "Second member should be foo with reversed partial interface mixin",
+ )
+ harness.check(
+ len(mixin.members[1].signatures()),
+ 2,
+ "Should have two foo signatures with reversed partial interface mixin",
+ )
+ harness.check(
+ mixin.members[2].identifier.name,
+ "y",
+ "Third member should be y with reversed partial interface mixin",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface Interface {};
+ interface mixin Mixin {
+ attribute short x;
+ };
+ Interface includes Mixin;
+ """
+ )
+ results = parser.finish()
+ iface = results[0]
+ harness.check(len(iface.members), 1, "Should merge members from mixins")
+ harness.check(
+ iface.members[0].identifier.name, "x", "Should merge members from mixins"
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface mixin A {
+ readonly attribute boolean x;
+ };
+ interface mixin A {
+ readonly attribute boolean y;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw, "Should not allow two non-partial interface mixins with the same name"
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ partial interface mixin A {
+ readonly attribute boolean x;
+ };
+ partial interface mixin A {
+ readonly attribute boolean y;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Must have a non-partial interface mixin for a given name")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ boolean x;
+ };
+ partial interface mixin A {
+ readonly attribute boolean y;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw,
+ "Should not allow a name collision between partial interface "
+ "mixin and other object",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary A {
+ boolean x;
+ };
+ interface mixin A {
+ readonly attribute boolean y;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw,
+ "Should not allow a name collision between interface mixin " "and other object",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface mixin A {
+ readonly attribute boolean x;
+ };
+ interface A;
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw,
+ "Should not allow a name collision between external interface "
+ "and interface mixin",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [SomeRandomAnnotation]
+ interface mixin A {
+ readonly attribute boolean y;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw, "Should not allow unknown extended attributes on interface mixins"
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface mixin A {
+ getter double (DOMString propertyName);
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow getters on interface mixins")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface mixin A {
+ setter undefined (DOMString propertyName, double propertyValue);
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow setters on interface mixins")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface mixin A {
+ deleter undefined (DOMString propertyName);
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow deleters on interface mixins")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface mixin A {
+ legacycaller double compute(double x);
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow legacycallers on interface mixins")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface mixin A {
+ inherit attribute x;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow inherited attribute on interface mixins")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Interface {};
+ interface NotMixin {
+ attribute short x;
+ };
+ Interface includes NotMixin;
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should fail if the right side does not point an interface mixin")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface mixin NotInterface {};
+ interface mixin Mixin {
+ attribute short x;
+ };
+ NotInterface includes Mixin;
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should fail if the left side does not point an interface")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface mixin Mixin {
+ iterable<DOMString>;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should fail if an interface mixin includes iterable")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface mixin Mixin {
+ setlike<DOMString>;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should fail if an interface mixin includes setlike")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface mixin Mixin {
+ maplike<DOMString, DOMString>;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should fail if an interface mixin includes maplike")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Interface {
+ attribute short attr;
+ };
+ interface mixin Mixin {
+ attribute short attr;
+ };
+ Interface includes Mixin;
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw, "Should fail if the included mixin interface has duplicated member"
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Interface {};
+ interface mixin Mixin1 {
+ attribute short attr;
+ };
+ interface mixin Mixin2 {
+ attribute short attr;
+ };
+ Interface includes Mixin1;
+ Interface includes Mixin2;
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw, "Should fail if the included mixin interfaces have duplicated member"
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ [Global, Exposed=Window] interface Window {};
+ [Global, Exposed=Worker] interface Worker {};
+ [Exposed=Window]
+ interface Base {};
+ interface mixin Mixin {
+ Base returnSelf();
+ };
+ Base includes Mixin;
+ """
+ )
+ results = parser.finish()
+ base = results[2]
+ attr = base.members[0]
+ harness.check(
+ attr.exposureSet,
+ set(["Window"]),
+ "Should expose on globals where the base interfaces are exposed",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ [Global, Exposed=Window] interface Window {};
+ [Global, Exposed=Worker] interface Worker {};
+ [Exposed=Window]
+ interface Base {};
+ [Exposed=Window]
+ interface mixin Mixin {
+ attribute short a;
+ };
+ Base includes Mixin;
+ """
+ )
+ results = parser.finish()
+ base = results[2]
+ attr = base.members[0]
+ harness.check(
+ attr.exposureSet, set(["Window"]), "Should follow [Exposed] on interface mixin"
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ [Global, Exposed=Window] interface Window {};
+ [Global, Exposed=Worker] interface Worker {};
+ [Exposed=Window]
+ interface Base1 {};
+ [Exposed=Worker]
+ interface Base2 {};
+ interface mixin Mixin {
+ attribute short a;
+ };
+ Base1 includes Mixin;
+ Base2 includes Mixin;
+ """
+ )
+ results = parser.finish()
+ base = results[2]
+ attr = base.members[0]
+ harness.check(
+ attr.exposureSet,
+ set(["Window", "Worker"]),
+ "Should expose on all globals where including interfaces are " "exposed",
+ )
+ base = results[3]
+ attr = base.members[0]
+ harness.check(
+ attr.exposureSet,
+ set(["Window", "Worker"]),
+ "Should expose on all globals where including interfaces are " "exposed",
+ )
diff --git a/dom/bindings/parser/tests/test_lenientSetter.py b/dom/bindings/parser/tests/test_lenientSetter.py
new file mode 100644
index 0000000000..1d2a9f06ce
--- /dev/null
+++ b/dom/bindings/parser/tests/test_lenientSetter.py
@@ -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/.
+
+
+def should_throw(parser, harness, message, code):
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(code)
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown: %s" % message)
+
+
+def WebIDLTest(parser, harness):
+ # The [LegacyLenientSetter] extended attribute MUST take no arguments.
+ should_throw(
+ parser,
+ harness,
+ "no arguments",
+ """
+ interface I {
+ [LegacyLenientSetter=X] readonly attribute long A;
+ };
+ """,
+ )
+
+ # An attribute with the [LegacyLenientSetter] extended attribute MUST NOT
+ # also be declared with the [PutForwards] extended attribute.
+ should_throw(
+ parser,
+ harness,
+ "PutForwards",
+ """
+ interface I {
+ [PutForwards=B, LegacyLenientSetter] readonly attribute J A;
+ };
+ interface J {
+ attribute long B;
+ };
+ """,
+ )
+
+ # An attribute with the [LegacyLenientSetter] extended attribute MUST NOT
+ # also be declared with the [Replaceable] extended attribute.
+ should_throw(
+ parser,
+ harness,
+ "Replaceable",
+ """
+ interface I {
+ [Replaceable, LegacyLenientSetter] readonly attribute J A;
+ };
+ """,
+ )
+
+ # The [LegacyLenientSetter] extended attribute MUST NOT be used on an
+ # attribute that is not read only.
+ should_throw(
+ parser,
+ harness,
+ "writable attribute",
+ """
+ interface I {
+ [LegacyLenientSetter] attribute long A;
+ };
+ """,
+ )
+
+ # The [LegacyLenientSetter] extended attribute MUST NOT be used on a
+ # static attribute.
+ should_throw(
+ parser,
+ harness,
+ "static attribute",
+ """
+ interface I {
+ [LegacyLenientSetter] static readonly attribute long A;
+ };
+ """,
+ )
diff --git a/dom/bindings/parser/tests/test_method.py b/dom/bindings/parser/tests/test_method.py
new file mode 100644
index 0000000000..e11044b742
--- /dev/null
+++ b/dom/bindings/parser/tests/test_method.py
@@ -0,0 +1,430 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ interface TestMethods {
+ undefined basic();
+ static undefined basicStatic();
+ undefined basicWithSimpleArgs(boolean arg1, byte arg2, unsigned long arg3);
+ boolean basicBoolean();
+ static boolean basicStaticBoolean();
+ boolean basicBooleanWithSimpleArgs(boolean arg1, byte arg2, unsigned long arg3);
+ undefined optionalArg(optional byte? arg1, optional sequence<byte> arg2);
+ undefined variadicArg(byte?... arg1);
+ object getObject();
+ undefined setObject(object arg1);
+ undefined setAny(any arg1);
+ float doFloats(float arg1);
+ };
+ """
+ )
+
+ results = parser.finish()
+
+ harness.ok(True, "TestMethods interface parsed without error.")
+ harness.check(len(results), 1, "Should be one production.")
+ iface = results[0]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should be an IDLInterface")
+ harness.check(
+ iface.identifier.QName(), "::TestMethods", "Interface has the right QName"
+ )
+ harness.check(iface.identifier.name, "TestMethods", "Interface has the right name")
+ harness.check(len(iface.members), 12, "Expect 12 members")
+
+ methods = iface.members
+
+ def checkArgument(argument, QName, name, type, optional, variadic):
+ harness.ok(isinstance(argument, WebIDL.IDLArgument), "Should be an IDLArgument")
+ harness.check(
+ argument.identifier.QName(), QName, "Argument has the right QName"
+ )
+ harness.check(argument.identifier.name, name, "Argument has the right name")
+ harness.check(str(argument.type), type, "Argument has the right return type")
+ harness.check(
+ argument.optional, optional, "Argument has the right optional value"
+ )
+ harness.check(
+ argument.variadic, variadic, "Argument has the right variadic value"
+ )
+
+ def checkMethod(
+ method,
+ QName,
+ name,
+ signatures,
+ static=False,
+ getter=False,
+ setter=False,
+ deleter=False,
+ legacycaller=False,
+ stringifier=False,
+ ):
+ harness.ok(isinstance(method, WebIDL.IDLMethod), "Should be an IDLMethod")
+ harness.ok(method.isMethod(), "Method is a method")
+ harness.ok(not method.isAttr(), "Method is not an attr")
+ harness.ok(not method.isConst(), "Method is not a const")
+ harness.check(method.identifier.QName(), QName, "Method has the right QName")
+ harness.check(method.identifier.name, name, "Method has the right name")
+ harness.check(method.isStatic(), static, "Method has the correct static value")
+ harness.check(method.isGetter(), getter, "Method has the correct getter value")
+ harness.check(method.isSetter(), setter, "Method has the correct setter value")
+ harness.check(
+ method.isDeleter(), deleter, "Method has the correct deleter value"
+ )
+ harness.check(
+ method.isLegacycaller(),
+ legacycaller,
+ "Method has the correct legacycaller value",
+ )
+ harness.check(
+ method.isStringifier(),
+ stringifier,
+ "Method has the correct stringifier value",
+ )
+ harness.check(
+ len(method.signatures()),
+ len(signatures),
+ "Method has the correct number of signatures",
+ )
+
+ sigpairs = zip(method.signatures(), signatures)
+ for (gotSignature, expectedSignature) in sigpairs:
+ (gotRetType, gotArgs) = gotSignature
+ (expectedRetType, expectedArgs) = expectedSignature
+
+ harness.check(
+ str(gotRetType), expectedRetType, "Method has the expected return type."
+ )
+
+ for i in range(0, len(gotArgs)):
+ (QName, name, type, optional, variadic) = expectedArgs[i]
+ checkArgument(gotArgs[i], QName, name, type, optional, variadic)
+
+ checkMethod(methods[0], "::TestMethods::basic", "basic", [("Undefined", [])])
+ checkMethod(
+ methods[1],
+ "::TestMethods::basicStatic",
+ "basicStatic",
+ [("Undefined", [])],
+ static=True,
+ )
+ checkMethod(
+ methods[2],
+ "::TestMethods::basicWithSimpleArgs",
+ "basicWithSimpleArgs",
+ [
+ (
+ "Undefined",
+ [
+ (
+ "::TestMethods::basicWithSimpleArgs::arg1",
+ "arg1",
+ "Boolean",
+ False,
+ False,
+ ),
+ (
+ "::TestMethods::basicWithSimpleArgs::arg2",
+ "arg2",
+ "Byte",
+ False,
+ False,
+ ),
+ (
+ "::TestMethods::basicWithSimpleArgs::arg3",
+ "arg3",
+ "UnsignedLong",
+ False,
+ False,
+ ),
+ ],
+ )
+ ],
+ )
+ checkMethod(
+ methods[3], "::TestMethods::basicBoolean", "basicBoolean", [("Boolean", [])]
+ )
+ checkMethod(
+ methods[4],
+ "::TestMethods::basicStaticBoolean",
+ "basicStaticBoolean",
+ [("Boolean", [])],
+ static=True,
+ )
+ checkMethod(
+ methods[5],
+ "::TestMethods::basicBooleanWithSimpleArgs",
+ "basicBooleanWithSimpleArgs",
+ [
+ (
+ "Boolean",
+ [
+ (
+ "::TestMethods::basicBooleanWithSimpleArgs::arg1",
+ "arg1",
+ "Boolean",
+ False,
+ False,
+ ),
+ (
+ "::TestMethods::basicBooleanWithSimpleArgs::arg2",
+ "arg2",
+ "Byte",
+ False,
+ False,
+ ),
+ (
+ "::TestMethods::basicBooleanWithSimpleArgs::arg3",
+ "arg3",
+ "UnsignedLong",
+ False,
+ False,
+ ),
+ ],
+ )
+ ],
+ )
+ checkMethod(
+ methods[6],
+ "::TestMethods::optionalArg",
+ "optionalArg",
+ [
+ (
+ "Undefined",
+ [
+ (
+ "::TestMethods::optionalArg::arg1",
+ "arg1",
+ "ByteOrNull",
+ True,
+ False,
+ ),
+ (
+ "::TestMethods::optionalArg::arg2",
+ "arg2",
+ "ByteSequence",
+ True,
+ False,
+ ),
+ ],
+ )
+ ],
+ )
+ checkMethod(
+ methods[7],
+ "::TestMethods::variadicArg",
+ "variadicArg",
+ [
+ (
+ "Undefined",
+ [
+ (
+ "::TestMethods::variadicArg::arg1",
+ "arg1",
+ "ByteOrNull",
+ True,
+ True,
+ )
+ ],
+ )
+ ],
+ )
+ checkMethod(methods[8], "::TestMethods::getObject", "getObject", [("Object", [])])
+ checkMethod(
+ methods[9],
+ "::TestMethods::setObject",
+ "setObject",
+ [
+ (
+ "Undefined",
+ [("::TestMethods::setObject::arg1", "arg1", "Object", False, False)],
+ )
+ ],
+ )
+ checkMethod(
+ methods[10],
+ "::TestMethods::setAny",
+ "setAny",
+ [("Undefined", [("::TestMethods::setAny::arg1", "arg1", "Any", False, False)])],
+ )
+ checkMethod(
+ methods[11],
+ "::TestMethods::doFloats",
+ "doFloats",
+ [("Float", [("::TestMethods::doFloats::arg1", "arg1", "Float", False, False)])],
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ undefined foo(optional float bar = 1);
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(not threw, "Should allow integer to float type corecion")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ [GetterThrows] undefined foo();
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow [GetterThrows] on methods")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ [SetterThrows] undefined foo();
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow [SetterThrows] on methods")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ [Throw] undefined foo();
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should spell [Throws] correctly on methods")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ undefined __noSuchMethod__();
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow __noSuchMethod__ methods")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ [Throws, LenientFloat]
+ undefined foo(float myFloat);
+ [Throws]
+ undefined foo();
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(not threw, "Should allow LenientFloat to be only in a specific overload")
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface A {
+ [Throws]
+ undefined foo();
+ [Throws, LenientFloat]
+ undefined foo(float myFloat);
+ };
+ """
+ )
+ results = parser.finish()
+ iface = results[0]
+ methods = iface.members
+ lenientFloat = methods[0].getExtendedAttribute("LenientFloat")
+ harness.ok(
+ lenientFloat is not None,
+ "LenientFloat in overloads must be added to the method",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ [Throws, LenientFloat]
+ undefined foo(float myFloat);
+ [Throws]
+ undefined foo(float myFloat, float yourFloat);
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw,
+ "Should prevent overloads from getting different restricted float behavior",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ [Throws]
+ undefined foo(float myFloat, float yourFloat);
+ [Throws, LenientFloat]
+ undefined foo(float myFloat);
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw,
+ "Should prevent overloads from getting different restricted float behavior (2)",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ [Throws, LenientFloat]
+ undefined foo(float myFloat);
+ [Throws, LenientFloat]
+ undefined foo(short myShort);
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should prevent overloads from getting redundant [LenientFloat]")
diff --git a/dom/bindings/parser/tests/test_namespace.py b/dom/bindings/parser/tests/test_namespace.py
new file mode 100644
index 0000000000..cf315800ae
--- /dev/null
+++ b/dom/bindings/parser/tests/test_namespace.py
@@ -0,0 +1,232 @@
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ namespace MyNamespace {
+ attribute any foo;
+ any bar();
+ };
+ """
+ )
+
+ results = parser.finish()
+ harness.check(len(results), 1, "Should have a thing.")
+ harness.ok(results[0].isNamespace(), "Our thing should be a namespace")
+ harness.check(len(results[0].members), 2, "Should have two things in our namespace")
+ harness.ok(results[0].members[0].isAttr(), "First member is attribute")
+ harness.ok(results[0].members[0].isStatic(), "Attribute should be static")
+ harness.ok(results[0].members[1].isMethod(), "Second member is method")
+ harness.ok(results[0].members[1].isStatic(), "Operation should be static")
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ namespace MyNamespace {
+ attribute any foo;
+ };
+ partial namespace MyNamespace {
+ any bar();
+ };
+ """
+ )
+
+ results = parser.finish()
+ harness.check(len(results), 2, "Should have things.")
+ harness.ok(results[0].isNamespace(), "Our thing should be a namespace")
+ harness.check(len(results[0].members), 2, "Should have two things in our namespace")
+ harness.ok(results[0].members[0].isAttr(), "First member is attribute")
+ harness.ok(results[0].members[0].isStatic(), "Attribute should be static")
+ harness.ok(results[0].members[1].isMethod(), "Second member is method")
+ harness.ok(results[0].members[1].isStatic(), "Operation should be static")
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ partial namespace MyNamespace {
+ any bar();
+ };
+ namespace MyNamespace {
+ attribute any foo;
+ };
+ """
+ )
+
+ results = parser.finish()
+ harness.check(len(results), 2, "Should have things.")
+ harness.ok(results[1].isNamespace(), "Our thing should be a namespace")
+ harness.check(len(results[1].members), 2, "Should have two things in our namespace")
+ harness.ok(results[1].members[0].isAttr(), "First member is attribute")
+ harness.ok(results[1].members[0].isStatic(), "Attribute should be static")
+ harness.ok(results[1].members[1].isMethod(), "Second member is method")
+ harness.ok(results[1].members[1].isStatic(), "Operation should be static")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ namespace MyNamespace {
+ static attribute any foo;
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ namespace MyNamespace {
+ static any bar();
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ namespace MyNamespace {
+ any bar();
+ };
+
+ interface MyNamespace {
+ any baz();
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface MyNamespace {
+ any baz();
+ };
+
+ namespace MyNamespace {
+ any bar();
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ namespace MyNamespace {
+ any baz();
+ };
+
+ namespace MyNamespace {
+ any bar();
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ partial namespace MyNamespace {
+ any baz();
+ };
+
+ interface MyNamespace {
+ any bar();
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ namespace MyNamespace {
+ any bar();
+ };
+
+ partial interface MyNamespace {
+ any baz();
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ partial interface MyNamespace {
+ any baz();
+ };
+
+ namespace MyNamespace {
+ any bar();
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface MyNamespace {
+ any bar();
+ };
+
+ partial namespace MyNamespace {
+ any baz();
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should have thrown.")
diff --git a/dom/bindings/parser/tests/test_newobject.py b/dom/bindings/parser/tests/test_newobject.py
new file mode 100644
index 0000000000..efa0385542
--- /dev/null
+++ b/dom/bindings/parser/tests/test_newobject.py
@@ -0,0 +1,73 @@
+# Import the WebIDL module, so we can do isinstance checks and whatnot
+def WebIDLTest(parser, harness):
+ # Basic functionality
+ parser.parse(
+ """
+ interface Iface {
+ [NewObject] readonly attribute Iface attr;
+ [NewObject] Iface method();
+ };
+ """
+ )
+ results = parser.finish()
+ harness.ok(results, "Should not have thrown on basic [NewObject] usage")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Iface {
+ [Pure, NewObject] readonly attribute Iface attr;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "[NewObject] attributes must depend on something")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Iface {
+ [Pure, NewObject] Iface method();
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "[NewObject] methods must depend on something")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Iface {
+ [Cached, NewObject, Affects=Nothing] readonly attribute Iface attr;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "[NewObject] attributes must not be [Cached]")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Iface {
+ [StoreInSlot, NewObject, Affects=Nothing] readonly attribute Iface attr;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "[NewObject] attributes must not be [StoreInSlot]")
diff --git a/dom/bindings/parser/tests/test_nullable_equivalency.py b/dom/bindings/parser/tests/test_nullable_equivalency.py
new file mode 100644
index 0000000000..278794f719
--- /dev/null
+++ b/dom/bindings/parser/tests/test_nullable_equivalency.py
@@ -0,0 +1,138 @@
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ interface TestNullableEquivalency1 {
+ attribute long a;
+ attribute long? b;
+ };
+
+ interface TestNullableEquivalency2 {
+ attribute ArrayBuffer a;
+ attribute ArrayBuffer? b;
+ };
+
+ /* Can't have dictionary-valued attributes, so can't test that here */
+
+ enum TestNullableEquivalency4Enum {
+ "Foo",
+ "Bar"
+ };
+
+ interface TestNullableEquivalency4 {
+ attribute TestNullableEquivalency4Enum a;
+ attribute TestNullableEquivalency4Enum? b;
+ };
+
+ interface TestNullableEquivalency5 {
+ attribute TestNullableEquivalency4 a;
+ attribute TestNullableEquivalency4? b;
+ };
+
+ interface TestNullableEquivalency6 {
+ attribute boolean a;
+ attribute boolean? b;
+ };
+
+ interface TestNullableEquivalency7 {
+ attribute DOMString a;
+ attribute DOMString? b;
+ };
+
+ interface TestNullableEquivalency8 {
+ attribute float a;
+ attribute float? b;
+ };
+
+ interface TestNullableEquivalency9 {
+ attribute double a;
+ attribute double? b;
+ };
+
+ interface TestNullableEquivalency10 {
+ attribute object a;
+ attribute object? b;
+ };
+ """
+ )
+
+ for decl in parser.finish():
+ if decl.isInterface():
+ checkEquivalent(decl, harness)
+
+
+def checkEquivalent(iface, harness):
+ type1 = iface.members[0].type
+ type2 = iface.members[1].type
+
+ harness.check(type1.nullable(), False, "attr1 should not be nullable")
+ harness.check(type2.nullable(), True, "attr2 should be nullable")
+
+ # We don't know about type1, but type2, the nullable type, definitely
+ # shouldn't be builtin.
+ harness.check(type2.builtin, False, "attr2 should not be builtin")
+
+ # Ensure that all attributes of type2 match those in type1, except for:
+ # - names on an ignore list,
+ # - names beginning with '_',
+ # - functions which throw when called with no args, and
+ # - class-level non-callables ("static variables").
+ #
+ # Yes, this is an ugly, fragile hack. But it finds bugs...
+ for attr in dir(type1):
+ if (
+ attr.startswith("_")
+ or attr
+ in [
+ "nullable",
+ "builtin",
+ "filename",
+ "location",
+ "inner",
+ "QName",
+ "getDeps",
+ "name",
+ "prettyName",
+ ]
+ or (hasattr(type(type1), attr) and not callable(getattr(type1, attr)))
+ ):
+ continue
+
+ a1 = getattr(type1, attr)
+
+ if callable(a1):
+ try:
+ v1 = a1()
+ except Exception:
+ # Can't call a1 with no args, so skip this attriute.
+ continue
+
+ try:
+ a2 = getattr(type2, attr)
+ except Exception:
+ harness.ok(
+ False,
+ "Missing %s attribute on type %s in %s" % (attr, type2, iface),
+ )
+ continue
+
+ if not callable(a2):
+ harness.ok(
+ False,
+ "%s attribute on type %s in %s wasn't callable"
+ % (attr, type2, iface),
+ )
+ continue
+
+ v2 = a2()
+ harness.check(v2, v1, "%s method return value" % attr)
+ else:
+ try:
+ a2 = getattr(type2, attr)
+ except Exception:
+ harness.ok(
+ False,
+ "Missing %s attribute on type %s in %s" % (attr, type2, iface),
+ )
+ continue
+
+ harness.check(a2, a1, "%s attribute should match" % attr)
diff --git a/dom/bindings/parser/tests/test_nullable_void.py b/dom/bindings/parser/tests/test_nullable_void.py
new file mode 100644
index 0000000000..5acba727e5
--- /dev/null
+++ b/dom/bindings/parser/tests/test_nullable_void.py
@@ -0,0 +1,16 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface NullableVoid {
+ void? foo();
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/dom/bindings/parser/tests/test_observableArray.py b/dom/bindings/parser/tests/test_observableArray.py
new file mode 100644
index 0000000000..601f626bcf
--- /dev/null
+++ b/dom/bindings/parser/tests/test_observableArray.py
@@ -0,0 +1,288 @@
+# 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/.
+
+
+def WebIDLTest(parser, harness):
+
+ # Test dictionary as inner type
+ harness.should_throw(
+ parser,
+ """
+ dictionary A {
+ boolean member;
+ };
+ interface B {
+ attribute ObservableArray<A> foo;
+ };
+ """,
+ "use dictionary as inner type",
+ )
+
+ # Test sequence as inner type
+ harness.should_throw(
+ parser,
+ """
+ interface A {
+ attribute ObservableArray<sequence<boolean>> foo;
+ };
+ """,
+ "use sequence as inner type",
+ )
+
+ # Test sequence<dictionary> as inner type
+ harness.should_throw(
+ parser,
+ """
+ dictionary A {
+ boolean member;
+ };
+ interface B {
+ attribute ObservableArray<sequence<A>> foo;
+ };
+ """,
+ "use sequence<dictionary> as inner type",
+ )
+
+ # Test record as inner type
+ harness.should_throw(
+ parser,
+ """
+ interface A {
+ attribute ObservableArray<record<DOMString, boolean>> foo;
+ };
+ """,
+ "use record as inner type",
+ )
+
+ # Test record<dictionary> as inner type
+ harness.should_throw(
+ parser,
+ """
+ dictionary A {
+ boolean member;
+ };
+ interface B {
+ attribute ObservableArray<record<DOMString, A>> foo;
+ };
+ """,
+ "use record<dictionary> as inner type",
+ )
+
+ # Test observable array as inner type
+ harness.should_throw(
+ parser,
+ """
+ interface A {
+ attribute ObservableArray<ObservableArray<boolean>> foo;
+ };
+ """,
+ "use ObservableArray as inner type",
+ )
+
+ # Test nullable attribute
+ harness.should_throw(
+ parser,
+ """
+ interface A {
+ attribute ObservableArray<boolean>? foo;
+ };
+ """,
+ "nullable",
+ )
+
+ # Test sequence
+ harness.should_throw(
+ parser,
+ """
+ interface A {
+ undefined foo(sequence<ObservableArray<boolean>> foo);
+ };
+ """,
+ "used in sequence",
+ )
+
+ # Test record
+ harness.should_throw(
+ parser,
+ """
+ interface A {
+ undefined foo(record<DOMString, ObservableArray<boolean>> foo);
+ };
+ """,
+ "used in record",
+ )
+
+ # Test promise
+ harness.should_throw(
+ parser,
+ """
+ interface A {
+ Promise<ObservableArray<boolean>> foo();
+ };
+ """,
+ "used in promise",
+ )
+
+ # Test union
+ harness.should_throw(
+ parser,
+ """
+ interface A {
+ attribute (DOMString or ObservableArray<boolean>>) foo;
+ };
+ """,
+ "used in union",
+ )
+
+ # Test dictionary member
+ harness.should_throw(
+ parser,
+ """
+ dictionary A {
+ ObservableArray<boolean> foo;
+ };
+ """,
+ "used on dictionary member type",
+ )
+
+ # Test argument
+ harness.should_throw(
+ parser,
+ """
+ interface A {
+ undefined foo(ObservableArray<boolean> foo);
+ };
+ """,
+ "used on argument",
+ )
+
+ # Test static attribute
+ harness.should_throw(
+ parser,
+ """
+ interface A {
+ static attribute ObservableArray<boolean> foo;
+ };
+ """,
+ "used on static attribute type",
+ )
+
+ # Test iterable
+ harness.should_throw(
+ parser,
+ """
+ interface A {
+ iterable<ObservableArray<boolean>>;
+ };
+ """,
+ "used in iterable",
+ )
+
+ # Test maplike
+ harness.should_throw(
+ parser,
+ """
+ interface A {
+ maplike<long, ObservableArray<boolean>>;
+ };
+ """,
+ "used in maplike",
+ )
+
+ # Test setlike
+ harness.should_throw(
+ parser,
+ """
+ interface A {
+ setlike<ObservableArray<boolean>>;
+ };
+ """,
+ "used in setlike",
+ )
+
+ # Test JS implemented interface
+ harness.should_throw(
+ parser,
+ """
+ [JSImplementation="@mozilla.org/dom/test-interface-js;1"]
+ interface A {
+ readonly attribute ObservableArray<boolean> foo;
+ };
+ """,
+ "used in JS implemented interface",
+ )
+
+ # Test namespace
+ harness.should_throw(
+ parser,
+ """
+ namespace A {
+ readonly attribute ObservableArray<boolean> foo;
+ };
+ """,
+ "used in namespaces",
+ )
+
+ # Test [Cached] extended attribute
+ harness.should_throw(
+ parser,
+ """
+ interface A {
+ [Cached, Pure]
+ readonly attribute ObservableArray<boolean> foo;
+ };
+ """,
+ "have Cached extended attribute",
+ )
+
+ # Test [StoreInSlot] extended attribute
+ harness.should_throw(
+ parser,
+ """
+ interface A {
+ [StoreInSlot, Pure]
+ readonly attribute ObservableArray<boolean> foo;
+ };
+ """,
+ "have StoreInSlot extended attribute",
+ )
+
+ # Test regular attribute
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface A {
+ readonly attribute ObservableArray<boolean> foo;
+ attribute ObservableArray<[Clamp] octet> bar;
+ attribute ObservableArray<long?> baz;
+ attribute ObservableArray<(boolean or long)> qux;
+ };
+ """
+ )
+ results = parser.finish()
+ A = results[0]
+ foo = A.members[0]
+ harness.ok(foo.readonly, "A.foo is readonly attribute")
+ harness.ok(foo.type.isObservableArray(), "A.foo is ObservableArray type")
+ harness.check(
+ foo.slotIndices[A.identifier.name], 0, "A.foo should be stored in slot"
+ )
+ bar = A.members[1]
+ harness.ok(bar.type.isObservableArray(), "A.bar is ObservableArray type")
+ harness.check(
+ bar.slotIndices[A.identifier.name], 1, "A.bar should be stored in slot"
+ )
+ harness.ok(bar.type.inner.hasClamp(), "A.bar's inner type should be clamped")
+ baz = A.members[2]
+ harness.ok(baz.type.isObservableArray(), "A.baz is ObservableArray type")
+ harness.check(
+ baz.slotIndices[A.identifier.name], 2, "A.baz should be stored in slot"
+ )
+ harness.ok(baz.type.inner.nullable(), "A.baz's inner type should be nullable")
+ qux = A.members[3]
+ harness.ok(qux.type.isObservableArray(), "A.qux is ObservableArray type")
+ harness.check(
+ qux.slotIndices[A.identifier.name], 3, "A.qux should be stored in slot"
+ )
+ harness.ok(qux.type.inner.isUnion(), "A.qux's inner type should be union")
diff --git a/dom/bindings/parser/tests/test_optional_constraints.py b/dom/bindings/parser/tests/test_optional_constraints.py
new file mode 100644
index 0000000000..78e71df544
--- /dev/null
+++ b/dom/bindings/parser/tests/test_optional_constraints.py
@@ -0,0 +1,35 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface OptionalConstraints1 {
+ undefined foo(optional byte arg1, byte arg2);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ not threw,
+ "Should not have thrown on non-optional argument following "
+ "optional argument.",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface OptionalConstraints2 {
+ undefined foo(optional byte arg1 = 1, optional byte arg2 = 2,
+ optional byte arg3, optional byte arg4 = 4,
+ optional byte arg5, optional byte arg6 = 9);
+ };
+ """
+ )
+ results = parser.finish()
+ args = results[0].members[0].signatures()[0][1]
+ harness.check(len(args), 6, "Should have 6 arguments")
+ harness.check(args[5].defaultValue.value, 9, "Should have correct default value")
diff --git a/dom/bindings/parser/tests/test_overload.py b/dom/bindings/parser/tests/test_overload.py
new file mode 100644
index 0000000000..7816276aa6
--- /dev/null
+++ b/dom/bindings/parser/tests/test_overload.py
@@ -0,0 +1,74 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ interface TestOverloads {
+ undefined basic();
+ undefined basic(long arg1);
+ boolean abitharder(TestOverloads foo);
+ boolean abitharder(boolean foo);
+ undefined abitharder(ArrayBuffer? foo);
+ undefined withVariadics(long... numbers);
+ undefined withVariadics(TestOverloads iface);
+ undefined withVariadics(long num, TestOverloads iface);
+ undefined optionalTest();
+ undefined optionalTest(optional long num1, long num2);
+ };
+ """
+ )
+
+ results = parser.finish()
+
+ harness.ok(True, "TestOverloads interface parsed without error.")
+ harness.check(len(results), 1, "Should be one production.")
+ iface = results[0]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface), "Should be an IDLInterface")
+ harness.check(
+ iface.identifier.QName(), "::TestOverloads", "Interface has the right QName"
+ )
+ harness.check(
+ iface.identifier.name, "TestOverloads", "Interface has the right name"
+ )
+ harness.check(len(iface.members), 4, "Expect %s members" % 4)
+
+ member = iface.members[0]
+ harness.check(
+ member.identifier.QName(),
+ "::TestOverloads::basic",
+ "Method has the right QName",
+ )
+ harness.check(member.identifier.name, "basic", "Method has the right name")
+ harness.check(member.hasOverloads(), True, "Method has overloads")
+
+ signatures = member.signatures()
+ harness.check(len(signatures), 2, "Method should have 2 signatures")
+
+ (retval, argumentSet) = signatures[0]
+
+ harness.check(str(retval), "Undefined", "Expect an undefined retval")
+ harness.check(len(argumentSet), 0, "Expect an empty argument set")
+
+ (retval, argumentSet) = signatures[1]
+ harness.check(str(retval), "Undefined", "Expect an undefined retval")
+ harness.check(len(argumentSet), 1, "Expect an argument set with one argument")
+
+ argument = argumentSet[0]
+ harness.ok(isinstance(argument, WebIDL.IDLArgument), "Should be an IDLArgument")
+ harness.check(
+ argument.identifier.QName(),
+ "::TestOverloads::basic::arg1",
+ "Argument has the right QName",
+ )
+ harness.check(argument.identifier.name, "arg1", "Argument has the right name")
+ harness.check(str(argument.type), "Long", "Argument has the right type")
+
+ member = iface.members[3]
+ harness.check(
+ len(member.overloadsForArgCount(0)), 1, "Only one overload for no args"
+ )
+ harness.check(len(member.overloadsForArgCount(1)), 0, "No overloads for one arg")
+ harness.check(
+ len(member.overloadsForArgCount(2)), 1, "Only one overload for two args"
+ )
diff --git a/dom/bindings/parser/tests/test_promise.py b/dom/bindings/parser/tests/test_promise.py
new file mode 100644
index 0000000000..e8051c36f0
--- /dev/null
+++ b/dom/bindings/parser/tests/test_promise.py
@@ -0,0 +1,177 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ legacycaller Promise<any> foo();
+ };
+ """
+ )
+ parser.finish()
+
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow Promise return values for legacycaller.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ Promise<any> foo();
+ long foo(long arg);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw,
+ "Should not allow overloads which have both Promise and "
+ "non-Promise return types.",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ long foo(long arg);
+ Promise<any> foo();
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw,
+ "Should not allow overloads which have both Promise and "
+ "non-Promise return types.",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ Promise<any>? foo();
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow nullable Promise return values.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ undefined foo(Promise<any>? arg);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow nullable Promise arguments.")
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface A {
+ Promise<any> foo();
+ Promise<any> foo(long arg);
+ };
+ """
+ )
+ parser.finish()
+
+ harness.ok(
+ True, "Should allow overloads which only have Promise and return " "types."
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ attribute Promise<any> attr;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow writable Promise-typed attributes.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ [LegacyLenientSetter] readonly attribute Promise<any> attr;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw, "Should not allow [LegacyLenientSetter] Promise-typed attributes."
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ [PutForwards=bar] readonly attribute Promise<any> attr;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow [PutForwards] Promise-typed attributes.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ [Replaceable] readonly attribute Promise<any> attr;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow [Replaceable] Promise-typed attributes.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface A {
+ [SameObject] readonly attribute Promise<any> attr;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow [SameObject] Promise-typed attributes.")
diff --git a/dom/bindings/parser/tests/test_prototype_ident.py b/dom/bindings/parser/tests/test_prototype_ident.py
new file mode 100644
index 0000000000..a47f42af9d
--- /dev/null
+++ b/dom/bindings/parser/tests/test_prototype_ident.py
@@ -0,0 +1,107 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestIface {
+ static attribute boolean prototype;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "The identifier of a static attribute must not be 'prototype'")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestIface {
+ static boolean prototype();
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "The identifier of a static operation must not be 'prototype'")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestIface {
+ const boolean prototype = true;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "The identifier of a constant must not be 'prototype'")
+
+ # Make sure that we can parse non-static attributes with 'prototype' as identifier.
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface TestIface {
+ attribute boolean prototype;
+ };
+ """
+ )
+ results = parser.finish()
+
+ testIface = results[0]
+ harness.check(
+ testIface.members[0].isStatic(), False, "Attribute should not be static"
+ )
+ harness.check(
+ testIface.members[0].identifier.name,
+ "prototype",
+ "Attribute identifier should be 'prototype'",
+ )
+
+ # Make sure that we can parse non-static operations with 'prototype' as identifier.
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface TestIface {
+ boolean prototype();
+ };
+ """
+ )
+ results = parser.finish()
+
+ testIface = results[0]
+ harness.check(
+ testIface.members[0].isStatic(), False, "Operation should not be static"
+ )
+ harness.check(
+ testIface.members[0].identifier.name,
+ "prototype",
+ "Operation identifier should be 'prototype'",
+ )
+
+ # Make sure that we can parse dictionary members with 'prototype' as identifier.
+ parser = parser.reset()
+ parser.parse(
+ """
+ dictionary TestDict {
+ boolean prototype;
+ };
+ """
+ )
+ results = parser.finish()
+
+ testDict = results[0]
+ harness.check(
+ testDict.members[0].identifier.name,
+ "prototype",
+ "Dictionary member should be 'prototype'",
+ )
diff --git a/dom/bindings/parser/tests/test_putForwards.py b/dom/bindings/parser/tests/test_putForwards.py
new file mode 100644
index 0000000000..4e8504f766
--- /dev/null
+++ b/dom/bindings/parser/tests/test_putForwards.py
@@ -0,0 +1,119 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface I {
+ [PutForwards=B] readonly attribute long A;
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface I {
+ [PutForwards=B] readonly attribute J A;
+ };
+ interface J {
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface I {
+ [PutForwards=B] attribute J A;
+ };
+ interface J {
+ attribute long B;
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface I {
+ [PutForwards=B] static readonly attribute J A;
+ };
+ interface J {
+ attribute long B;
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ callback interface I {
+ [PutForwards=B] readonly attribute J A;
+ };
+ interface J {
+ attribute long B;
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface I {
+ [PutForwards=C] readonly attribute J A;
+ [PutForwards=C] readonly attribute J B;
+ };
+ interface J {
+ [PutForwards=D] readonly attribute K C;
+ };
+ interface K {
+ [PutForwards=A] readonly attribute I D;
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/dom/bindings/parser/tests/test_record.py b/dom/bindings/parser/tests/test_record.py
new file mode 100644
index 0000000000..930b0e1a90
--- /dev/null
+++ b/dom/bindings/parser/tests/test_record.py
@@ -0,0 +1,61 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ dictionary Dict {};
+ interface RecordArg {
+ undefined foo(record<DOMString, Dict> arg);
+ };
+ """
+ )
+
+ results = parser.finish()
+
+ harness.check(len(results), 2, "Should know about two things")
+ harness.ok(
+ isinstance(results[1], WebIDL.IDLInterface), "Should have an interface here"
+ )
+ members = results[1].members
+ harness.check(len(members), 1, "Should have one member")
+ harness.ok(members[0].isMethod(), "Should have method")
+ signature = members[0].signatures()[0]
+ args = signature[1]
+ harness.check(len(args), 1, "Should have one arg")
+ harness.ok(args[0].type.isRecord(), "Should have a record type here")
+ harness.ok(args[0].type.inner.isDictionary(), "Should have a dictionary inner type")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface RecordUndefinedArg {
+ undefined foo(record<DOMString, undefined> arg);
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw, "Should have thrown because record can't have undefined as value type."
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ dictionary Dict {
+ record<DOMString, Dict> val;
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should have thrown on dictionary containing itself via record.")
diff --git a/dom/bindings/parser/tests/test_replaceable.py b/dom/bindings/parser/tests/test_replaceable.py
new file mode 100644
index 0000000000..3b6df65c07
--- /dev/null
+++ b/dom/bindings/parser/tests/test_replaceable.py
@@ -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/.
+
+
+def should_throw(parser, harness, message, code):
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(code)
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown: %s" % message)
+
+
+def WebIDLTest(parser, harness):
+ # The [Replaceable] extended attribute MUST take no arguments.
+ should_throw(
+ parser,
+ harness,
+ "no arguments",
+ """
+ interface I {
+ [Replaceable=X] readonly attribute long A;
+ };
+ """,
+ )
+
+ # An attribute with the [Replaceable] extended attribute MUST NOT also be
+ # declared with the [PutForwards] extended attribute.
+ should_throw(
+ parser,
+ harness,
+ "PutForwards",
+ """
+ interface I {
+ [PutForwards=B, Replaceable] readonly attribute J A;
+ };
+ interface J {
+ attribute long B;
+ };
+ """,
+ )
+
+ # The [Replaceable] extended attribute MUST NOT be used on an attribute
+ # that is not read only.
+ should_throw(
+ parser,
+ harness,
+ "writable attribute",
+ """
+ interface I {
+ [Replaceable] attribute long A;
+ };
+ """,
+ )
+
+ # The [Replaceable] extended attribute MUST NOT be used on a static
+ # attribute.
+ should_throw(
+ parser,
+ harness,
+ "static attribute",
+ """
+ interface I {
+ [Replaceable] static readonly attribute long A;
+ };
+ """,
+ )
+
+ # The [Replaceable] extended attribute MUST NOT be used on an attribute
+ # declared on a callback interface.
+ should_throw(
+ parser,
+ harness,
+ "callback interface",
+ """
+ callback interface I {
+ [Replaceable] readonly attribute long A;
+ };
+ """,
+ )
diff --git a/dom/bindings/parser/tests/test_sanity.py b/dom/bindings/parser/tests/test_sanity.py
new file mode 100644
index 0000000000..d3184c0073
--- /dev/null
+++ b/dom/bindings/parser/tests/test_sanity.py
@@ -0,0 +1,7 @@
+def WebIDLTest(parser, harness):
+ parser.parse("")
+ parser.finish()
+ harness.ok(True, "Parsing nothing doesn't throw.")
+ parser.parse("interface Foo {};")
+ parser.finish()
+ harness.ok(True, "Parsing a silly interface doesn't throw.")
diff --git a/dom/bindings/parser/tests/test_securecontext_extended_attribute.py b/dom/bindings/parser/tests/test_securecontext_extended_attribute.py
new file mode 100644
index 0000000000..f7848bf092
--- /dev/null
+++ b/dom/bindings/parser/tests/test_securecontext_extended_attribute.py
@@ -0,0 +1,538 @@
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ [SecureContext]
+ interface TestSecureContextOnInterface {
+ const octet TEST_CONSTANT = 0;
+ readonly attribute byte testAttribute;
+ undefined testMethod(byte foo);
+ };
+ partial interface TestSecureContextOnInterface {
+ const octet TEST_CONSTANT_2 = 0;
+ readonly attribute byte testAttribute2;
+ undefined testMethod2(byte foo);
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(
+ len(results[0].members),
+ 6,
+ "TestSecureContextOnInterface should have six members",
+ )
+ harness.ok(
+ results[0].getExtendedAttribute("SecureContext"),
+ "Interface should have [SecureContext] extended attribute",
+ )
+ harness.ok(
+ results[0].members[0].getExtendedAttribute("SecureContext"),
+ "[SecureContext] should propagate from interface to constant members",
+ )
+ harness.ok(
+ results[0].members[1].getExtendedAttribute("SecureContext"),
+ "[SecureContext] should propagate from interface to attribute members",
+ )
+ harness.ok(
+ results[0].members[2].getExtendedAttribute("SecureContext"),
+ "[SecureContext] should propagate from interface to method members",
+ )
+ harness.ok(
+ results[0].members[3].getExtendedAttribute("SecureContext"),
+ (
+ "[SecureContext] should propagate from interface to "
+ "constant members from partial interface"
+ ),
+ )
+ harness.ok(
+ results[0].members[4].getExtendedAttribute("SecureContext"),
+ (
+ "[SecureContext] should propagate from interface to "
+ "attribute members from partial interface"
+ ),
+ )
+ harness.ok(
+ results[0].members[5].getExtendedAttribute("SecureContext"),
+ "[SecureContext] should propagate from interface to method members from partial interface",
+ )
+
+ # Same thing, but with the partial interface specified first:
+ parser = parser.reset()
+ parser.parse(
+ """
+ partial interface TestSecureContextOnInterfaceAfterPartialInterface {
+ const octet TEST_CONSTANT_2 = 0;
+ readonly attribute byte testAttribute2;
+ undefined testMethod2(byte foo);
+ };
+ [SecureContext]
+ interface TestSecureContextOnInterfaceAfterPartialInterface {
+ const octet TEST_CONSTANT = 0;
+ readonly attribute byte testAttribute;
+ undefined testMethod(byte foo);
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(
+ len(results[1].members),
+ 6,
+ "TestSecureContextOnInterfaceAfterPartialInterface should have six members",
+ )
+ harness.ok(
+ results[1].getExtendedAttribute("SecureContext"),
+ "Interface should have [SecureContext] extended attribute",
+ )
+ harness.ok(
+ results[1].members[0].getExtendedAttribute("SecureContext"),
+ "[SecureContext] should propagate from interface to constant members",
+ )
+ harness.ok(
+ results[1].members[1].getExtendedAttribute("SecureContext"),
+ "[SecureContext] should propagate from interface to attribute members",
+ )
+ harness.ok(
+ results[1].members[2].getExtendedAttribute("SecureContext"),
+ "[SecureContext] should propagate from interface to method members",
+ )
+ harness.ok(
+ results[1].members[3].getExtendedAttribute("SecureContext"),
+ (
+ "[SecureContext] should propagate from interface to constant members from "
+ "partial interface"
+ ),
+ )
+ harness.ok(
+ results[1].members[4].getExtendedAttribute("SecureContext"),
+ (
+ "[SecureContext] should propagate from interface to attribute members from "
+ "partial interface"
+ ),
+ )
+ harness.ok(
+ results[1].members[5].getExtendedAttribute("SecureContext"),
+ (
+ "[SecureContext] should propagate from interface to method members from partial "
+ "interface"
+ ),
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface TestSecureContextOnPartialInterface {
+ const octet TEST_CONSTANT = 0;
+ readonly attribute byte testAttribute;
+ undefined testMethod(byte foo);
+ };
+ [SecureContext]
+ partial interface TestSecureContextOnPartialInterface {
+ const octet TEST_CONSTANT_2 = 0;
+ readonly attribute byte testAttribute2;
+ undefined testMethod2(byte foo);
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(
+ len(results[0].members),
+ 6,
+ "TestSecureContextOnPartialInterface should have six members",
+ )
+ harness.ok(
+ results[0].getExtendedAttribute("SecureContext") is None,
+ "[SecureContext] should not propagate from a partial interface to the interface",
+ )
+ harness.ok(
+ results[0].members[0].getExtendedAttribute("SecureContext") is None,
+ (
+ "[SecureContext] should not propagate from a partial interface to the interface's "
+ "constant members"
+ ),
+ )
+ harness.ok(
+ results[0].members[1].getExtendedAttribute("SecureContext") is None,
+ (
+ "[SecureContext] should not propagate from a partial interface to the interface's "
+ "attribute members"
+ ),
+ )
+ harness.ok(
+ results[0].members[2].getExtendedAttribute("SecureContext") is None,
+ (
+ "[SecureContext] should not propagate from a partial interface to the interface's "
+ "method members"
+ ),
+ )
+ harness.ok(
+ results[0].members[3].getExtendedAttribute("SecureContext"),
+ "Constant members from [SecureContext] partial interface should be [SecureContext]",
+ )
+ harness.ok(
+ results[0].members[4].getExtendedAttribute("SecureContext"),
+ "Attribute members from [SecureContext] partial interface should be [SecureContext]",
+ )
+ harness.ok(
+ results[0].members[5].getExtendedAttribute("SecureContext"),
+ "Method members from [SecureContext] partial interface should be [SecureContext]",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface TestSecureContextOnInterfaceMembers {
+ const octet TEST_NON_SECURE_CONSTANT_1 = 0;
+ [SecureContext]
+ const octet TEST_SECURE_CONSTANT = 1;
+ const octet TEST_NON_SECURE_CONSTANT_2 = 2;
+ readonly attribute byte testNonSecureAttribute1;
+ [SecureContext]
+ readonly attribute byte testSecureAttribute;
+ readonly attribute byte testNonSecureAttribute2;
+ undefined testNonSecureMethod1(byte foo);
+ [SecureContext]
+ undefined testSecureMethod(byte foo);
+ undefined testNonSecureMethod2(byte foo);
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(
+ len(results[0].members),
+ 9,
+ "TestSecureContextOnInterfaceMembers should have nine members",
+ )
+ harness.ok(
+ results[0].getExtendedAttribute("SecureContext") is None,
+ "[SecureContext] on members should not propagate up to the interface",
+ )
+ harness.ok(
+ results[0].members[0].getExtendedAttribute("SecureContext") is None,
+ "Constant should not have [SecureContext] extended attribute",
+ )
+ harness.ok(
+ results[0].members[1].getExtendedAttribute("SecureContext"),
+ "Constant should have [SecureContext] extended attribute",
+ )
+ harness.ok(
+ results[0].members[2].getExtendedAttribute("SecureContext") is None,
+ "Constant should not have [SecureContext] extended attribute",
+ )
+ harness.ok(
+ results[0].members[3].getExtendedAttribute("SecureContext") is None,
+ "Attribute should not have [SecureContext] extended attribute",
+ )
+ harness.ok(
+ results[0].members[4].getExtendedAttribute("SecureContext"),
+ "Attribute should have [SecureContext] extended attribute",
+ )
+ harness.ok(
+ results[0].members[5].getExtendedAttribute("SecureContext") is None,
+ "Attribute should not have [SecureContext] extended attribute",
+ )
+ harness.ok(
+ results[0].members[6].getExtendedAttribute("SecureContext") is None,
+ "Method should not have [SecureContext] extended attribute",
+ )
+ harness.ok(
+ results[0].members[7].getExtendedAttribute("SecureContext"),
+ "Method should have [SecureContext] extended attribute",
+ )
+ harness.ok(
+ results[0].members[8].getExtendedAttribute("SecureContext") is None,
+ "Method should not have [SecureContext] extended attribute",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface TestSecureContextOnPartialInterfaceMembers {
+ };
+ partial interface TestSecureContextOnPartialInterfaceMembers {
+ const octet TEST_NON_SECURE_CONSTANT_1 = 0;
+ [SecureContext]
+ const octet TEST_SECURE_CONSTANT = 1;
+ const octet TEST_NON_SECURE_CONSTANT_2 = 2;
+ readonly attribute byte testNonSecureAttribute1;
+ [SecureContext]
+ readonly attribute byte testSecureAttribute;
+ readonly attribute byte testNonSecureAttribute2;
+ undefined testNonSecureMethod1(byte foo);
+ [SecureContext]
+ undefined testSecureMethod(byte foo);
+ undefined testNonSecureMethod2(byte foo);
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(
+ len(results[0].members),
+ 9,
+ "TestSecureContextOnPartialInterfaceMembers should have nine members",
+ )
+ harness.ok(
+ results[0].members[0].getExtendedAttribute("SecureContext") is None,
+ "Constant from partial interface should not have [SecureContext] extended attribute",
+ )
+ harness.ok(
+ results[0].members[1].getExtendedAttribute("SecureContext"),
+ "Constant from partial interface should have [SecureContext] extended attribute",
+ )
+ harness.ok(
+ results[0].members[2].getExtendedAttribute("SecureContext") is None,
+ "Constant from partial interface should not have [SecureContext] extended attribute",
+ )
+ harness.ok(
+ results[0].members[3].getExtendedAttribute("SecureContext") is None,
+ "Attribute from partial interface should not have [SecureContext] extended attribute",
+ )
+ harness.ok(
+ results[0].members[4].getExtendedAttribute("SecureContext"),
+ "Attribute from partial interface should have [SecureContext] extended attribute",
+ )
+ harness.ok(
+ results[0].members[5].getExtendedAttribute("SecureContext") is None,
+ "Attribute from partial interface should not have [SecureContext] extended attribute",
+ )
+ harness.ok(
+ results[0].members[6].getExtendedAttribute("SecureContext") is None,
+ "Method from partial interface should not have [SecureContext] extended attribute",
+ )
+ harness.ok(
+ results[0].members[7].getExtendedAttribute("SecureContext"),
+ "Method from partial interface should have [SecureContext] extended attribute",
+ )
+ harness.ok(
+ results[0].members[8].getExtendedAttribute("SecureContext") is None,
+ "Method from partial interface should not have [SecureContext] extended attribute",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [SecureContext=something]
+ interface TestSecureContextTakesNoValue1 {
+ const octet TEST_SECURE_CONSTANT = 0;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "[SecureContext] must take no arguments (testing on interface)")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestSecureContextForOverloads1 {
+ [SecureContext]
+ undefined testSecureMethod(byte foo);
+ };
+ partial interface TestSecureContextForOverloads1 {
+ undefined testSecureMethod(byte foo, byte bar);
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw,
+ (
+ "If [SecureContext] appears on an overloaded operation, then it MUST appear on all "
+ "overloads"
+ ),
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestSecureContextForOverloads2 {
+ [SecureContext]
+ undefined testSecureMethod(byte foo);
+ };
+ partial interface TestSecureContextForOverloads2 {
+ [SecureContext]
+ undefined testSecureMethod(byte foo, byte bar);
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ not threw,
+ "[SecureContext] can appear on an overloaded operation if it appears on all overloads",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [SecureContext]
+ interface TestSecureContextOnInterfaceAndMember {
+ [SecureContext]
+ undefined testSecureMethod(byte foo);
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw, "[SecureContext] must not appear on an interface and interface member"
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestSecureContextOnPartialInterfaceAndMember {
+ };
+ [SecureContext]
+ partial interface TestSecureContextOnPartialInterfaceAndMember {
+ [SecureContext]
+ undefined testSecureMethod(byte foo);
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw,
+ (
+ "[SecureContext] must not appear on a partial interface and one of the partial "
+ "interface's member's"
+ ),
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [SecureContext]
+ interface TestSecureContextOnInterfaceAndPartialInterfaceMember {
+ };
+ partial interface TestSecureContextOnInterfaceAndPartialInterfaceMember {
+ [SecureContext]
+ undefined testSecureMethod(byte foo);
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw,
+ (
+ "[SecureContext] must not appear on an interface and one of its partial interface's "
+ "member's"
+ ),
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [SecureContext]
+ interface TestSecureContextOnInheritedInterface {
+ };
+ interface TestSecureContextNotOnInheritingInterface : TestSecureContextOnInheritedInterface {
+ undefined testSecureMethod(byte foo);
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw,
+ (
+ "[SecureContext] must appear on interfaces that inherit from another [SecureContext] "
+ "interface"
+ ),
+ )
+
+ # Test 'includes'.
+ parser = parser.reset()
+ parser.parse(
+ """
+ [SecureContext]
+ interface TestSecureContextInterfaceThatIncludesNonSecureContextMixin {
+ const octet TEST_CONSTANT = 0;
+ };
+ interface mixin TestNonSecureContextMixin {
+ const octet TEST_CONSTANT_2 = 0;
+ readonly attribute byte testAttribute2;
+ undefined testMethod2(byte foo);
+ };
+ TestSecureContextInterfaceThatIncludesNonSecureContextMixin includes TestNonSecureContextMixin;
+ """
+ )
+ results = parser.finish()
+ harness.check(
+ len(results[0].members),
+ 4,
+ (
+ "TestSecureContextInterfaceThatImplementsNonSecureContextInterface should have four "
+ "members"
+ ),
+ )
+ harness.ok(
+ results[0].getExtendedAttribute("SecureContext"),
+ "Interface should have [SecureContext] extended attribute",
+ )
+ harness.ok(
+ results[0].members[0].getExtendedAttribute("SecureContext"),
+ (
+ "[SecureContext] should propagate from interface to constant members even when other "
+ "members are copied from a non-[SecureContext] interface"
+ ),
+ )
+ harness.ok(
+ results[0].members[1].getExtendedAttribute("SecureContext") is None,
+ "Constants copied from non-[SecureContext] mixin should not be [SecureContext]",
+ )
+ harness.ok(
+ results[0].members[2].getExtendedAttribute("SecureContext") is None,
+ "Attributes copied from non-[SecureContext] mixin should not be [SecureContext]",
+ )
+ harness.ok(
+ results[0].members[3].getExtendedAttribute("SecureContext") is None,
+ "Methods copied from non-[SecureContext] mixin should not be [SecureContext]",
+ )
+
+ # Test SecureContext and LegacyNoInterfaceObject
+ parser = parser.reset()
+ parser.parse(
+ """
+ [LegacyNoInterfaceObject, SecureContext]
+ interface TestSecureContextLegacyNoInterfaceObject {
+ undefined testSecureMethod(byte foo);
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(
+ len(results[0].members),
+ 1,
+ "TestSecureContextLegacyNoInterfaceObject should have only one member",
+ )
+ harness.ok(
+ results[0].getExtendedAttribute("SecureContext"),
+ "Interface should have [SecureContext] extended attribute",
+ )
+ harness.ok(
+ results[0].members[0].getExtendedAttribute("SecureContext"),
+ "Interface member should have [SecureContext] extended attribute",
+ )
diff --git a/dom/bindings/parser/tests/test_special_method_signature_mismatch.py b/dom/bindings/parser/tests/test_special_method_signature_mismatch.py
new file mode 100644
index 0000000000..edbb396bd3
--- /dev/null
+++ b/dom/bindings/parser/tests/test_special_method_signature_mismatch.py
@@ -0,0 +1,256 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface SpecialMethodSignatureMismatch1 {
+ getter long long foo(long index);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface SpecialMethodSignatureMismatch2 {
+ getter undefined foo(unsigned long index);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface SpecialMethodSignatureMismatch3 {
+ getter boolean foo(unsigned long index, boolean extraArg);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface SpecialMethodSignatureMismatch4 {
+ getter boolean foo(unsigned long... index);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface SpecialMethodSignatureMismatch5 {
+ getter boolean foo(optional unsigned long index);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface SpecialMethodSignatureMismatch6 {
+ getter boolean foo();
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface SpecialMethodSignatureMismatch7 {
+ deleter long long foo(long index);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface SpecialMethodSignatureMismatch9 {
+ deleter boolean foo(unsigned long index, boolean extraArg);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface SpecialMethodSignatureMismatch10 {
+ deleter boolean foo(unsigned long... index);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface SpecialMethodSignatureMismatch11 {
+ deleter boolean foo(optional unsigned long index);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface SpecialMethodSignatureMismatch12 {
+ deleter boolean foo();
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface SpecialMethodSignatureMismatch13 {
+ setter long long foo(long index, long long value);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface SpecialMethodSignatureMismatch15 {
+ setter boolean foo(unsigned long index, boolean value, long long extraArg);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface SpecialMethodSignatureMismatch16 {
+ setter boolean foo(unsigned long index, boolean... value);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface SpecialMethodSignatureMismatch17 {
+ setter boolean foo(unsigned long index, optional boolean value);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface SpecialMethodSignatureMismatch18 {
+ setter boolean foo();
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/dom/bindings/parser/tests/test_special_methods.py b/dom/bindings/parser/tests/test_special_methods.py
new file mode 100644
index 0000000000..64b6cea9e0
--- /dev/null
+++ b/dom/bindings/parser/tests/test_special_methods.py
@@ -0,0 +1,117 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ interface SpecialMethods {
+ getter long long (unsigned long index);
+ setter long long (unsigned long index, long long value);
+ getter boolean (DOMString name);
+ setter boolean (DOMString name, boolean value);
+ deleter boolean (DOMString name);
+ readonly attribute unsigned long length;
+ };
+
+ interface SpecialMethodsCombination {
+ getter deleter boolean (DOMString name);
+ };
+ """
+ )
+
+ results = parser.finish()
+
+ def checkMethod(
+ method,
+ QName,
+ name,
+ static=False,
+ getter=False,
+ setter=False,
+ deleter=False,
+ legacycaller=False,
+ stringifier=False,
+ ):
+ harness.ok(isinstance(method, WebIDL.IDLMethod), "Should be an IDLMethod")
+ harness.check(method.identifier.QName(), QName, "Method has the right QName")
+ harness.check(method.identifier.name, name, "Method has the right name")
+ harness.check(method.isStatic(), static, "Method has the correct static value")
+ harness.check(method.isGetter(), getter, "Method has the correct getter value")
+ harness.check(method.isSetter(), setter, "Method has the correct setter value")
+ harness.check(
+ method.isDeleter(), deleter, "Method has the correct deleter value"
+ )
+ harness.check(
+ method.isLegacycaller(),
+ legacycaller,
+ "Method has the correct legacycaller value",
+ )
+ harness.check(
+ method.isStringifier(),
+ stringifier,
+ "Method has the correct stringifier value",
+ )
+
+ harness.check(len(results), 2, "Expect 2 interfaces")
+
+ iface = results[0]
+ harness.check(len(iface.members), 6, "Expect 6 members")
+
+ checkMethod(
+ iface.members[0],
+ "::SpecialMethods::__indexedgetter",
+ "__indexedgetter",
+ getter=True,
+ )
+ checkMethod(
+ iface.members[1],
+ "::SpecialMethods::__indexedsetter",
+ "__indexedsetter",
+ setter=True,
+ )
+ checkMethod(
+ iface.members[2],
+ "::SpecialMethods::__namedgetter",
+ "__namedgetter",
+ getter=True,
+ )
+ checkMethod(
+ iface.members[3],
+ "::SpecialMethods::__namedsetter",
+ "__namedsetter",
+ setter=True,
+ )
+ checkMethod(
+ iface.members[4],
+ "::SpecialMethods::__nameddeleter",
+ "__nameddeleter",
+ deleter=True,
+ )
+
+ iface = results[1]
+ harness.check(len(iface.members), 1, "Expect 1 member")
+
+ checkMethod(
+ iface.members[0],
+ "::SpecialMethodsCombination::__namedgetterdeleter",
+ "__namedgetterdeleter",
+ getter=True,
+ deleter=True,
+ )
+
+ parser = parser.reset()
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface IndexedDeleter {
+ deleter undefined(unsigned long index);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "There are no indexed deleters")
diff --git a/dom/bindings/parser/tests/test_special_methods_uniqueness.py b/dom/bindings/parser/tests/test_special_methods_uniqueness.py
new file mode 100644
index 0000000000..b7781207ee
--- /dev/null
+++ b/dom/bindings/parser/tests/test_special_methods_uniqueness.py
@@ -0,0 +1,51 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface SpecialMethodUniqueness1 {
+ getter deleter boolean (DOMString name);
+ getter boolean (DOMString name);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface SpecialMethodUniqueness1 {
+ deleter boolean (DOMString name);
+ getter deleter boolean (DOMString name);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface SpecialMethodUniqueness1 {
+ setter boolean (DOMString name);
+ setter boolean (DOMString name);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/dom/bindings/parser/tests/test_stringifier.py b/dom/bindings/parser/tests/test_stringifier.py
new file mode 100644
index 0000000000..13c7ce4af8
--- /dev/null
+++ b/dom/bindings/parser/tests/test_stringifier.py
@@ -0,0 +1,196 @@
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ interface TestStringifier {
+ stringifier;
+ };
+ """
+ )
+
+ results = parser.finish()
+
+ harness.ok(
+ isinstance(results[0].members[0], WebIDL.IDLMethod),
+ "Stringifer should be method",
+ )
+
+ parser = parser.reset()
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestStringifier {
+ stringifier;
+ stringifier;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should not allow two 'stringifier;'")
+
+ parser = parser.reset()
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestStringifier {
+ stringifier;
+ stringifier DOMString foo();
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should not allow a 'stringifier;' and a 'stringifier()'")
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface TestStringifier {
+ stringifier attribute DOMString foo;
+ };
+ """
+ )
+ results = parser.finish()
+ harness.ok(
+ isinstance(results[0].members[0], WebIDL.IDLAttribute),
+ "Stringifier attribute should be an attribute",
+ )
+ stringifier = results[0].members[1]
+ harness.ok(
+ isinstance(stringifier, WebIDL.IDLMethod),
+ "Stringifier attribute should insert a method",
+ )
+ harness.ok(stringifier.isStringifier(), "Inserted method should be a stringifier")
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface TestStringifier {};
+ interface mixin TestStringifierMixin {
+ stringifier attribute DOMString foo;
+ };
+ TestStringifier includes TestStringifierMixin;
+ """
+ )
+ results = parser.finish()
+ harness.ok(
+ isinstance(results[0].members[0], WebIDL.IDLAttribute),
+ "Stringifier attribute should be an attribute",
+ )
+ stringifier = results[0].members[1]
+ harness.ok(
+ isinstance(stringifier, WebIDL.IDLMethod),
+ "Stringifier attribute should insert a method",
+ )
+ harness.ok(stringifier.isStringifier(), "Inserted method should be a stringifier")
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface TestStringifier {
+ stringifier attribute USVString foo;
+ };
+ """
+ )
+ results = parser.finish()
+ stringifier = results[0].members[1]
+ harness.ok(
+ stringifier.signatures()[0][0].isUSVString(),
+ "Stringifier attributes should allow USVString",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface TestStringifier {
+ [Throws, NeedsSubjectPrincipal]
+ stringifier attribute USVString foo;
+ };
+ """
+ )
+ results = parser.finish()
+ stringifier = results[0].members[1]
+ harness.ok(
+ stringifier.getExtendedAttribute("Throws"),
+ "Stringifier attributes should support [Throws]",
+ )
+ harness.ok(
+ stringifier.getExtendedAttribute("NeedsSubjectPrincipal"),
+ "Stringifier attributes should support [NeedsSubjectPrincipal]",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface TestStringifier {
+ stringifier attribute UTF8String foo;
+ };
+ """
+ )
+ results = parser.finish()
+ stringifier = results[0].members[1]
+ harness.ok(
+ stringifier.signatures()[0][0].isUTF8String(),
+ "Stringifier attributes should allow UTF8String",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestStringifier {
+ stringifier attribute ByteString foo;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should not allow ByteString")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestStringifier {
+ stringifier;
+ stringifier attribute DOMString foo;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should not allow a 'stringifier;' and a stringifier attribute")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface TestStringifier {
+ stringifier attribute DOMString foo;
+ stringifier attribute DOMString bar;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should not allow multiple stringifier attributes")
diff --git a/dom/bindings/parser/tests/test_toJSON.py b/dom/bindings/parser/tests/test_toJSON.py
new file mode 100644
index 0000000000..42bad30e50
--- /dev/null
+++ b/dom/bindings/parser/tests/test_toJSON.py
@@ -0,0 +1,309 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Test {
+ object toJSON();
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(not threw, "Should allow a toJSON method.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Test {
+ object toJSON(object arg);
+ object toJSON(long arg);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow overloads of a toJSON method.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Test {
+ object toJSON(object arg);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should not allow a toJSON method with arguments.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Test {
+ long toJSON();
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(not threw, "Should allow a toJSON method with 'long' as return type.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Test {
+ [Default] object toJSON();
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ not threw, "Should allow a default toJSON method with 'object' as return type."
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Test {
+ [Default] long toJSON();
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw,
+ "Should not allow a default toJSON method with non-'object' as return type.",
+ )
+
+ JsonTypes = [
+ "byte",
+ "octet",
+ "short",
+ "unsigned short",
+ "long",
+ "unsigned long",
+ "long long",
+ "unsigned long long",
+ "float",
+ "unrestricted float",
+ "double",
+ "unrestricted double",
+ "boolean",
+ "DOMString",
+ "ByteString",
+ "UTF8String",
+ "USVString",
+ "Enum",
+ "InterfaceWithToJSON",
+ "object",
+ ]
+
+ nonJsonTypes = [
+ "InterfaceWithoutToJSON",
+ "any",
+ "Int8Array",
+ "Int16Array",
+ "Int32Array",
+ "Uint8Array",
+ "Uint16Array",
+ "Uint32Array",
+ "Uint8ClampedArray",
+ "Float32Array",
+ "Float64Array",
+ "ArrayBuffer",
+ ]
+
+ def doTest(testIDL, shouldThrow, description):
+ p = parser.reset()
+ threw = False
+ try:
+ p.parse(
+ testIDL
+ + """
+ enum Enum { "a", "b", "c" };
+ interface InterfaceWithToJSON { long toJSON(); };
+ interface InterfaceWithoutToJSON {};
+ """
+ )
+ p.finish()
+ except Exception as x:
+ threw = True
+ harness.ok(x.message == "toJSON method has non-JSON return type", x)
+ harness.check(threw, shouldThrow, description)
+
+ for type in JsonTypes:
+ doTest(
+ "interface Test { %s toJSON(); };" % type,
+ False,
+ "%s should be a JSON type" % type,
+ )
+
+ doTest(
+ "interface Test { sequence<%s> toJSON(); };" % type,
+ False,
+ "sequence<%s> should be a JSON type" % type,
+ )
+
+ doTest(
+ "dictionary Foo { %s foo; }; " "interface Test { Foo toJSON(); }; " % type,
+ False,
+ "dictionary containing only JSON type (%s) should be a JSON type" % type,
+ )
+
+ doTest(
+ "dictionary Foo { %s foo; }; dictionary Bar : Foo { }; "
+ "interface Test { Bar toJSON(); }; " % type,
+ False,
+ "dictionary whose ancestors only contain JSON types should be a JSON type",
+ )
+
+ doTest(
+ "dictionary Foo { any foo; }; dictionary Bar : Foo { %s bar; };"
+ "interface Test { Bar toJSON(); };" % type,
+ True,
+ "dictionary whose ancestors contain non-JSON types should not be a JSON type",
+ )
+
+ doTest(
+ "interface Test { record<DOMString, %s> toJSON(); };" % type,
+ False,
+ "record<DOMString, %s> should be a JSON type" % type,
+ )
+
+ doTest(
+ "interface Test { record<ByteString, %s> toJSON(); };" % type,
+ False,
+ "record<ByteString, %s> should be a JSON type" % type,
+ )
+
+ doTest(
+ "interface Test { record<UTF8String, %s> toJSON(); };" % type,
+ False,
+ "record<UTF8String, %s> should be a JSON type" % type,
+ )
+
+ doTest(
+ "interface Test { record<USVString, %s> toJSON(); };" % type,
+ False,
+ "record<USVString, %s> should be a JSON type" % type,
+ )
+
+ otherUnionType = "Foo" if type != "object" else "long"
+ doTest(
+ "interface Foo { object toJSON(); };"
+ "interface Test { (%s or %s) toJSON(); };" % (otherUnionType, type),
+ False,
+ "union containing only JSON types (%s or %s) should be a JSON type"
+ % (otherUnionType, type),
+ )
+
+ doTest(
+ "interface test { %s? toJSON(); };" % type,
+ False,
+ "Nullable type (%s) should be a JSON type" % type,
+ )
+
+ doTest(
+ "interface Foo : InterfaceWithoutToJSON { %s toJSON(); };"
+ "interface Test { Foo toJSON(); };" % type,
+ False,
+ "interface with toJSON should be a JSON type",
+ )
+
+ doTest(
+ "interface Foo : InterfaceWithToJSON { };" "interface Test { Foo toJSON(); };",
+ False,
+ "inherited interface with toJSON should be a JSON type",
+ )
+
+ for type in nonJsonTypes:
+ doTest(
+ "interface Test { %s toJSON(); };" % type,
+ True,
+ "%s should not be a JSON type" % type,
+ )
+
+ doTest(
+ "interface Test { sequence<%s> toJSON(); };" % type,
+ True,
+ "sequence<%s> should not be a JSON type" % type,
+ )
+
+ doTest(
+ "dictionary Foo { %s foo; }; " "interface Test { Foo toJSON(); }; " % type,
+ True,
+ "Dictionary containing a non-JSON type (%s) should not be a JSON type"
+ % type,
+ )
+
+ doTest(
+ "dictionary Foo { %s foo; }; dictionary Bar : Foo { }; "
+ "interface Test { Bar toJSON(); }; " % type,
+ True,
+ "dictionary whose ancestors only contain non-JSON types should not be a JSON type",
+ )
+
+ doTest(
+ "interface Test { record<DOMString, %s> toJSON(); };" % type,
+ True,
+ "record<DOMString, %s> should not be a JSON type" % type,
+ )
+
+ doTest(
+ "interface Test { record<ByteString, %s> toJSON(); };" % type,
+ True,
+ "record<ByteString, %s> should not be a JSON type" % type,
+ )
+
+ doTest(
+ "interface Test { record<USVString, %s> toJSON(); };" % type,
+ True,
+ "record<USVString, %s> should not be a JSON type" % type,
+ )
+
+ if type != "any":
+ doTest(
+ "interface Foo { object toJSON(); }; "
+ "interface Test { (Foo or %s) toJSON(); };" % type,
+ True,
+ "union containing a non-JSON type (%s) should not be a JSON type"
+ % type,
+ )
+
+ doTest(
+ "interface test { %s? toJSON(); };" % type,
+ True,
+ "Nullable type (%s) should not be a JSON type" % type,
+ )
+
+ doTest(
+ "dictionary Foo { long foo; any bar; };" "interface Test { Foo toJSON(); };",
+ True,
+ "dictionary containing a non-JSON type should not be a JSON type",
+ )
+
+ doTest(
+ "interface Foo : InterfaceWithoutToJSON { }; "
+ "interface Test { Foo toJSON(); };",
+ True,
+ "interface without toJSON should not be a JSON type",
+ )
diff --git a/dom/bindings/parser/tests/test_treatNonCallableAsNull.py b/dom/bindings/parser/tests/test_treatNonCallableAsNull.py
new file mode 100644
index 0000000000..fb44cf7fdf
--- /dev/null
+++ b/dom/bindings/parser/tests/test_treatNonCallableAsNull.py
@@ -0,0 +1,77 @@
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ [TreatNonCallableAsNull] callback Function = any(any... arguments);
+
+ interface TestTreatNonCallableAsNull1 {
+ attribute Function? onfoo;
+ attribute Function onbar;
+ };
+ """
+ )
+
+ results = parser.finish()
+
+ iface = results[1]
+ attr = iface.members[0]
+ harness.check(attr.type.treatNonCallableAsNull(), True, "Got the expected value")
+ attr = iface.members[1]
+ harness.check(attr.type.treatNonCallableAsNull(), False, "Got the expected value")
+
+ parser = parser.reset()
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ callback Function = any(any... arguments);
+
+ interface TestTreatNonCallableAsNull2 {
+ [TreatNonCallableAsNull] attribute Function onfoo;
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ callback Function = any(any... arguments);
+
+ [TreatNonCallableAsNull]
+ interface TestTreatNonCallableAsNull3 {
+ attribute Function onfoo;
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+
+ threw = False
+ try:
+ parser.parse(
+ """
+ [TreatNonCallableAsNull, LegacyTreatNonObjectAsNull]
+ callback Function = any(any... arguments);
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/dom/bindings/parser/tests/test_typedef.py b/dom/bindings/parser/tests/test_typedef.py
new file mode 100644
index 0000000000..8179f90d5c
--- /dev/null
+++ b/dom/bindings/parser/tests/test_typedef.py
@@ -0,0 +1,94 @@
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ typedef long mylong;
+ typedef long? mynullablelong;
+ interface Foo {
+ const mylong X = 5;
+ undefined foo(optional mynullablelong arg = 7);
+ undefined bar(optional mynullablelong arg = null);
+ undefined baz(mylong arg);
+ };
+ """
+ )
+
+ results = parser.finish()
+
+ harness.check(
+ results[2].members[1].signatures()[0][1][0].type.name,
+ "LongOrNull",
+ "Should expand typedefs",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ typedef long? mynullablelong;
+ interface Foo {
+ undefined foo(mynullablelong? Y);
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown on nullable inside nullable arg.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ typedef long? mynullablelong;
+ interface Foo {
+ const mynullablelong? X = 5;
+ };
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown on nullable inside nullable const.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Foo {
+ const mynullablelong? X = 5;
+ };
+ typedef long? mynullablelong;
+ """
+ )
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Should have thrown on nullable inside nullable const typedef "
+ "after interface.",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface Foo {
+ const mylong X = 5;
+ };
+ typedef long mylong;
+ """
+ )
+
+ results = parser.finish()
+
+ harness.check(
+ results[0].members[0].type.name,
+ "Long",
+ "Should expand typedefs that come before interface",
+ )
diff --git a/dom/bindings/parser/tests/test_typedef_identifier_conflict.py b/dom/bindings/parser/tests/test_typedef_identifier_conflict.py
new file mode 100644
index 0000000000..90e45ddb7d
--- /dev/null
+++ b/dom/bindings/parser/tests/test_typedef_identifier_conflict.py
@@ -0,0 +1,19 @@
+def WebIDLTest(parser, harness):
+ exception = None
+ try:
+ parser.parse(
+ """
+ typedef long foo;
+ typedef long foo;
+ """
+ )
+
+ parser.finish()
+ except Exception as e:
+ exception = e
+
+ harness.ok(exception, "Should have thrown.")
+ harness.ok(
+ "Multiple unresolvable definitions of identifier 'foo'" in str(exception),
+ "Should have a sane exception message",
+ )
diff --git a/dom/bindings/parser/tests/test_undefined.py b/dom/bindings/parser/tests/test_undefined.py
new file mode 100644
index 0000000000..3b6f18292f
--- /dev/null
+++ b/dom/bindings/parser/tests/test_undefined.py
@@ -0,0 +1,243 @@
+def WebIDLTest(parser, harness):
+ try:
+ parser.parse(
+ """
+ dictionary Dict {
+ undefined undefinedMember;
+ double bar;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "undefined must not be used as the type of a dictionary member")
+
+ parser = parser.reset()
+ threw = False
+
+ try:
+ parser.parse(
+ """
+ dictionary Dict {
+ (undefined or double) undefinedMemberOfUnionInDict;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "undefined must not be used as the type of a dictionary member, "
+ "whether directly or in a union",
+ )
+
+ parser = parser.reset()
+ threw = False
+
+ try:
+ parser.parse(
+ """
+ interface Foo {
+ double bar(undefined foo);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "undefined must not be used as the type of an argument in any "
+ "circumstance (so not as the argument of a regular operation)",
+ )
+
+ parser = parser.reset()
+ threw = False
+
+ try:
+ parser.parse(
+ """
+ interface Foo {
+ getter double(undefined name);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "undefined must not be used as the type of an argument in any "
+ "circumstance (so not as the argument of a getter)",
+ )
+
+ parser = parser.reset()
+ threw = False
+
+ try:
+ parser.parse(
+ """
+ interface Foo {
+ setter undefined(DOMString name, undefined value);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "undefined must not be used as the type of an argument in any "
+ "circumstance (so not as the argument of a setter)",
+ )
+
+ parser = parser.reset()
+ threw = False
+
+ try:
+ parser.parse(
+ """
+ interface Foo {
+ deleter undefined (undefined name);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "undefined must not be used as the type of an argument in any "
+ "circumstance (so not as the argument of a deleter)",
+ )
+
+ parser = parser.reset()
+ threw = False
+
+ try:
+ parser.parse(
+ """
+ interface Foo {
+ constructor (undefined foo);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "undefined must not be used as the type of an argument in any "
+ "circumstance (so not as the argument of a constructor)",
+ )
+
+ parser = parser.reset()
+ threw = False
+
+ try:
+ parser.parse(
+ """
+ callback Callback = undefined (undefined foo);
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "undefined must not be used as the type of an argument in any "
+ "circumstance (so not as the argument of a callback)",
+ )
+
+ parser = parser.reset()
+ threw = False
+
+ try:
+ parser.parse(
+ """
+ interface Foo {
+ async iterable(undefined name);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "undefined must not be used as the type of an argument in any "
+ "circumstance (so not as the argument of an async iterable "
+ "iterator)",
+ )
+
+ parser = parser.reset()
+ threw = False
+
+ try:
+ parser.parse(
+ """
+ interface Foo {
+ static double bar(undefined foo);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "undefined must not be used as the type of an argument in any "
+ "circumstance (so not as the argument of a static operation)",
+ )
+
+ parser = parser.reset()
+ threw = False
+
+ try:
+ parser.parse(
+ """
+ interface Foo {
+ const undefined FOO = undefined;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "undefined is not a valid type for a constant",
+ )
+
+ parser = parser.reset()
+ threw = False
+
+ try:
+ parser.parse(
+ """
+ interface Foo {
+ const any FOO = undefined;
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "undefined is not a valid value for a constant",
+ )
diff --git a/dom/bindings/parser/tests/test_unenumerable_own_properties.py b/dom/bindings/parser/tests/test_unenumerable_own_properties.py
new file mode 100644
index 0000000000..37e0167696
--- /dev/null
+++ b/dom/bindings/parser/tests/test_unenumerable_own_properties.py
@@ -0,0 +1,71 @@
+def WebIDLTest(parser, harness):
+
+ parser.parse(
+ """
+ interface Foo {};
+ [LegacyUnenumerableNamedProperties]
+ interface Bar : Foo {
+ getter long(DOMString name);
+ };
+ interface Baz : Bar {
+ getter long(DOMString name);
+ };
+ """
+ )
+ results = parser.finish()
+ harness.check(len(results), 3, "Should have three interfaces")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [LegacyUnenumerableNamedProperties]
+ interface NoNamedGetter {
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [LegacyUnenumerableNamedProperties=Foo]
+ interface ShouldNotHaveArg {
+ getter long(DOMString name);
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ [LegacyUnenumerableNamedProperties]
+ interface Foo {
+ getter long(DOMString name);
+ };
+ interface Bar : Foo {};
+ [LegacyUnenumerableNamedProperties]
+ interface Baz : Bar {
+ getter long(DOMString name);
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(threw, "Should have thrown.")
diff --git a/dom/bindings/parser/tests/test_unforgeable.py b/dom/bindings/parser/tests/test_unforgeable.py
new file mode 100644
index 0000000000..18946bcc50
--- /dev/null
+++ b/dom/bindings/parser/tests/test_unforgeable.py
@@ -0,0 +1,311 @@
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ interface Child : Parent {
+ };
+ interface Parent {
+ [LegacyUnforgeable] readonly attribute long foo;
+ };
+ """
+ )
+
+ results = parser.finish()
+ harness.check(
+ len(results),
+ 2,
+ "Should be able to inherit from an interface with "
+ "[LegacyUnforgeable] properties.",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface Child : Parent {
+ const short foo = 10;
+ };
+ interface Parent {
+ [LegacyUnforgeable] readonly attribute long foo;
+ };
+ """
+ )
+
+ results = parser.finish()
+ harness.check(
+ len(results),
+ 2,
+ "Should be able to inherit from an interface with "
+ "[LegacyUnforgeable] properties even if we have a constant with "
+ "the same name.",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface Child : Parent {
+ static attribute short foo;
+ };
+ interface Parent {
+ [LegacyUnforgeable] readonly attribute long foo;
+ };
+ """
+ )
+
+ results = parser.finish()
+ harness.check(
+ len(results),
+ 2,
+ "Should be able to inherit from an interface with "
+ "[LegacyUnforgeable] properties even if we have a static attribute "
+ "with the same name.",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface Child : Parent {
+ static undefined foo();
+ };
+ interface Parent {
+ [LegacyUnforgeable] readonly attribute long foo;
+ };
+ """
+ )
+
+ results = parser.finish()
+ harness.check(
+ len(results),
+ 2,
+ "Should be able to inherit from an interface with "
+ "[LegacyUnforgeable] properties even if we have a static operation "
+ "with the same name.",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Child : Parent {
+ undefined foo();
+ };
+ interface Parent {
+ [LegacyUnforgeable] readonly attribute long foo;
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw,
+ "Should have thrown when shadowing unforgeable attribute on "
+ "parent with operation.",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Child : Parent {
+ undefined foo();
+ };
+ interface Parent {
+ [LegacyUnforgeable] undefined foo();
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw,
+ "Should have thrown when shadowing unforgeable operation on "
+ "parent with operation.",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Child : Parent {
+ attribute short foo;
+ };
+ interface Parent {
+ [LegacyUnforgeable] readonly attribute long foo;
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw,
+ "Should have thrown when shadowing unforgeable attribute on "
+ "parent with attribute.",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Child : Parent {
+ attribute short foo;
+ };
+ interface Parent {
+ [LegacyUnforgeable] undefined foo();
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+ harness.ok(
+ threw,
+ "Should have thrown when shadowing unforgeable operation on "
+ "parent with attribute.",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface Child : Parent {
+ };
+ interface Parent {};
+ interface mixin Mixin {
+ [LegacyUnforgeable] readonly attribute long foo;
+ };
+ Parent includes Mixin;
+ """
+ )
+
+ results = parser.finish()
+ harness.check(
+ len(results),
+ 4,
+ "Should be able to inherit from an interface with a "
+ "mixin with [LegacyUnforgeable] properties.",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Child : Parent {
+ undefined foo();
+ };
+ interface Parent {};
+ interface mixin Mixin {
+ [LegacyUnforgeable] readonly attribute long foo;
+ };
+ Parent includes Mixin;
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Should have thrown when shadowing unforgeable attribute "
+ "of parent's consequential interface.",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Child : Parent {
+ };
+ interface Parent : GrandParent {};
+ interface GrandParent {};
+ interface mixin Mixin {
+ [LegacyUnforgeable] readonly attribute long foo;
+ };
+ GrandParent includes Mixin;
+ interface mixin ChildMixin {
+ undefined foo();
+ };
+ Child includes ChildMixin;
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Should have thrown when our consequential interface shadows unforgeable attribute "
+ "of ancestor's consequential interface.",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface Child : Parent {
+ };
+ interface Parent : GrandParent {};
+ interface GrandParent {};
+ interface mixin Mixin {
+ [LegacyUnforgeable] undefined foo();
+ };
+ GrandParent includes Mixin;
+ interface mixin ChildMixin {
+ undefined foo();
+ };
+ Child includes ChildMixin;
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Should have thrown when our consequential interface shadows unforgeable operation "
+ "of ancestor's consequential interface.",
+ )
+
+ parser = parser.reset()
+ parser.parse(
+ """
+ interface iface {
+ [LegacyUnforgeable] attribute long foo;
+ };
+ """
+ )
+
+ results = parser.finish()
+ harness.check(
+ len(results), 1, "Should allow writable [LegacyUnforgeable] attribute."
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface iface {
+ [LegacyUnforgeable] static readonly attribute long foo;
+ };
+ """
+ )
+
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown for static [LegacyUnforgeable] attribute.")
diff --git a/dom/bindings/parser/tests/test_union.py b/dom/bindings/parser/tests/test_union.py
new file mode 100644
index 0000000000..675e2d0264
--- /dev/null
+++ b/dom/bindings/parser/tests/test_union.py
@@ -0,0 +1,196 @@
+import string
+
+# We'd like to use itertools.chain but it's 2.6 or higher.
+
+
+def chain(*iterables):
+ # chain('ABC', 'DEF') --> A B C D E F
+ for it in iterables:
+ for element in it:
+ yield element
+
+
+# We'd like to use itertools.combinations but it's 2.6 or higher.
+def combinations(iterable, r):
+ # combinations('ABCD', 2) --> AB AC AD BC BD CD
+ # combinations(range(4), 3) --> 012 013 023 123
+ pool = tuple(iterable)
+ n = len(pool)
+ if r > n:
+ return
+ indices = list(range(r))
+ yield tuple(pool[i] for i in indices)
+ while True:
+ for i in reversed(range(r)):
+ if indices[i] != i + n - r:
+ break
+ else:
+ return
+ indices[i] += 1
+ for j in range(i + 1, r):
+ indices[j] = indices[j - 1] + 1
+ yield tuple(pool[i] for i in indices)
+
+
+# We'd like to use itertools.combinations_with_replacement but it's 2.7 or
+# higher.
+def combinations_with_replacement(iterable, r):
+ # combinations_with_replacement('ABC', 2) --> AA AB AC BB BC CC
+ pool = tuple(iterable)
+ n = len(pool)
+ if not n and r:
+ return
+ indices = [0] * r
+ yield tuple(pool[i] for i in indices)
+ while True:
+ for i in reversed(range(r)):
+ if indices[i] != n - 1:
+ break
+ else:
+ return
+ indices[i:] = [indices[i] + 1] * (r - i)
+ yield tuple(pool[i] for i in indices)
+
+
+def WebIDLTest(parser, harness):
+ types = [
+ "float",
+ "double",
+ "short",
+ "unsigned short",
+ "long",
+ "unsigned long",
+ "long long",
+ "unsigned long long",
+ "boolean",
+ "byte",
+ "octet",
+ "DOMString",
+ "ByteString",
+ "USVString",
+ # "sequence<float>",
+ "object",
+ "ArrayBuffer",
+ # "Date",
+ "TestInterface1",
+ "TestInterface2",
+ ]
+
+ testPre = """
+ interface TestInterface1 {
+ };
+ interface TestInterface2 {
+ };
+ """
+
+ interface = (
+ testPre
+ + """
+ interface PrepareForTest {
+ """
+ )
+ for (i, type) in enumerate(types):
+ interface += string.Template(
+ """
+ readonly attribute ${type} attr${i};
+ """
+ ).substitute(i=i, type=type)
+ interface += """
+ };
+ """
+
+ parser.parse(interface)
+ results = parser.finish()
+
+ iface = results[2]
+
+ parser = parser.reset()
+
+ def typesAreDistinguishable(t):
+ return all(u[0].isDistinguishableFrom(u[1]) for u in combinations(t, 2))
+
+ def typesAreNotDistinguishable(t):
+ return any(not u[0].isDistinguishableFrom(u[1]) for u in combinations(t, 2))
+
+ def unionTypeName(t):
+ if len(t) > 2:
+ t[0:2] = [unionTypeName(t[0:2])]
+ return "(" + " or ".join(t) + ")"
+
+ # typeCombinations is an iterable of tuples containing the name of the type
+ # as a string and the parsed IDL type.
+ def unionTypes(typeCombinations, predicate):
+ for c in typeCombinations:
+ if predicate(t[1] for t in c):
+ yield unionTypeName([t[0] for t in c])
+
+ # We limit invalid union types with a union member type to the subset of 3
+ # types with one invalid combination.
+ # typeCombinations is an iterable of tuples containing the name of the type
+ # as a string and the parsed IDL type.
+ def invalidUnionWithUnion(typeCombinations):
+ for c in typeCombinations:
+ if (
+ typesAreNotDistinguishable((c[0][1], c[1][1]))
+ and typesAreDistinguishable((c[1][1], c[2][1]))
+ and typesAreDistinguishable((c[0][1], c[2][1]))
+ ):
+ yield unionTypeName([t[0] for t in c])
+
+ # Create a list of tuples containing the name of the type as a string and
+ # the parsed IDL type.
+ types = zip(types, (a.type for a in iface.members))
+
+ validUnionTypes = chain(
+ unionTypes(combinations(types, 2), typesAreDistinguishable),
+ unionTypes(combinations(types, 3), typesAreDistinguishable),
+ )
+ invalidUnionTypes = chain(
+ unionTypes(combinations_with_replacement(types, 2), typesAreNotDistinguishable),
+ invalidUnionWithUnion(combinations(types, 3)),
+ )
+ interface = (
+ testPre
+ + """
+ interface TestUnion {
+ """
+ )
+ for (i, type) in enumerate(validUnionTypes):
+ interface += string.Template(
+ """
+ undefined method${i}(${type} arg);
+ ${type} returnMethod${i}();
+ attribute ${type} attr${i};
+ undefined optionalMethod${i}(${type}? arg);
+ """
+ ).substitute(i=i, type=type)
+ interface += """
+ };
+ """
+ parser.parse(interface)
+ results = parser.finish()
+
+ parser = parser.reset()
+
+ for invalid in invalidUnionTypes:
+ interface = (
+ testPre
+ + string.Template(
+ """
+ interface TestUnion {
+ undefined method(${type} arg);
+ };
+ """
+ ).substitute(type=invalid)
+ )
+
+ threw = False
+ try:
+ parser.parse(interface)
+ results = parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
diff --git a/dom/bindings/parser/tests/test_union_any.py b/dom/bindings/parser/tests/test_union_any.py
new file mode 100644
index 0000000000..83ee7493b7
--- /dev/null
+++ b/dom/bindings/parser/tests/test_union_any.py
@@ -0,0 +1,16 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface AnyNotInUnion {
+ undefined foo((any or DOMString) arg);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/dom/bindings/parser/tests/test_union_nullable.py b/dom/bindings/parser/tests/test_union_nullable.py
new file mode 100644
index 0000000000..d3255a7e05
--- /dev/null
+++ b/dom/bindings/parser/tests/test_union_nullable.py
@@ -0,0 +1,60 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface OneNullableInUnion {
+ undefined foo((object? or DOMString?) arg);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Two nullable member types of a union should have thrown.")
+
+ parser.reset()
+ threw = False
+
+ try:
+ parser.parse(
+ """
+ interface NullableInNullableUnion {
+ undefined foo((object? or DOMString)? arg);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "A nullable union type with a nullable member type should have " "thrown.",
+ )
+
+ parser.reset()
+ threw = False
+
+ try:
+ parser.parse(
+ """
+ interface NullableInUnionNullableUnionHelper {
+ };
+ interface NullableInUnionNullableUnion {
+ undefined foo(((object? or DOMString) or NullableInUnionNullableUnionHelper)? arg);
+ };
+ """
+ )
+
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "A nullable union type with a nullable member type should have " "thrown.",
+ )
diff --git a/dom/bindings/parser/tests/test_usvstring.py b/dom/bindings/parser/tests/test_usvstring.py
new file mode 100644
index 0000000000..effede391c
--- /dev/null
+++ b/dom/bindings/parser/tests/test_usvstring.py
@@ -0,0 +1,40 @@
+# -*- coding: UTF-8 -*-
+
+import WebIDL
+
+
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ interface TestUSVString {
+ attribute USVString svs;
+ };
+ """
+ )
+
+ results = parser.finish()
+
+ harness.check(len(results), 1, "Should be one production")
+ harness.ok(isinstance(results[0], WebIDL.IDLInterface), "Should be an IDLInterface")
+ iface = results[0]
+ harness.check(
+ iface.identifier.QName(), "::TestUSVString", "Interface has the right QName"
+ )
+ harness.check(
+ iface.identifier.name, "TestUSVString", "Interface has the right name"
+ )
+ harness.check(iface.parent, None, "Interface has no parent")
+
+ members = iface.members
+ harness.check(len(members), 1, "Should be one member")
+
+ attr = members[0]
+ harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Should be an IDLAttribute")
+ harness.check(
+ attr.identifier.QName(), "::TestUSVString::svs", "Attr has correct QName"
+ )
+ harness.check(attr.identifier.name, "svs", "Attr has correct name")
+ harness.check(str(attr.type), "USVString", "Attr type is the correct name")
+ harness.ok(attr.type.isUSVString(), "Should be USVString type")
+ harness.ok(attr.type.isString(), "Should be String collective type")
+ harness.ok(not attr.type.isDOMString(), "Should be not be DOMString type")
diff --git a/dom/bindings/parser/tests/test_variadic_callback.py b/dom/bindings/parser/tests/test_variadic_callback.py
new file mode 100644
index 0000000000..fab1c7ef10
--- /dev/null
+++ b/dom/bindings/parser/tests/test_variadic_callback.py
@@ -0,0 +1,10 @@
+def WebIDLTest(parser, harness):
+ parser.parse(
+ """
+ callback TestVariadicCallback = any(any... arguments);
+ """
+ )
+
+ parser.finish()
+
+ harness.ok(True, "TestVariadicCallback callback parsed without error.")
diff --git a/dom/bindings/parser/tests/test_variadic_constraints.py b/dom/bindings/parser/tests/test_variadic_constraints.py
new file mode 100644
index 0000000000..8ffbfe0374
--- /dev/null
+++ b/dom/bindings/parser/tests/test_variadic_constraints.py
@@ -0,0 +1,74 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface VariadicConstraints1 {
+ undefined foo(byte... arg1, byte arg2);
+ };
+ """
+ )
+ parser.finish()
+
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Should have thrown on variadic argument followed by required " "argument.",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface VariadicConstraints2 {
+ undefined foo(byte... arg1, optional byte arg2);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Should have thrown on variadic argument followed by optional " "argument.",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface VariadicConstraints3 {
+ undefined foo(optional byte... arg1);
+ };
+ """
+ )
+ parser.finish()
+
+ except Exception:
+ threw = True
+
+ harness.ok(
+ threw,
+ "Should have thrown on variadic argument explicitly flagged as " "optional.",
+ )
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse(
+ """
+ interface VariadicConstraints4 {
+ undefined foo(byte... arg1 = 0);
+ };
+ """
+ )
+ parser.finish()
+ except Exception:
+ threw = True
+
+ harness.ok(threw, "Should have thrown on variadic argument with default value.")
diff --git a/dom/bindings/test/Makefile.in b/dom/bindings/test/Makefile.in
new file mode 100644
index 0000000000..e338cc30d0
--- /dev/null
+++ b/dom/bindings/test/Makefile.in
@@ -0,0 +1,17 @@
+# 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/.
+
+ifdef COMPILE_ENVIRONMENT
+
+include ../webidlsrcs.mk
+
+# $(test_sources) comes from webidlsrcs.mk.
+# TODO Update this variable in backend.mk.
+CPPSRCS += $(addprefix ../,$(test_sources))
+
+# Include rules.mk before any of our targets so our first target is coming from
+# rules.mk and running make with no target in this dir does the right thing.
+include $(topsrcdir)/config/rules.mk
+
+endif
diff --git a/dom/bindings/test/TestBindingHeader.h b/dom/bindings/test/TestBindingHeader.h
new file mode 100644
index 0000000000..f3ddb4975b
--- /dev/null
+++ b/dom/bindings/test/TestBindingHeader.h
@@ -0,0 +1,1772 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 TestBindingHeader_h
+#define TestBindingHeader_h
+
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Record.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCOMPtr.h"
+#include "nsGenericHTMLElement.h"
+#include "nsWrapperCache.h"
+#include "js/Object.h" // JS::GetClass
+
+// Forward declare this before we include TestCodeGenBinding.h, because that
+// header relies on including this one for it, for ParentDict. Hopefully it
+// won't begin to rely on it in more fundamental ways.
+namespace mozilla {
+namespace dom {
+class DocGroup;
+class TestExternalInterface;
+class TestUnionArguments;
+class Promise;
+} // namespace dom
+} // namespace mozilla
+
+// We don't export TestCodeGenBinding.h, but it's right in our parent dir.
+#include "../TestCodeGenBinding.h"
+
+extern bool TestFuncControlledMember(JSContext*, JSObject*);
+
+namespace mozilla {
+namespace dom {
+
+// IID for nsRenamedInterface
+#define NS_RENAMED_INTERFACE_IID \
+ { \
+ 0xd4b19ef3, 0xe68b, 0x4e3f, { \
+ 0x94, 0xbc, 0xc9, 0xde, 0x3a, 0x69, 0xb0, 0xe8 \
+ } \
+ }
+
+class nsRenamedInterface : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_RENAMED_INTERFACE_IID)
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsRenamedInterface, NS_RENAMED_INTERFACE_IID)
+
+// IID for the TestExternalInterface
+#define NS_TEST_EXTERNAL_INTERFACE_IID \
+ { \
+ 0xd5ba0c99, 0x9b1d, 0x4e71, { \
+ 0x8a, 0x94, 0x56, 0x38, 0x6c, 0xa3, 0xda, 0x3d \
+ } \
+ }
+class TestExternalInterface : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_TEST_EXTERNAL_INTERFACE_IID)
+ NS_DECL_ISUPPORTS
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(TestExternalInterface,
+ NS_TEST_EXTERNAL_INTERFACE_IID)
+
+class TestNonWrapperCacheInterface : public nsISupports {
+ public:
+ NS_DECL_ISUPPORTS
+
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector);
+};
+
+class OnlyForUseInConstructor : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+};
+
+class TestInterface : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject and GetDocGroup to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+ DocGroup* GetDocGroup() const;
+
+ // And now our actual WebIDL API
+ // Constructors
+ static already_AddRefed<TestInterface> Constructor(const GlobalObject&);
+ static already_AddRefed<TestInterface> Constructor(const GlobalObject&,
+ const nsAString&);
+ static already_AddRefed<TestInterface> Constructor(const GlobalObject&,
+ uint32_t,
+ const Nullable<bool>&);
+ static already_AddRefed<TestInterface> Constructor(const GlobalObject&,
+ TestInterface*);
+ static already_AddRefed<TestInterface> Constructor(const GlobalObject&,
+ uint32_t, TestInterface&);
+
+ static already_AddRefed<TestInterface> Constructor(const GlobalObject&,
+ const ArrayBuffer&);
+ static already_AddRefed<TestInterface> Constructor(const GlobalObject&,
+ const Uint8Array&);
+ /* static
+ already_AddRefed<TestInterface>
+ Constructor(const GlobalObject&, uint32_t, uint32_t,
+ const TestInterfaceOrOnlyForUseInConstructor&);
+ */
+
+ static already_AddRefed<TestInterface> Test(const GlobalObject&,
+ ErrorResult&);
+ static already_AddRefed<TestInterface> Test(const GlobalObject&,
+ const nsAString&, ErrorResult&);
+ static already_AddRefed<TestInterface> Test(const GlobalObject&,
+ const nsACString&, ErrorResult&);
+
+ static already_AddRefed<TestInterface> Test2(
+ const GlobalObject&, const DictForConstructor&, JS::Handle<JS::Value>,
+ JS::Handle<JSObject*>, JS::Handle<JSObject*>, const Sequence<Dict>&,
+ JS::Handle<JS::Value>, const Optional<JS::Handle<JSObject*>>&,
+ const Optional<JS::Handle<JSObject*>>&, ErrorResult&);
+
+ static already_AddRefed<TestInterface> Test3(const GlobalObject&,
+ const LongOrStringAnyRecord&,
+ ErrorResult&);
+
+ static already_AddRefed<TestInterface> Test4(
+ const GlobalObject&, const Record<nsString, Record<nsString, JS::Value>>&,
+ ErrorResult&);
+
+ static already_AddRefed<TestInterface> Test5(
+ const GlobalObject&,
+ const Record<
+ nsString,
+ Sequence<Record<nsString,
+ Record<nsString, Sequence<Sequence<JS::Value>>>>>>&,
+ ErrorResult&);
+
+ static already_AddRefed<TestInterface> Test6(
+ const GlobalObject&,
+ const Sequence<Record<
+ nsCString,
+ Sequence<Sequence<Record<nsCString, Record<nsString, JS::Value>>>>>>&,
+ ErrorResult&);
+
+ // Integer types
+ int8_t ReadonlyByte();
+ int8_t WritableByte();
+ void SetWritableByte(int8_t);
+ void PassByte(int8_t);
+ int8_t ReceiveByte();
+ void PassOptionalByte(const Optional<int8_t>&);
+ void PassOptionalByteBeforeRequired(const Optional<int8_t>&, int8_t);
+ void PassOptionalByteWithDefault(int8_t);
+ void PassOptionalByteWithDefaultBeforeRequired(int8_t, int8_t);
+ void PassNullableByte(const Nullable<int8_t>&);
+ void PassOptionalNullableByte(const Optional<Nullable<int8_t>>&);
+ void PassVariadicByte(const Sequence<int8_t>&);
+ int8_t CachedByte();
+ int8_t CachedConstantByte();
+ int8_t CachedWritableByte();
+ void SetCachedWritableByte(int8_t);
+ int8_t SideEffectFreeByte();
+ void SetSideEffectFreeByte(int8_t);
+ int8_t DomDependentByte();
+ void SetDomDependentByte(int8_t);
+ int8_t ConstantByte();
+ int8_t DeviceStateDependentByte();
+ int8_t ReturnByteSideEffectFree();
+ int8_t ReturnDOMDependentByte();
+ int8_t ReturnConstantByte();
+ int8_t ReturnDeviceStateDependentByte();
+
+ void UnsafePrerenderMethod();
+ int32_t UnsafePrerenderWritable();
+ void SetUnsafePrerenderWritable(int32_t);
+ int32_t UnsafePrerenderReadonly();
+ int16_t ReadonlyShort();
+ int16_t WritableShort();
+ void SetWritableShort(int16_t);
+ void PassShort(int16_t);
+ int16_t ReceiveShort();
+ void PassOptionalShort(const Optional<int16_t>&);
+ void PassOptionalShortWithDefault(int16_t);
+
+ int32_t ReadonlyLong();
+ int32_t WritableLong();
+ void SetWritableLong(int32_t);
+ void PassLong(int32_t);
+ int16_t ReceiveLong();
+ void PassOptionalLong(const Optional<int32_t>&);
+ void PassOptionalLongWithDefault(int32_t);
+
+ int64_t ReadonlyLongLong();
+ int64_t WritableLongLong();
+ void SetWritableLongLong(int64_t);
+ void PassLongLong(int64_t);
+ int64_t ReceiveLongLong();
+ void PassOptionalLongLong(const Optional<int64_t>&);
+ void PassOptionalLongLongWithDefault(int64_t);
+
+ uint8_t ReadonlyOctet();
+ uint8_t WritableOctet();
+ void SetWritableOctet(uint8_t);
+ void PassOctet(uint8_t);
+ uint8_t ReceiveOctet();
+ void PassOptionalOctet(const Optional<uint8_t>&);
+ void PassOptionalOctetWithDefault(uint8_t);
+
+ uint16_t ReadonlyUnsignedShort();
+ uint16_t WritableUnsignedShort();
+ void SetWritableUnsignedShort(uint16_t);
+ void PassUnsignedShort(uint16_t);
+ uint16_t ReceiveUnsignedShort();
+ void PassOptionalUnsignedShort(const Optional<uint16_t>&);
+ void PassOptionalUnsignedShortWithDefault(uint16_t);
+
+ uint32_t ReadonlyUnsignedLong();
+ uint32_t WritableUnsignedLong();
+ void SetWritableUnsignedLong(uint32_t);
+ void PassUnsignedLong(uint32_t);
+ uint32_t ReceiveUnsignedLong();
+ void PassOptionalUnsignedLong(const Optional<uint32_t>&);
+ void PassOptionalUnsignedLongWithDefault(uint32_t);
+
+ uint64_t ReadonlyUnsignedLongLong();
+ uint64_t WritableUnsignedLongLong();
+ void SetWritableUnsignedLongLong(uint64_t);
+ void PassUnsignedLongLong(uint64_t);
+ uint64_t ReceiveUnsignedLongLong();
+ void PassOptionalUnsignedLongLong(const Optional<uint64_t>&);
+ void PassOptionalUnsignedLongLongWithDefault(uint64_t);
+
+ float WritableFloat() const;
+ void SetWritableFloat(float);
+ float WritableUnrestrictedFloat() const;
+ void SetWritableUnrestrictedFloat(float);
+ Nullable<float> GetWritableNullableFloat() const;
+ void SetWritableNullableFloat(const Nullable<float>&);
+ Nullable<float> GetWritableNullableUnrestrictedFloat() const;
+ void SetWritableNullableUnrestrictedFloat(const Nullable<float>&);
+ double WritableDouble() const;
+ void SetWritableDouble(double);
+ double WritableUnrestrictedDouble() const;
+ void SetWritableUnrestrictedDouble(double);
+ Nullable<double> GetWritableNullableDouble() const;
+ void SetWritableNullableDouble(const Nullable<double>&);
+ Nullable<double> GetWritableNullableUnrestrictedDouble() const;
+ void SetWritableNullableUnrestrictedDouble(const Nullable<double>&);
+ void PassFloat(float, float, const Nullable<float>&, const Nullable<float>&,
+ double, double, const Nullable<double>&,
+ const Nullable<double>&, const Sequence<float>&,
+ const Sequence<float>&, const Sequence<Nullable<float>>&,
+ const Sequence<Nullable<float>>&, const Sequence<double>&,
+ const Sequence<double>&, const Sequence<Nullable<double>>&,
+ const Sequence<Nullable<double>>&);
+ void PassLenientFloat(float, float, const Nullable<float>&,
+ const Nullable<float>&, double, double,
+ const Nullable<double>&, const Nullable<double>&,
+ const Sequence<float>&, const Sequence<float>&,
+ const Sequence<Nullable<float>>&,
+ const Sequence<Nullable<float>>&,
+ const Sequence<double>&, const Sequence<double>&,
+ const Sequence<Nullable<double>>&,
+ const Sequence<Nullable<double>>&);
+ float LenientFloatAttr() const;
+ void SetLenientFloatAttr(float);
+ double LenientDoubleAttr() const;
+ void SetLenientDoubleAttr(double);
+
+ void PassUnrestricted(float arg1, float arg2, float arg3, float arg4,
+ double arg5, double arg6, double arg7, double arg8);
+
+ // Interface types
+ already_AddRefed<TestInterface> ReceiveSelf();
+ already_AddRefed<TestInterface> ReceiveNullableSelf();
+ TestInterface* ReceiveWeakSelf();
+ TestInterface* ReceiveWeakNullableSelf();
+ void PassSelf(TestInterface&);
+ void PassNullableSelf(TestInterface*);
+ already_AddRefed<TestInterface> NonNullSelf();
+ void SetNonNullSelf(TestInterface&);
+ already_AddRefed<TestInterface> GetNullableSelf();
+ already_AddRefed<TestInterface> CachedSelf();
+ void SetNullableSelf(TestInterface*);
+ void PassOptionalSelf(const Optional<TestInterface*>&);
+ void PassOptionalNonNullSelf(const Optional<NonNull<TestInterface>>&);
+ void PassOptionalSelfWithDefault(TestInterface*);
+
+ already_AddRefed<TestNonWrapperCacheInterface>
+ ReceiveNonWrapperCacheInterface();
+ already_AddRefed<TestNonWrapperCacheInterface>
+ ReceiveNullableNonWrapperCacheInterface();
+ void ReceiveNonWrapperCacheInterfaceSequence(
+ nsTArray<RefPtr<TestNonWrapperCacheInterface>>&);
+ void ReceiveNullableNonWrapperCacheInterfaceSequence(
+ nsTArray<RefPtr<TestNonWrapperCacheInterface>>&);
+ void ReceiveNonWrapperCacheInterfaceNullableSequence(
+ Nullable<nsTArray<RefPtr<TestNonWrapperCacheInterface>>>&);
+ void ReceiveNullableNonWrapperCacheInterfaceNullableSequence(
+ Nullable<nsTArray<RefPtr<TestNonWrapperCacheInterface>>>&);
+
+ already_AddRefed<TestExternalInterface> ReceiveExternal();
+ already_AddRefed<TestExternalInterface> ReceiveNullableExternal();
+ TestExternalInterface* ReceiveWeakExternal();
+ TestExternalInterface* ReceiveWeakNullableExternal();
+ void PassExternal(TestExternalInterface*);
+ void PassNullableExternal(TestExternalInterface*);
+ already_AddRefed<TestExternalInterface> NonNullExternal();
+ void SetNonNullExternal(TestExternalInterface*);
+ already_AddRefed<TestExternalInterface> GetNullableExternal();
+ void SetNullableExternal(TestExternalInterface*);
+ void PassOptionalExternal(const Optional<TestExternalInterface*>&);
+ void PassOptionalNonNullExternal(const Optional<TestExternalInterface*>&);
+ void PassOptionalExternalWithDefault(TestExternalInterface*);
+
+ already_AddRefed<TestCallbackInterface> ReceiveCallbackInterface();
+ already_AddRefed<TestCallbackInterface> ReceiveNullableCallbackInterface();
+ TestCallbackInterface* ReceiveWeakCallbackInterface();
+ TestCallbackInterface* ReceiveWeakNullableCallbackInterface();
+ void PassCallbackInterface(TestCallbackInterface&);
+ void PassNullableCallbackInterface(TestCallbackInterface*);
+ already_AddRefed<TestCallbackInterface> NonNullCallbackInterface();
+ void SetNonNullCallbackInterface(TestCallbackInterface&);
+ already_AddRefed<TestCallbackInterface> GetNullableCallbackInterface();
+ void SetNullableCallbackInterface(TestCallbackInterface*);
+ void PassOptionalCallbackInterface(
+ const Optional<RefPtr<TestCallbackInterface>>&);
+ void PassOptionalNonNullCallbackInterface(
+ const Optional<OwningNonNull<TestCallbackInterface>>&);
+ void PassOptionalCallbackInterfaceWithDefault(TestCallbackInterface*);
+
+ // Sequence types
+ void GetReadonlySequence(nsTArray<int32_t>&);
+ void GetReadonlySequenceOfDictionaries(JSContext*, nsTArray<Dict>&);
+ void GetReadonlyNullableSequenceOfDictionaries(JSContext*,
+ Nullable<nsTArray<Dict>>&);
+ void GetReadonlyFrozenSequence(JSContext*, nsTArray<Dict>&);
+ void GetReadonlyFrozenNullableSequence(JSContext*, Nullable<nsTArray<Dict>>&);
+ void ReceiveSequence(nsTArray<int32_t>&);
+ void ReceiveNullableSequence(Nullable<nsTArray<int32_t>>&);
+ void ReceiveSequenceOfNullableInts(nsTArray<Nullable<int32_t>>&);
+ void ReceiveNullableSequenceOfNullableInts(
+ Nullable<nsTArray<Nullable<int32_t>>>&);
+ void PassSequence(const Sequence<int32_t>&);
+ void PassNullableSequence(const Nullable<Sequence<int32_t>>&);
+ void PassSequenceOfNullableInts(const Sequence<Nullable<int32_t>>&);
+ void PassOptionalSequenceOfNullableInts(
+ const Optional<Sequence<Nullable<int32_t>>>&);
+ void PassOptionalNullableSequenceOfNullableInts(
+ const Optional<Nullable<Sequence<Nullable<int32_t>>>>&);
+ void ReceiveCastableObjectSequence(nsTArray<RefPtr<TestInterface>>&);
+ void ReceiveCallbackObjectSequence(nsTArray<RefPtr<TestCallbackInterface>>&);
+ void ReceiveNullableCastableObjectSequence(nsTArray<RefPtr<TestInterface>>&);
+ void ReceiveNullableCallbackObjectSequence(
+ nsTArray<RefPtr<TestCallbackInterface>>&);
+ void ReceiveCastableObjectNullableSequence(
+ Nullable<nsTArray<RefPtr<TestInterface>>>&);
+ void ReceiveNullableCastableObjectNullableSequence(
+ Nullable<nsTArray<RefPtr<TestInterface>>>&);
+ void ReceiveWeakCastableObjectSequence(nsTArray<RefPtr<TestInterface>>&);
+ void ReceiveWeakNullableCastableObjectSequence(
+ nsTArray<RefPtr<TestInterface>>&);
+ void ReceiveWeakCastableObjectNullableSequence(
+ Nullable<nsTArray<RefPtr<TestInterface>>>&);
+ void ReceiveWeakNullableCastableObjectNullableSequence(
+ Nullable<nsTArray<RefPtr<TestInterface>>>&);
+ void PassCastableObjectSequence(
+ const Sequence<OwningNonNull<TestInterface>>&);
+ void PassNullableCastableObjectSequence(
+ const Sequence<RefPtr<TestInterface>>&);
+ void PassCastableObjectNullableSequence(
+ const Nullable<Sequence<OwningNonNull<TestInterface>>>&);
+ void PassNullableCastableObjectNullableSequence(
+ const Nullable<Sequence<RefPtr<TestInterface>>>&);
+ void PassOptionalSequence(const Optional<Sequence<int32_t>>&);
+ void PassOptionalSequenceWithDefaultValue(const Sequence<int32_t>&);
+ void PassOptionalNullableSequence(
+ const Optional<Nullable<Sequence<int32_t>>>&);
+ void PassOptionalNullableSequenceWithDefaultValue(
+ const Nullable<Sequence<int32_t>>&);
+ void PassOptionalNullableSequenceWithDefaultValue2(
+ const Nullable<Sequence<int32_t>>&);
+ void PassOptionalObjectSequence(
+ const Optional<Sequence<OwningNonNull<TestInterface>>>&);
+ void PassExternalInterfaceSequence(
+ const Sequence<RefPtr<TestExternalInterface>>&);
+ void PassNullableExternalInterfaceSequence(
+ const Sequence<RefPtr<TestExternalInterface>>&);
+
+ void ReceiveStringSequence(nsTArray<nsString>&);
+ void PassStringSequence(const Sequence<nsString>&);
+
+ void ReceiveByteStringSequence(nsTArray<nsCString>&);
+ void PassByteStringSequence(const Sequence<nsCString>&);
+
+ void ReceiveUTF8StringSequence(nsTArray<nsCString>&);
+ void PassUTF8StringSequence(const Sequence<nsCString>&);
+
+ void ReceiveAnySequence(JSContext*, nsTArray<JS::Value>&);
+ void ReceiveNullableAnySequence(JSContext*, Nullable<nsTArray<JS::Value>>&);
+ void ReceiveAnySequenceSequence(JSContext*, nsTArray<nsTArray<JS::Value>>&);
+
+ void ReceiveObjectSequence(JSContext*, nsTArray<JSObject*>&);
+ void ReceiveNullableObjectSequence(JSContext*, nsTArray<JSObject*>&);
+
+ void PassSequenceOfSequences(const Sequence<Sequence<int32_t>>&);
+ void PassSequenceOfSequencesOfSequences(
+ const Sequence<Sequence<Sequence<int32_t>>>&);
+ void ReceiveSequenceOfSequences(nsTArray<nsTArray<int32_t>>&);
+ void ReceiveSequenceOfSequencesOfSequences(
+ nsTArray<nsTArray<nsTArray<int32_t>>>&);
+
+ // Record types
+ void PassRecord(const Record<nsString, int32_t>&);
+ void PassNullableRecord(const Nullable<Record<nsString, int32_t>>&);
+ void PassRecordOfNullableInts(const Record<nsString, Nullable<int32_t>>&);
+ void PassOptionalRecordOfNullableInts(
+ const Optional<Record<nsString, Nullable<int32_t>>>&);
+ void PassOptionalNullableRecordOfNullableInts(
+ const Optional<Nullable<Record<nsString, Nullable<int32_t>>>>&);
+ void PassCastableObjectRecord(
+ const Record<nsString, OwningNonNull<TestInterface>>&);
+ void PassNullableCastableObjectRecord(
+ const Record<nsString, RefPtr<TestInterface>>&);
+ void PassCastableObjectNullableRecord(
+ const Nullable<Record<nsString, OwningNonNull<TestInterface>>>&);
+ void PassNullableCastableObjectNullableRecord(
+ const Nullable<Record<nsString, RefPtr<TestInterface>>>&);
+ void PassOptionalRecord(const Optional<Record<nsString, int32_t>>&);
+ void PassOptionalNullableRecord(
+ const Optional<Nullable<Record<nsString, int32_t>>>&);
+ void PassOptionalNullableRecordWithDefaultValue(
+ const Nullable<Record<nsString, int32_t>>&);
+ void PassOptionalObjectRecord(
+ const Optional<Record<nsString, OwningNonNull<TestInterface>>>&);
+ void PassExternalInterfaceRecord(
+ const Record<nsString, RefPtr<TestExternalInterface>>&);
+ void PassNullableExternalInterfaceRecord(
+ const Record<nsString, RefPtr<TestExternalInterface>>&);
+ void PassStringRecord(const Record<nsString, nsString>&);
+ void PassByteStringRecord(const Record<nsString, nsCString>&);
+ void PassUTF8StringRecord(const Record<nsString, nsCString>&);
+ void PassRecordOfRecords(const Record<nsString, Record<nsString, int32_t>>&);
+ void ReceiveRecord(Record<nsString, int32_t>&);
+ void ReceiveNullableRecord(Nullable<Record<nsString, int32_t>>&);
+ void ReceiveRecordOfNullableInts(Record<nsString, Nullable<int32_t>>&);
+ void ReceiveNullableRecordOfNullableInts(
+ Nullable<Record<nsString, Nullable<int32_t>>>&);
+ void ReceiveRecordOfRecords(Record<nsString, Record<nsString, int32_t>>&);
+ void ReceiveAnyRecord(JSContext*, Record<nsString, JS::Value>&);
+
+ // Typed array types
+ void PassArrayBuffer(const ArrayBuffer&);
+ void PassNullableArrayBuffer(const Nullable<ArrayBuffer>&);
+ void PassOptionalArrayBuffer(const Optional<ArrayBuffer>&);
+ void PassOptionalNullableArrayBuffer(const Optional<Nullable<ArrayBuffer>>&);
+ void PassOptionalNullableArrayBufferWithDefaultValue(
+ const Nullable<ArrayBuffer>&);
+ void PassArrayBufferView(const ArrayBufferView&);
+ void PassInt8Array(const Int8Array&);
+ void PassInt16Array(const Int16Array&);
+ void PassInt32Array(const Int32Array&);
+ void PassUint8Array(const Uint8Array&);
+ void PassUint16Array(const Uint16Array&);
+ void PassUint32Array(const Uint32Array&);
+ void PassUint8ClampedArray(const Uint8ClampedArray&);
+ void PassFloat32Array(const Float32Array&);
+ void PassFloat64Array(const Float64Array&);
+ void PassSequenceOfArrayBuffers(const Sequence<ArrayBuffer>&);
+ void PassSequenceOfNullableArrayBuffers(
+ const Sequence<Nullable<ArrayBuffer>>&);
+ void PassRecordOfArrayBuffers(const Record<nsString, ArrayBuffer>&);
+ void PassRecordOfNullableArrayBuffers(
+ const Record<nsString, Nullable<ArrayBuffer>>&);
+ void PassVariadicTypedArray(const Sequence<Float32Array>&);
+ void PassVariadicNullableTypedArray(const Sequence<Nullable<Float32Array>>&);
+ void ReceiveUint8Array(JSContext*, JS::MutableHandle<JSObject*>);
+ void SetUint8ArrayAttr(const Uint8Array&);
+ void GetUint8ArrayAttr(JSContext*, JS::MutableHandle<JSObject*>);
+
+ // DOMString types
+ void PassString(const nsAString&);
+ void PassNullableString(const nsAString&);
+ void PassOptionalString(const Optional<nsAString>&);
+ void PassOptionalStringWithDefaultValue(const nsAString&);
+ void PassOptionalNullableString(const Optional<nsAString>&);
+ void PassOptionalNullableStringWithDefaultValue(const nsAString&);
+ void PassVariadicString(const Sequence<nsString>&);
+ void ReceiveString(DOMString&);
+
+ // ByteString types
+ void PassByteString(const nsCString&);
+ void PassNullableByteString(const nsCString&);
+ void PassOptionalByteString(const Optional<nsCString>&);
+ void PassOptionalByteStringWithDefaultValue(const nsCString&);
+ void PassOptionalNullableByteString(const Optional<nsCString>&);
+ void PassOptionalNullableByteStringWithDefaultValue(const nsCString&);
+ void PassVariadicByteString(const Sequence<nsCString>&);
+ void PassOptionalUnionByteString(const Optional<ByteStringOrLong>&);
+ void PassOptionalUnionByteStringWithDefaultValue(const ByteStringOrLong&);
+
+ // UTF8String types
+ void PassUTF8String(const nsACString&);
+ void PassNullableUTF8String(const nsACString&);
+ void PassOptionalUTF8String(const Optional<nsACString>&);
+ void PassOptionalUTF8StringWithDefaultValue(const nsACString&);
+ void PassOptionalNullableUTF8String(const Optional<nsACString>&);
+ void PassOptionalNullableUTF8StringWithDefaultValue(const nsACString&);
+ void PassVariadicUTF8String(const Sequence<nsCString>&);
+ void PassOptionalUnionUTF8String(const Optional<UTF8StringOrLong>&);
+ void PassOptionalUnionUTF8StringWithDefaultValue(const UTF8StringOrLong&);
+
+ // USVString types
+ void PassUSVS(const nsAString&);
+ void PassNullableUSVS(const nsAString&);
+ void PassOptionalUSVS(const Optional<nsAString>&);
+ void PassOptionalUSVSWithDefaultValue(const nsAString&);
+ void PassOptionalNullableUSVS(const Optional<nsAString>&);
+ void PassOptionalNullableUSVSWithDefaultValue(const nsAString&);
+ void PassVariadicUSVS(const Sequence<nsString>&);
+ void ReceiveUSVS(DOMString&);
+
+ // JSString types
+ void PassJSString(JSContext*, JS::Handle<JSString*>);
+ void PassOptionalJSStringWithDefaultValue(JSContext*, JS::Handle<JSString*>);
+ void ReceiveJSString(JSContext*, JS::MutableHandle<JSString*>);
+ void GetReadonlyJSStringAttr(JSContext*, JS::MutableHandle<JSString*>);
+ void GetJsStringAttr(JSContext*, JS::MutableHandle<JSString*>);
+ void SetJsStringAttr(JSContext*, JS::Handle<JSString*>);
+
+ // Enumerated types
+ void PassEnum(TestEnum);
+ void PassNullableEnum(const Nullable<TestEnum>&);
+ void PassOptionalEnum(const Optional<TestEnum>&);
+ void PassEnumWithDefault(TestEnum);
+ void PassOptionalNullableEnum(const Optional<Nullable<TestEnum>>&);
+ void PassOptionalNullableEnumWithDefaultValue(const Nullable<TestEnum>&);
+ void PassOptionalNullableEnumWithDefaultValue2(const Nullable<TestEnum>&);
+ TestEnum ReceiveEnum();
+ Nullable<TestEnum> ReceiveNullableEnum();
+ TestEnum EnumAttribute();
+ TestEnum ReadonlyEnumAttribute();
+ void SetEnumAttribute(TestEnum);
+
+ // Callback types
+ void PassCallback(TestCallback&);
+ void PassNullableCallback(TestCallback*);
+ void PassOptionalCallback(const Optional<OwningNonNull<TestCallback>>&);
+ void PassOptionalNullableCallback(const Optional<RefPtr<TestCallback>>&);
+ void PassOptionalNullableCallbackWithDefaultValue(TestCallback*);
+ already_AddRefed<TestCallback> ReceiveCallback();
+ already_AddRefed<TestCallback> ReceiveNullableCallback();
+ void PassNullableTreatAsNullCallback(TestTreatAsNullCallback*);
+ void PassOptionalNullableTreatAsNullCallback(
+ const Optional<RefPtr<TestTreatAsNullCallback>>&);
+ void PassOptionalNullableTreatAsNullCallbackWithDefaultValue(
+ TestTreatAsNullCallback*);
+ void SetTreatAsNullCallback(TestTreatAsNullCallback&);
+ already_AddRefed<TestTreatAsNullCallback> TreatAsNullCallback();
+ void SetNullableTreatAsNullCallback(TestTreatAsNullCallback*);
+ already_AddRefed<TestTreatAsNullCallback> GetNullableTreatAsNullCallback();
+
+ void ForceCallbackGeneration(
+ TestIntegerReturn&, TestNullableIntegerReturn&, TestBooleanReturn&,
+ TestFloatReturn&, TestStringReturn&, TestEnumReturn&,
+ TestInterfaceReturn&, TestNullableInterfaceReturn&,
+ TestExternalInterfaceReturn&, TestNullableExternalInterfaceReturn&,
+ TestCallbackInterfaceReturn&, TestNullableCallbackInterfaceReturn&,
+ TestCallbackReturn&, TestNullableCallbackReturn&, TestObjectReturn&,
+ TestNullableObjectReturn&, TestTypedArrayReturn&,
+ TestNullableTypedArrayReturn&, TestSequenceReturn&,
+ TestNullableSequenceReturn&, TestIntegerArguments&,
+ TestInterfaceArguments&, TestStringEnumArguments&, TestObjectArguments&,
+ TestOptionalArguments&, TestUnionArguments&, TestUndefinedConstruction&,
+ TestIntegerConstruction&, TestBooleanConstruction&,
+ TestFloatConstruction&, TestStringConstruction&, TestEnumConstruction&,
+ TestInterfaceConstruction&, TestExternalInterfaceConstruction&,
+ TestCallbackInterfaceConstruction&, TestCallbackConstruction&,
+ TestObjectConstruction&, TestTypedArrayConstruction&,
+ TestSequenceConstruction&);
+
+ // Any types
+ void PassAny(JSContext*, JS::Handle<JS::Value>);
+ void PassVariadicAny(JSContext*, const Sequence<JS::Value>&);
+ void PassOptionalAny(JSContext*, JS::Handle<JS::Value>);
+ void PassAnyDefaultNull(JSContext*, JS::Handle<JS::Value>);
+ void PassSequenceOfAny(JSContext*, const Sequence<JS::Value>&);
+ void PassNullableSequenceOfAny(JSContext*,
+ const Nullable<Sequence<JS::Value>>&);
+ void PassOptionalSequenceOfAny(JSContext*,
+ const Optional<Sequence<JS::Value>>&);
+ void PassOptionalNullableSequenceOfAny(
+ JSContext*, const Optional<Nullable<Sequence<JS::Value>>>&);
+ void PassOptionalSequenceOfAnyWithDefaultValue(
+ JSContext*, const Nullable<Sequence<JS::Value>>&);
+ void PassSequenceOfSequenceOfAny(JSContext*,
+ const Sequence<Sequence<JS::Value>>&);
+ void PassSequenceOfNullableSequenceOfAny(
+ JSContext*, const Sequence<Nullable<Sequence<JS::Value>>>&);
+ void PassNullableSequenceOfNullableSequenceOfAny(
+ JSContext*, const Nullable<Sequence<Nullable<Sequence<JS::Value>>>>&);
+ void PassOptionalNullableSequenceOfNullableSequenceOfAny(
+ JSContext*,
+ const Optional<Nullable<Sequence<Nullable<Sequence<JS::Value>>>>>&);
+ void PassRecordOfAny(JSContext*, const Record<nsString, JS::Value>&);
+ void PassNullableRecordOfAny(JSContext*,
+ const Nullable<Record<nsString, JS::Value>>&);
+ void PassOptionalRecordOfAny(JSContext*,
+ const Optional<Record<nsString, JS::Value>>&);
+ void PassOptionalNullableRecordOfAny(
+ JSContext*, const Optional<Nullable<Record<nsString, JS::Value>>>&);
+ void PassOptionalRecordOfAnyWithDefaultValue(
+ JSContext*, const Nullable<Record<nsString, JS::Value>>&);
+ void PassRecordOfRecordOfAny(
+ JSContext*, const Record<nsString, Record<nsString, JS::Value>>&);
+ void PassRecordOfNullableRecordOfAny(
+ JSContext*,
+ const Record<nsString, Nullable<Record<nsString, JS::Value>>>&);
+ void PassNullableRecordOfNullableRecordOfAny(
+ JSContext*,
+ const Nullable<Record<nsString, Nullable<Record<nsString, JS::Value>>>>&);
+ void PassOptionalNullableRecordOfNullableRecordOfAny(
+ JSContext*,
+ const Optional<
+ Nullable<Record<nsString, Nullable<Record<nsString, JS::Value>>>>>&);
+ void PassOptionalNullableRecordOfNullableSequenceOfAny(
+ JSContext*,
+ const Optional<
+ Nullable<Record<nsString, Nullable<Sequence<JS::Value>>>>>&);
+ void PassOptionalNullableSequenceOfNullableRecordOfAny(
+ JSContext*,
+ const Optional<
+ Nullable<Sequence<Nullable<Record<nsString, JS::Value>>>>>&);
+ void ReceiveAny(JSContext*, JS::MutableHandle<JS::Value>);
+
+ // object types
+ void PassObject(JSContext*, JS::Handle<JSObject*>);
+ void PassVariadicObject(JSContext*, const Sequence<JSObject*>&);
+ void PassNullableObject(JSContext*, JS::Handle<JSObject*>);
+ void PassVariadicNullableObject(JSContext*, const Sequence<JSObject*>&);
+ void PassOptionalObject(JSContext*, const Optional<JS::Handle<JSObject*>>&);
+ void PassOptionalNullableObject(JSContext*,
+ const Optional<JS::Handle<JSObject*>>&);
+ void PassOptionalNullableObjectWithDefaultValue(JSContext*,
+ JS::Handle<JSObject*>);
+ void PassSequenceOfObject(JSContext*, const Sequence<JSObject*>&);
+ void PassSequenceOfNullableObject(JSContext*, const Sequence<JSObject*>&);
+ void PassNullableSequenceOfObject(JSContext*,
+ const Nullable<Sequence<JSObject*>>&);
+ void PassOptionalNullableSequenceOfNullableSequenceOfObject(
+ JSContext*,
+ const Optional<Nullable<Sequence<Nullable<Sequence<JSObject*>>>>>&);
+ void PassOptionalNullableSequenceOfNullableSequenceOfNullableObject(
+ JSContext*,
+ const Optional<Nullable<Sequence<Nullable<Sequence<JSObject*>>>>>&);
+ void PassRecordOfObject(JSContext*, const Record<nsString, JSObject*>&);
+ void ReceiveObject(JSContext*, JS::MutableHandle<JSObject*>);
+ void ReceiveNullableObject(JSContext*, JS::MutableHandle<JSObject*>);
+
+ // Union types
+ void PassUnion(JSContext*, const ObjectOrLong& arg);
+ void PassUnionWithNullable(JSContext* cx, const ObjectOrNullOrLong& arg) {
+ OwningObjectOrLong returnValue;
+ if (arg.IsNull()) {
+ } else if (arg.IsObject()) {
+ JS::Rooted<JSObject*> obj(cx, arg.GetAsObject());
+ JS::GetClass(obj);
+ returnValue.SetAsObject() = obj;
+ } else {
+ int32_t i = arg.GetAsLong();
+ i += 1;
+ returnValue.SetAsLong() = i;
+ }
+ }
+#ifdef DEBUG
+ void PassUnion2(const LongOrBoolean& arg);
+ void PassUnion3(JSContext*, const ObjectOrLongOrBoolean& arg);
+ void PassUnion4(const NodeOrLongOrBoolean& arg);
+ void PassUnion5(JSContext*, const ObjectOrBoolean& arg);
+ void PassUnion6(JSContext*, const ObjectOrString& arg);
+ void PassUnion7(JSContext*, const ObjectOrStringOrLong& arg);
+ void PassUnion8(JSContext*, const ObjectOrStringOrBoolean& arg);
+ void PassUnion9(JSContext*, const ObjectOrStringOrLongOrBoolean& arg);
+ void PassUnion10(const EventInitOrLong& arg);
+ void PassUnion11(JSContext*, const CustomEventInitOrLong& arg);
+ void PassUnion12(const EventInitOrLong& arg);
+ void PassUnion13(JSContext*, const ObjectOrLongOrNull& arg);
+ void PassUnion14(JSContext*, const ObjectOrLongOrNull& arg);
+ void PassUnion15(const LongSequenceOrLong&);
+ void PassUnion16(const Optional<LongSequenceOrLong>&);
+ void PassUnion17(const LongSequenceOrNullOrLong&);
+ void PassUnion18(JSContext*, const ObjectSequenceOrLong&);
+ void PassUnion19(JSContext*, const Optional<ObjectSequenceOrLong>&);
+ void PassUnion20(JSContext*, const ObjectSequenceOrLong&);
+ void PassUnion21(const StringLongRecordOrLong&);
+ void PassUnion22(JSContext*, const StringObjectRecordOrLong&);
+ void PassUnion23(const ImageDataSequenceOrLong&);
+ void PassUnion24(const ImageDataOrNullSequenceOrLong&);
+ void PassUnion25(const ImageDataSequenceSequenceOrLong&);
+ void PassUnion26(const ImageDataOrNullSequenceSequenceOrLong&);
+ void PassUnion27(const StringSequenceOrEventInit&);
+ void PassUnion28(const EventInitOrStringSequence&);
+ void PassUnionWithCallback(const EventHandlerNonNullOrNullOrLong& arg);
+ void PassUnionWithByteString(const ByteStringOrLong&);
+ void PassUnionWithUTF8String(const UTF8StringOrLong&);
+ void PassUnionWithRecord(const StringStringRecordOrString&);
+ void PassUnionWithRecordAndSequence(
+ const StringStringRecordOrStringSequence&);
+ void PassUnionWithSequenceAndRecord(
+ const StringSequenceOrStringStringRecord&);
+ void PassUnionWithUSVS(const USVStringOrLong&);
+#endif
+ void PassNullableUnion(JSContext*, const Nullable<ObjectOrLong>&);
+ void PassOptionalUnion(JSContext*, const Optional<ObjectOrLong>&);
+ void PassOptionalNullableUnion(JSContext*,
+ const Optional<Nullable<ObjectOrLong>>&);
+ void PassOptionalNullableUnionWithDefaultValue(JSContext*,
+ const Nullable<ObjectOrLong>&);
+ // void PassUnionWithInterfaces(const TestInterfaceOrTestExternalInterface&
+ // arg); void PassUnionWithInterfacesAndNullable(const
+ // TestInterfaceOrNullOrTestExternalInterface& arg);
+ void PassUnionWithArrayBuffer(const ArrayBufferOrLong&);
+ void PassUnionWithString(JSContext*, const StringOrObject&);
+ void PassUnionWithEnum(JSContext*, const SupportedTypeOrObject&);
+ // void PassUnionWithCallback(JSContext*, const TestCallbackOrLong&);
+ void PassUnionWithObject(JSContext*, const ObjectOrLong&);
+
+ void PassUnionWithDefaultValue1(const DoubleOrString& arg);
+ void PassUnionWithDefaultValue2(const DoubleOrString& arg);
+ void PassUnionWithDefaultValue3(const DoubleOrString& arg);
+ void PassUnionWithDefaultValue4(const FloatOrString& arg);
+ void PassUnionWithDefaultValue5(const FloatOrString& arg);
+ void PassUnionWithDefaultValue6(const FloatOrString& arg);
+ void PassUnionWithDefaultValue7(const UnrestrictedDoubleOrString& arg);
+ void PassUnionWithDefaultValue8(const UnrestrictedDoubleOrString& arg);
+ void PassUnionWithDefaultValue9(const UnrestrictedDoubleOrString& arg);
+ void PassUnionWithDefaultValue10(const UnrestrictedDoubleOrString& arg);
+ void PassUnionWithDefaultValue11(const UnrestrictedFloatOrString& arg);
+ void PassUnionWithDefaultValue12(const UnrestrictedFloatOrString& arg);
+ void PassUnionWithDefaultValue13(const UnrestrictedFloatOrString& arg);
+ void PassUnionWithDefaultValue14(const DoubleOrByteString& arg);
+ void PassUnionWithDefaultValue15(const DoubleOrByteString& arg);
+ void PassUnionWithDefaultValue16(const DoubleOrByteString& arg);
+ void PassUnionWithDefaultValue17(const DoubleOrSupportedType& arg);
+ void PassUnionWithDefaultValue18(const DoubleOrSupportedType& arg);
+ void PassUnionWithDefaultValue19(const DoubleOrSupportedType& arg);
+ void PassUnionWithDefaultValue20(const DoubleOrUSVString& arg);
+ void PassUnionWithDefaultValue21(const DoubleOrUSVString& arg);
+ void PassUnionWithDefaultValue22(const DoubleOrUSVString& arg);
+ void PassUnionWithDefaultValue23(const DoubleOrUTF8String& arg);
+ void PassUnionWithDefaultValue24(const DoubleOrUTF8String& arg);
+ void PassUnionWithDefaultValue25(const DoubleOrUTF8String& arg);
+
+ void PassNullableUnionWithDefaultValue1(const Nullable<DoubleOrString>& arg);
+ void PassNullableUnionWithDefaultValue2(const Nullable<DoubleOrString>& arg);
+ void PassNullableUnionWithDefaultValue3(const Nullable<DoubleOrString>& arg);
+ void PassNullableUnionWithDefaultValue4(const Nullable<FloatOrString>& arg);
+ void PassNullableUnionWithDefaultValue5(const Nullable<FloatOrString>& arg);
+ void PassNullableUnionWithDefaultValue6(const Nullable<FloatOrString>& arg);
+ void PassNullableUnionWithDefaultValue7(
+ const Nullable<UnrestrictedDoubleOrString>& arg);
+ void PassNullableUnionWithDefaultValue8(
+ const Nullable<UnrestrictedDoubleOrString>& arg);
+ void PassNullableUnionWithDefaultValue9(
+ const Nullable<UnrestrictedDoubleOrString>& arg);
+ void PassNullableUnionWithDefaultValue10(
+ const Nullable<UnrestrictedFloatOrString>& arg);
+ void PassNullableUnionWithDefaultValue11(
+ const Nullable<UnrestrictedFloatOrString>& arg);
+ void PassNullableUnionWithDefaultValue12(
+ const Nullable<UnrestrictedFloatOrString>& arg);
+ void PassNullableUnionWithDefaultValue13(
+ const Nullable<DoubleOrByteString>& arg);
+ void PassNullableUnionWithDefaultValue14(
+ const Nullable<DoubleOrByteString>& arg);
+ void PassNullableUnionWithDefaultValue15(
+ const Nullable<DoubleOrByteString>& arg);
+ void PassNullableUnionWithDefaultValue16(
+ const Nullable<DoubleOrByteString>& arg);
+ void PassNullableUnionWithDefaultValue17(
+ const Nullable<DoubleOrSupportedType>& arg);
+ void PassNullableUnionWithDefaultValue18(
+ const Nullable<DoubleOrSupportedType>& arg);
+ void PassNullableUnionWithDefaultValue19(
+ const Nullable<DoubleOrSupportedType>& arg);
+ void PassNullableUnionWithDefaultValue20(
+ const Nullable<DoubleOrSupportedType>& arg);
+ void PassNullableUnionWithDefaultValue21(
+ const Nullable<DoubleOrUSVString>& arg);
+ void PassNullableUnionWithDefaultValue22(
+ const Nullable<DoubleOrUSVString>& arg);
+ void PassNullableUnionWithDefaultValue23(
+ const Nullable<DoubleOrUSVString>& arg);
+ void PassNullableUnionWithDefaultValue24(
+ const Nullable<DoubleOrUSVString>& arg);
+ void PassNullableUnionWithDefaultValue25(
+ const Nullable<DoubleOrUTF8String>& arg);
+ void PassNullableUnionWithDefaultValue26(
+ const Nullable<DoubleOrUTF8String>& arg);
+ void PassNullableUnionWithDefaultValue27(
+ const Nullable<DoubleOrUTF8String>& arg);
+ void PassNullableUnionWithDefaultValue28(
+ const Nullable<DoubleOrUTF8String>& arg);
+
+ void PassSequenceOfUnions(
+ const Sequence<OwningCanvasPatternOrCanvasGradient>&);
+ void PassSequenceOfUnions2(JSContext*, const Sequence<OwningObjectOrLong>&);
+ void PassVariadicUnion(const Sequence<OwningCanvasPatternOrCanvasGradient>&);
+
+ void PassSequenceOfNullableUnions(
+ const Sequence<Nullable<OwningCanvasPatternOrCanvasGradient>>&);
+ void PassVariadicNullableUnion(
+ const Sequence<Nullable<OwningCanvasPatternOrCanvasGradient>>&);
+ void PassRecordOfUnions(
+ const Record<nsString, OwningCanvasPatternOrCanvasGradient>&);
+ void PassRecordOfUnions2(JSContext*,
+ const Record<nsString, OwningObjectOrLong>&);
+
+ void PassUnionWithSequenceOfUnions(
+ const StringOrOnlyForUseInInnerUnionOrCanvasPatternSequence& arg);
+ void PassUnionWithRecordOfUnions(
+ const LongSequenceOrStringOnlyForUseInInnerUnionOrLongSequenceRecord&
+ arg);
+
+ void ReceiveUnion(OwningCanvasPatternOrCanvasGradient&);
+ void ReceiveUnion2(JSContext*, OwningObjectOrLong&);
+ void ReceiveUnionContainingNull(OwningCanvasPatternOrNullOrCanvasGradient&);
+ void ReceiveNullableUnion(Nullable<OwningCanvasPatternOrCanvasGradient>&);
+ void ReceiveNullableUnion2(JSContext*, Nullable<OwningObjectOrLong>&);
+ void ReceiveUnionWithUndefined(OwningUndefinedOrCanvasPattern&);
+ void ReceiveUnionWithNullableUndefined(OwningUndefinedOrNullOrCanvasPattern&);
+ void ReceiveUnionWithUndefinedAndNullable(
+ OwningUndefinedOrCanvasPatternOrNull&);
+ void ReceiveNullableUnionWithUndefined(
+ Nullable<OwningUndefinedOrCanvasPattern>&);
+
+ void GetWritableUnion(OwningCanvasPatternOrCanvasGradient&);
+ void SetWritableUnion(const CanvasPatternOrCanvasGradient&);
+ void GetWritableUnionContainingNull(
+ OwningCanvasPatternOrNullOrCanvasGradient&);
+ void SetWritableUnionContainingNull(
+ const CanvasPatternOrNullOrCanvasGradient&);
+ void GetWritableNullableUnion(Nullable<OwningCanvasPatternOrCanvasGradient>&);
+ void SetWritableNullableUnion(const Nullable<CanvasPatternOrCanvasGradient>&);
+ void GetWritableUnionWithUndefined(OwningUndefinedOrCanvasPattern&);
+ void SetWritableUnionWithUndefined(const UndefinedOrCanvasPattern&);
+ void GetWritableUnionWithNullableUndefined(
+ OwningUndefinedOrNullOrCanvasPattern&);
+ void SetWritableUnionWithNullableUndefined(
+ const UndefinedOrNullOrCanvasPattern&);
+ void GetWritableUnionWithUndefinedAndNullable(
+ OwningUndefinedOrCanvasPatternOrNull&);
+ void SetWritableUnionWithUndefinedAndNullable(
+ const UndefinedOrCanvasPatternOrNull&);
+ void GetWritableNullableUnionWithUndefined(
+ Nullable<OwningUndefinedOrCanvasPattern>&);
+ void SetWritableNullableUnionWithUndefined(
+ const Nullable<UndefinedOrCanvasPattern>&);
+
+ // Promise types
+ void PassPromise(Promise&);
+ void PassOptionalPromise(const Optional<OwningNonNull<Promise>>&);
+ void PassPromiseSequence(const Sequence<OwningNonNull<Promise>>&);
+ void PassPromiseRecord(const Record<nsString, RefPtr<Promise>>&);
+ Promise* ReceivePromise();
+ already_AddRefed<Promise> ReceiveAddrefedPromise();
+
+ // ObservableArray types
+ void OnDeleteBooleanObservableArray(bool, uint32_t, ErrorResult&);
+ void OnSetBooleanObservableArray(bool, uint32_t, ErrorResult&);
+ void OnDeleteObjectObservableArray(JSContext*, JS::Handle<JSObject*>,
+ uint32_t, ErrorResult&);
+ void OnSetObjectObservableArray(JSContext*, JS::Handle<JSObject*>, uint32_t,
+ ErrorResult&);
+ void OnDeleteAnyObservableArray(JSContext*, JS::Handle<JS::Value>, uint32_t,
+ ErrorResult&);
+ void OnSetAnyObservableArray(JSContext*, JS::Handle<JS::Value>, uint32_t,
+ ErrorResult&);
+ void OnDeleteInterfaceObservableArray(TestInterface*, uint32_t, ErrorResult&);
+ void OnSetInterfaceObservableArray(TestInterface*, uint32_t, ErrorResult&);
+ void OnDeleteNullableObservableArray(const Nullable<int>&, uint32_t,
+ ErrorResult&);
+ void OnSetNullableObservableArray(const Nullable<int>&, uint32_t,
+ ErrorResult&);
+
+ // binaryNames tests
+ void MethodRenamedTo();
+ void MethodRenamedTo(int8_t);
+ int8_t AttributeGetterRenamedTo();
+ int8_t AttributeRenamedTo();
+ void SetAttributeRenamedTo(int8_t);
+
+ // Dictionary tests
+ void PassDictionary(JSContext*, const Dict&);
+ void PassDictionary2(JSContext*, const Dict&);
+ void GetReadonlyDictionary(JSContext*, Dict&);
+ void GetReadonlyNullableDictionary(JSContext*, Nullable<Dict>&);
+ void GetWritableDictionary(JSContext*, Dict&);
+ void SetWritableDictionary(JSContext*, const Dict&);
+ void GetReadonlyFrozenDictionary(JSContext*, Dict&);
+ void GetReadonlyFrozenNullableDictionary(JSContext*, Nullable<Dict>&);
+ void GetWritableFrozenDictionary(JSContext*, Dict&);
+ void SetWritableFrozenDictionary(JSContext*, const Dict&);
+ void ReceiveDictionary(JSContext*, Dict&);
+ void ReceiveNullableDictionary(JSContext*, Nullable<Dict>&);
+ void PassOtherDictionary(const GrandparentDict&);
+ void PassSequenceOfDictionaries(JSContext*, const Sequence<Dict>&);
+ void PassRecordOfDictionaries(const Record<nsString, GrandparentDict>&);
+ void PassDictionaryOrLong(JSContext*, const Dict&);
+ void PassDictionaryOrLong(int32_t);
+ void PassDictContainingDict(JSContext*, const DictContainingDict&);
+ void PassDictContainingSequence(JSContext*, const DictContainingSequence&);
+ void ReceiveDictContainingSequence(JSContext*, DictContainingSequence&);
+ void PassVariadicDictionary(JSContext*, const Sequence<Dict>&);
+
+ // Typedefs
+ void ExerciseTypedefInterfaces1(TestInterface&);
+ already_AddRefed<TestInterface> ExerciseTypedefInterfaces2(TestInterface*);
+ void ExerciseTypedefInterfaces3(TestInterface&);
+
+ // Deprecated methods and attributes
+ int8_t DeprecatedAttribute();
+ void SetDeprecatedAttribute(int8_t);
+ int8_t DeprecatedMethod();
+ int8_t DeprecatedMethodWithContext(JSContext*, const JS::Value&);
+
+ // Static methods and attributes
+ static void StaticMethod(const GlobalObject&, bool);
+ static void StaticMethodWithContext(const GlobalObject&, const JS::Value&);
+ static bool StaticAttribute(const GlobalObject&);
+ static void SetStaticAttribute(const GlobalObject&, bool);
+ static void Assert(const GlobalObject&, bool);
+
+ // Deprecated static methods and attributes
+ static int8_t StaticDeprecatedAttribute(const GlobalObject&);
+ static void SetStaticDeprecatedAttribute(const GlobalObject&, int8_t);
+ static void StaticDeprecatedMethod(const GlobalObject&);
+ static void StaticDeprecatedMethodWithContext(const GlobalObject&,
+ const JS::Value&);
+
+ // Overload resolution tests
+ bool Overload1(TestInterface&);
+ TestInterface* Overload1(const nsAString&, TestInterface&);
+ void Overload2(TestInterface&);
+ void Overload2(JSContext*, const Dict&);
+ void Overload2(bool);
+ void Overload2(const nsAString&);
+ void Overload3(TestInterface&);
+ void Overload3(const TestCallback&);
+ void Overload3(bool);
+ void Overload4(TestInterface&);
+ void Overload4(TestCallbackInterface&);
+ void Overload4(const nsAString&);
+ void Overload5(int32_t);
+ void Overload5(TestEnum);
+ void Overload6(int32_t);
+ void Overload6(bool);
+ void Overload7(int32_t);
+ void Overload7(bool);
+ void Overload7(const nsCString&);
+ void Overload8(int32_t);
+ void Overload8(TestInterface&);
+ void Overload9(const Nullable<int32_t>&);
+ void Overload9(const nsAString&);
+ void Overload10(const Nullable<int32_t>&);
+ void Overload10(JSContext*, JS::Handle<JSObject*>);
+ void Overload11(int32_t);
+ void Overload11(const nsAString&);
+ void Overload12(int32_t);
+ void Overload12(const Nullable<bool>&);
+ void Overload13(const Nullable<int32_t>&);
+ void Overload13(bool);
+ void Overload14(const Optional<int32_t>&);
+ void Overload14(TestInterface&);
+ void Overload15(int32_t);
+ void Overload15(const Optional<NonNull<TestInterface>>&);
+ void Overload16(int32_t);
+ void Overload16(const Optional<TestInterface*>&);
+ void Overload17(const Sequence<int32_t>&);
+ void Overload17(const Record<nsString, int32_t>&);
+ void Overload18(const Record<nsString, nsString>&);
+ void Overload18(const Sequence<nsString>&);
+ void Overload19(const Sequence<int32_t>&);
+ void Overload19(JSContext*, const Dict&);
+ void Overload20(JSContext*, const Dict&);
+ void Overload20(const Sequence<int32_t>&);
+
+ // Variadic handling
+ void PassVariadicThirdArg(const nsAString&, int32_t,
+ const Sequence<OwningNonNull<TestInterface>>&);
+
+ // Conditionally exposed methods/attributes
+ bool Prefable1();
+ bool Prefable2();
+ bool Prefable3();
+ bool Prefable4();
+ bool Prefable5();
+ bool Prefable6();
+ bool Prefable7();
+ bool Prefable8();
+ bool Prefable9();
+ void Prefable10();
+ void Prefable11();
+ bool Prefable12();
+ void Prefable13();
+ bool Prefable14();
+ bool Prefable15();
+ bool Prefable16();
+ void Prefable17();
+ void Prefable18();
+ void Prefable19();
+ void Prefable20();
+ void Prefable21();
+ void Prefable22();
+ void Prefable23();
+ void Prefable24();
+
+ // Conditionally exposed methods/attributes involving [SecureContext]
+ bool ConditionalOnSecureContext1();
+ bool ConditionalOnSecureContext2();
+ bool ConditionalOnSecureContext3();
+ bool ConditionalOnSecureContext4();
+ void ConditionalOnSecureContext5();
+ void ConditionalOnSecureContext6();
+ void ConditionalOnSecureContext7();
+ void ConditionalOnSecureContext8();
+
+ bool ConditionalOnSecureContext9();
+ void ConditionalOnSecureContext10();
+
+ // Miscellania
+ int32_t AttrWithLenientThis();
+ void SetAttrWithLenientThis(int32_t);
+ uint32_t UnforgeableAttr();
+ uint32_t UnforgeableAttr2();
+ uint32_t UnforgeableMethod();
+ uint32_t UnforgeableMethod2();
+ void Stringify(nsString&);
+ void PassRenamedInterface(nsRenamedInterface&);
+ TestInterface* PutForwardsAttr();
+ TestInterface* PutForwardsAttr2();
+ TestInterface* PutForwardsAttr3();
+ void GetToJSONShouldSkipThis(JSContext*, JS::MutableHandle<JS::Value>);
+ void SetToJSONShouldSkipThis(JSContext*, JS::Rooted<JS::Value>&);
+ TestParentInterface* ToJSONShouldSkipThis2();
+ void SetToJSONShouldSkipThis2(TestParentInterface&);
+ TestCallbackInterface* ToJSONShouldSkipThis3();
+ void SetToJSONShouldSkipThis3(TestCallbackInterface&);
+ void ThrowingMethod(ErrorResult& aRv);
+ bool GetThrowingAttr(ErrorResult& aRv) const;
+ void SetThrowingAttr(bool arg, ErrorResult& aRv);
+ bool GetThrowingGetterAttr(ErrorResult& aRv) const;
+ void SetThrowingGetterAttr(bool arg);
+ bool ThrowingSetterAttr() const;
+ void SetThrowingSetterAttr(bool arg, ErrorResult& aRv);
+ void CanOOMMethod(OOMReporter& aRv);
+ bool GetCanOOMAttr(OOMReporter& aRv) const;
+ void SetCanOOMAttr(bool arg, OOMReporter& aRv);
+ bool GetCanOOMGetterAttr(OOMReporter& aRv) const;
+ void SetCanOOMGetterAttr(bool arg);
+ bool CanOOMSetterAttr() const;
+ void SetCanOOMSetterAttr(bool arg, OOMReporter& aRv);
+ void NeedsSubjectPrincipalMethod(nsIPrincipal&);
+ bool NeedsSubjectPrincipalAttr(nsIPrincipal&);
+ void SetNeedsSubjectPrincipalAttr(bool, nsIPrincipal&);
+ void NeedsCallerTypeMethod(CallerType);
+ bool NeedsCallerTypeAttr(CallerType);
+ void SetNeedsCallerTypeAttr(bool, CallerType);
+ void NeedsNonSystemSubjectPrincipalMethod(nsIPrincipal*);
+ bool NeedsNonSystemSubjectPrincipalAttr(nsIPrincipal*);
+ void SetNeedsNonSystemSubjectPrincipalAttr(bool, nsIPrincipal*);
+ void CeReactionsMethod();
+ void CeReactionsMethodOverload();
+ void CeReactionsMethodOverload(const nsAString&);
+ bool CeReactionsAttr() const;
+ void SetCeReactionsAttr(bool);
+ int16_t LegacyCall(const JS::Value&, uint32_t, TestInterface&);
+ void PassArgsWithDefaults(JSContext*, const Optional<int32_t>&,
+ TestInterface*, const Dict&, double,
+ const Optional<float>&);
+
+ void SetDashed_attribute(int8_t);
+ int8_t Dashed_attribute();
+ void Dashed_method();
+
+ bool NonEnumerableAttr() const;
+ void SetNonEnumerableAttr(bool);
+ void NonEnumerableMethod();
+
+ // Methods and properties imported via "includes"
+ bool MixedInProperty();
+ void SetMixedInProperty(bool);
+ void MixedInMethod();
+
+ // Test EnforceRange/Clamp
+ void DontEnforceRangeOrClamp(int8_t);
+ void DoEnforceRange(int8_t);
+ void DoEnforceRangeNullable(const Nullable<int8_t>&);
+ void DoClamp(int8_t);
+ void DoClampNullable(const Nullable<int8_t>&);
+ void SetEnforcedByte(int8_t);
+ int8_t EnforcedByte();
+ void SetEnforcedNullableByte(const Nullable<int8_t>&);
+ Nullable<int8_t> GetEnforcedNullableByte() const;
+ void SetClampedNullableByte(const Nullable<int8_t>&);
+ Nullable<int8_t> GetClampedNullableByte() const;
+ void SetClampedByte(int8_t);
+ int8_t ClampedByte();
+
+ // Test AllowShared
+ void SetAllowSharedArrayBufferViewTypedef(const ArrayBufferView&);
+ void GetAllowSharedArrayBufferViewTypedef(JSContext*,
+ JS::MutableHandle<JSObject*>);
+ void SetAllowSharedArrayBufferView(const ArrayBufferView&);
+ void GetAllowSharedArrayBufferView(JSContext*, JS::MutableHandle<JSObject*>);
+ void SetAllowSharedNullableArrayBufferView(const Nullable<ArrayBufferView>&);
+ void GetAllowSharedNullableArrayBufferView(JSContext*,
+ JS::MutableHandle<JSObject*>);
+ void SetAllowSharedArrayBuffer(const ArrayBuffer&);
+ void GetAllowSharedArrayBuffer(JSContext*, JS::MutableHandle<JSObject*>);
+ void SetAllowSharedNullableArrayBuffer(const Nullable<ArrayBuffer>&);
+ void GetAllowSharedNullableArrayBuffer(JSContext*,
+ JS::MutableHandle<JSObject*>);
+
+ void PassAllowSharedArrayBufferViewTypedef(const ArrayBufferView&);
+ void PassAllowSharedArrayBufferView(const ArrayBufferView&);
+ void PassAllowSharedNullableArrayBufferView(const Nullable<ArrayBufferView>&);
+ void PassAllowSharedArrayBuffer(const ArrayBuffer&);
+ void PassAllowSharedNullableArrayBuffer(const Nullable<ArrayBuffer>&);
+ void PassUnionArrayBuffer(const StringOrArrayBuffer& foo);
+ void PassUnionAllowSharedArrayBuffer(
+ const StringOrMaybeSharedArrayBuffer& foo);
+
+ private:
+ // We add signatures here that _could_ start matching if the codegen
+ // got data types wrong. That way if it ever does we'll have a call
+ // to these private deleted methods and compilation will fail.
+ void SetReadonlyByte(int8_t) = delete;
+ template <typename T>
+ void SetWritableByte(T) = delete;
+ template <typename T>
+ void PassByte(T) = delete;
+ void PassNullableByte(Nullable<int8_t>&) = delete;
+ template <typename T>
+ void PassOptionalByte(const Optional<T>&) = delete;
+ template <typename T>
+ void PassOptionalByteWithDefault(T) = delete;
+ void PassVariadicByte(Sequence<int8_t>&) = delete;
+
+ void SetReadonlyShort(int16_t) = delete;
+ template <typename T>
+ void SetWritableShort(T) = delete;
+ template <typename T>
+ void PassShort(T) = delete;
+ template <typename T>
+ void PassOptionalShort(const Optional<T>&) = delete;
+ template <typename T>
+ void PassOptionalShortWithDefault(T) = delete;
+
+ void SetReadonlyLong(int32_t) = delete;
+ template <typename T>
+ void SetWritableLong(T) = delete;
+ template <typename T>
+ void PassLong(T) = delete;
+ template <typename T>
+ void PassOptionalLong(const Optional<T>&) = delete;
+ template <typename T>
+ void PassOptionalLongWithDefault(T) = delete;
+
+ void SetReadonlyLongLong(int64_t) = delete;
+ template <typename T>
+ void SetWritableLongLong(T) = delete;
+ template <typename T>
+ void PassLongLong(T) = delete;
+ template <typename T>
+ void PassOptionalLongLong(const Optional<T>&) = delete;
+ template <typename T>
+ void PassOptionalLongLongWithDefault(T) = delete;
+
+ void SetReadonlyOctet(uint8_t) = delete;
+ template <typename T>
+ void SetWritableOctet(T) = delete;
+ template <typename T>
+ void PassOctet(T) = delete;
+ template <typename T>
+ void PassOptionalOctet(const Optional<T>&) = delete;
+ template <typename T>
+ void PassOptionalOctetWithDefault(T) = delete;
+
+ void SetReadonlyUnsignedShort(uint16_t) = delete;
+ template <typename T>
+ void SetWritableUnsignedShort(T) = delete;
+ template <typename T>
+ void PassUnsignedShort(T) = delete;
+ template <typename T>
+ void PassOptionalUnsignedShort(const Optional<T>&) = delete;
+ template <typename T>
+ void PassOptionalUnsignedShortWithDefault(T) = delete;
+
+ void SetReadonlyUnsignedLong(uint32_t) = delete;
+ template <typename T>
+ void SetWritableUnsignedLong(T) = delete;
+ template <typename T>
+ void PassUnsignedLong(T) = delete;
+ template <typename T>
+ void PassOptionalUnsignedLong(const Optional<T>&) = delete;
+ template <typename T>
+ void PassOptionalUnsignedLongWithDefault(T) = delete;
+
+ void SetReadonlyUnsignedLongLong(uint64_t) = delete;
+ template <typename T>
+ void SetWritableUnsignedLongLong(T) = delete;
+ template <typename T>
+ void PassUnsignedLongLong(T) = delete;
+ template <typename T>
+ void PassOptionalUnsignedLongLong(const Optional<T>&) = delete;
+ template <typename T>
+ void PassOptionalUnsignedLongLongWithDefault(T) = delete;
+
+ // Enforce that only const things are passed for sequences
+ void PassSequence(Sequence<int32_t>&) = delete;
+ void PassNullableSequence(Nullable<Sequence<int32_t>>&) = delete;
+ void PassOptionalNullableSequenceWithDefaultValue(
+ Nullable<Sequence<int32_t>>&) = delete;
+ void PassSequenceOfAny(JSContext*, Sequence<JS::Value>&) = delete;
+ void PassNullableSequenceOfAny(JSContext*,
+ Nullable<Sequence<JS::Value>>&) = delete;
+ void PassOptionalSequenceOfAny(JSContext*,
+ Optional<Sequence<JS::Value>>&) = delete;
+ void PassOptionalNullableSequenceOfAny(
+ JSContext*, Optional<Nullable<Sequence<JS::Value>>>&) = delete;
+ void PassOptionalSequenceOfAnyWithDefaultValue(
+ JSContext*, Nullable<Sequence<JS::Value>>&) = delete;
+ void PassSequenceOfSequenceOfAny(JSContext*,
+ Sequence<Sequence<JS::Value>>&) = delete;
+ void PassSequenceOfNullableSequenceOfAny(
+ JSContext*, Sequence<Nullable<Sequence<JS::Value>>>&) = delete;
+ void PassNullableSequenceOfNullableSequenceOfAny(
+ JSContext*, Nullable<Sequence<Nullable<Sequence<JS::Value>>>>&) = delete;
+ void PassOptionalNullableSequenceOfNullableSequenceOfAny(
+ JSContext*,
+ Optional<Nullable<Sequence<Nullable<Sequence<JS::Value>>>>>&) = delete;
+ void PassSequenceOfObject(JSContext*, Sequence<JSObject*>&) = delete;
+ void PassSequenceOfNullableObject(JSContext*, Sequence<JSObject*>&) = delete;
+ void PassOptionalNullableSequenceOfNullableSequenceOfObject(
+ JSContext*,
+ Optional<Nullable<Sequence<Nullable<Sequence<JSObject*>>>>>&) = delete;
+ void PassOptionalNullableSequenceOfNullableSequenceOfNullableObject(
+ JSContext*,
+ Optional<Nullable<Sequence<Nullable<Sequence<JSObject*>>>>>&) = delete;
+
+ // Enforce that only const things are passed for optional
+ void PassOptionalByte(Optional<int8_t>&) = delete;
+ void PassOptionalNullableByte(Optional<Nullable<int8_t>>&) = delete;
+ void PassOptionalShort(Optional<int16_t>&) = delete;
+ void PassOptionalLong(Optional<int32_t>&) = delete;
+ void PassOptionalLongLong(Optional<int64_t>&) = delete;
+ void PassOptionalOctet(Optional<uint8_t>&) = delete;
+ void PassOptionalUnsignedShort(Optional<uint16_t>&) = delete;
+ void PassOptionalUnsignedLong(Optional<uint32_t>&) = delete;
+ void PassOptionalUnsignedLongLong(Optional<uint64_t>&) = delete;
+ void PassOptionalSelf(Optional<TestInterface*>&) = delete;
+ void PassOptionalNonNullSelf(Optional<NonNull<TestInterface>>&) = delete;
+ void PassOptionalExternal(Optional<TestExternalInterface*>&) = delete;
+ void PassOptionalNonNullExternal(Optional<TestExternalInterface*>&) = delete;
+ void PassOptionalSequence(Optional<Sequence<int32_t>>&) = delete;
+ void PassOptionalNullableSequence(Optional<Nullable<Sequence<int32_t>>>&) =
+ delete;
+ void PassOptionalObjectSequence(
+ Optional<Sequence<OwningNonNull<TestInterface>>>&) = delete;
+ void PassOptionalArrayBuffer(Optional<ArrayBuffer>&) = delete;
+ void PassOptionalNullableArrayBuffer(Optional<ArrayBuffer*>&) = delete;
+ void PassOptionalEnum(Optional<TestEnum>&) = delete;
+ void PassOptionalCallback(JSContext*,
+ Optional<OwningNonNull<TestCallback>>&) = delete;
+ void PassOptionalNullableCallback(JSContext*,
+ Optional<RefPtr<TestCallback>>&) = delete;
+ void PassOptionalAny(Optional<JS::Handle<JS::Value>>&) = delete;
+
+ // And test that string stuff is always const
+ void PassString(nsAString&) = delete;
+ void PassNullableString(nsAString&) = delete;
+ void PassOptionalString(Optional<nsAString>&) = delete;
+ void PassOptionalStringWithDefaultValue(nsAString&) = delete;
+ void PassOptionalNullableString(Optional<nsAString>&) = delete;
+ void PassOptionalNullableStringWithDefaultValue(nsAString&) = delete;
+ void PassVariadicString(Sequence<nsString>&) = delete;
+
+ // cstrings should be const as well
+ void PassByteString(nsCString&) = delete;
+ void PassNullableByteString(nsCString&) = delete;
+ void PassOptionalByteString(Optional<nsCString>&) = delete;
+ void PassOptionalByteStringWithDefaultValue(nsCString&) = delete;
+ void PassOptionalNullableByteString(Optional<nsCString>&) = delete;
+ void PassOptionalNullableByteStringWithDefaultValue(nsCString&) = delete;
+ void PassVariadicByteString(Sequence<nsCString>&) = delete;
+
+ // cstrings should be const as well
+ void PassUTF8String(nsACString&) = delete;
+ void PassNullableUTF8String(nsACString&) = delete;
+ void PassOptionalUTF8String(Optional<nsACString>&) = delete;
+ void PassOptionalUTF8StringWithDefaultValue(nsACString&) = delete;
+ void PassOptionalNullableUTF8String(Optional<nsACString>&) = delete;
+ void PassOptionalNullableUTF8StringWithDefaultValue(nsACString&) = delete;
+ void PassVariadicUTF8String(Sequence<nsCString>&) = delete;
+
+ // Make sure dictionary arguments are always const
+ void PassDictionary(JSContext*, Dict&) = delete;
+ void PassOtherDictionary(GrandparentDict&) = delete;
+ void PassSequenceOfDictionaries(JSContext*, Sequence<Dict>&) = delete;
+ void PassDictionaryOrLong(JSContext*, Dict&) = delete;
+ void PassDictContainingDict(JSContext*, DictContainingDict&) = delete;
+ void PassDictContainingSequence(DictContainingSequence&) = delete;
+
+ // Make sure various nullable things are always const
+ void PassNullableEnum(Nullable<TestEnum>&) = delete;
+
+ // Make sure unions are always const
+ void PassUnion(JSContext*, ObjectOrLong& arg) = delete;
+ void PassUnionWithNullable(JSContext*, ObjectOrNullOrLong& arg) = delete;
+ void PassNullableUnion(JSContext*, Nullable<ObjectOrLong>&) = delete;
+ void PassOptionalUnion(JSContext*, Optional<ObjectOrLong>&) = delete;
+ void PassOptionalNullableUnion(JSContext*,
+ Optional<Nullable<ObjectOrLong>>&) = delete;
+ void PassOptionalNullableUnionWithDefaultValue(
+ JSContext*, Nullable<ObjectOrLong>&) = delete;
+
+ // Make sure variadics are const as needed
+ void PassVariadicAny(JSContext*, Sequence<JS::Value>&) = delete;
+ void PassVariadicObject(JSContext*, Sequence<JSObject*>&) = delete;
+ void PassVariadicNullableObject(JSContext*, Sequence<JSObject*>&) = delete;
+
+ // Ensure NonNull does not leak in
+ void PassSelf(NonNull<TestInterface>&) = delete;
+ void PassSelf(OwningNonNull<TestInterface>&) = delete;
+ void PassSelf(const NonNull<TestInterface>&) = delete;
+ void PassSelf(const OwningNonNull<TestInterface>&) = delete;
+ void PassCallbackInterface(OwningNonNull<TestCallbackInterface>&) = delete;
+ void PassCallbackInterface(const OwningNonNull<TestCallbackInterface>&) =
+ delete;
+ void PassCallbackInterface(NonNull<TestCallbackInterface>&) = delete;
+ void PassCallbackInterface(const NonNull<TestCallbackInterface>&) = delete;
+ void PassCallback(OwningNonNull<TestCallback>&) = delete;
+ void PassCallback(const OwningNonNull<TestCallback>&) = delete;
+ void PassCallback(NonNull<TestCallback>&) = delete;
+ void PassCallback(const NonNull<TestCallback>&) = delete;
+ void PassString(const NonNull<nsAString>&) = delete;
+ void PassString(NonNull<nsAString>&) = delete;
+ void PassString(const OwningNonNull<nsAString>&) = delete;
+ void PassString(OwningNonNull<nsAString>&) = delete;
+};
+
+class TestIndexedGetterInterface : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+
+ uint32_t IndexedGetter(uint32_t, bool&);
+ uint32_t IndexedGetter(uint32_t&) = delete;
+ uint32_t Item(uint32_t&);
+ uint32_t Item(uint32_t, bool&) = delete;
+ uint32_t Length();
+ void LegacyCall(JS::Handle<JS::Value>);
+ int32_t CachedAttr();
+ int32_t StoreInSlotAttr();
+};
+
+class TestNamedGetterInterface : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+
+ void NamedGetter(const nsAString&, bool&, nsAString&);
+ void GetSupportedNames(nsTArray<nsString>&);
+};
+
+class TestIndexedGetterAndSetterAndNamedGetterInterface
+ : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+
+ void NamedGetter(const nsAString&, bool&, nsAString&);
+ void GetSupportedNames(nsTArray<nsString>&);
+ int32_t IndexedGetter(uint32_t, bool&);
+ void IndexedSetter(uint32_t, int32_t);
+ uint32_t Length();
+};
+
+class TestIndexedAndNamedGetterInterface : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+
+ uint32_t IndexedGetter(uint32_t, bool&);
+ void NamedGetter(const nsAString&, bool&, nsAString&);
+ void NamedItem(const nsAString&, nsAString&);
+ uint32_t Length();
+ void GetSupportedNames(nsTArray<nsString>&);
+};
+
+class TestIndexedSetterInterface : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+
+ void IndexedSetter(uint32_t, const nsAString&);
+ void IndexedGetter(uint32_t, bool&, nsString&);
+ uint32_t Length();
+ void SetItem(uint32_t, const nsAString&);
+};
+
+class TestNamedSetterInterface : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+
+ void NamedSetter(const nsAString&, TestIndexedSetterInterface&);
+ TestIndexedSetterInterface* NamedGetter(const nsAString&, bool&);
+ void GetSupportedNames(nsTArray<nsString>&);
+};
+
+class TestIndexedAndNamedSetterInterface : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+
+ void IndexedSetter(uint32_t, TestIndexedSetterInterface&);
+ TestIndexedSetterInterface* IndexedGetter(uint32_t, bool&);
+ uint32_t Length();
+ void NamedSetter(const nsAString&, TestIndexedSetterInterface&);
+ TestIndexedSetterInterface* NamedGetter(const nsAString&, bool&);
+ void SetNamedItem(const nsAString&, TestIndexedSetterInterface&);
+ void GetSupportedNames(nsTArray<nsString>&);
+};
+
+class TestIndexedAndNamedGetterAndSetterInterface
+ : public TestIndexedSetterInterface {
+ public:
+ uint32_t IndexedGetter(uint32_t, bool&);
+ uint32_t Item(uint32_t);
+ void NamedGetter(const nsAString&, bool&, nsAString&);
+ void NamedItem(const nsAString&, nsAString&);
+ void IndexedSetter(uint32_t, int32_t&);
+ void IndexedSetter(uint32_t, const nsAString&) = delete;
+ void NamedSetter(const nsAString&, const nsAString&);
+ void Stringify(nsAString&);
+ uint32_t Length();
+ void GetSupportedNames(nsTArray<nsString>&);
+};
+
+class TestCppKeywordNamedMethodsInterface : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+
+ bool Continue();
+ bool Delete();
+ int32_t Volatile();
+};
+
+class TestNamedDeleterInterface : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+
+ void NamedDeleter(const nsAString&, bool&);
+ long NamedGetter(const nsAString&, bool&);
+ void GetSupportedNames(nsTArray<nsString>&);
+};
+
+class TestNamedDeleterWithRetvalInterface : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+
+ bool NamedDeleter(const nsAString&, bool&);
+ bool NamedDeleter(const nsAString&) = delete;
+ long NamedGetter(const nsAString&, bool&);
+ bool DelNamedItem(const nsAString&);
+ bool DelNamedItem(const nsAString&, bool&) = delete;
+ void GetSupportedNames(nsTArray<nsString>&);
+};
+
+class TestParentInterface : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+};
+
+class TestChildInterface : public TestParentInterface {};
+
+class TestDeprecatedInterface : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+
+ static already_AddRefed<TestDeprecatedInterface> Constructor(
+ const GlobalObject&);
+
+ static void AlsoDeprecated(const GlobalObject&);
+
+ virtual nsISupports* GetParentObject();
+};
+
+class TestInterfaceWithPromiseConstructorArg : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+
+ static already_AddRefed<TestInterfaceWithPromiseConstructorArg> Constructor(
+ const GlobalObject&, Promise&);
+
+ virtual nsISupports* GetParentObject();
+};
+
+class TestSecureContextInterface : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+
+ static already_AddRefed<TestSecureContextInterface> Constructor(
+ const GlobalObject&, ErrorResult&);
+
+ static void AlsoSecureContext(const GlobalObject&);
+
+ virtual nsISupports* GetParentObject();
+};
+
+class TestNamespace {
+ public:
+ static bool Foo(const GlobalObject&);
+ static int32_t Bar(const GlobalObject&);
+ static void Baz(const GlobalObject&);
+};
+
+class TestRenamedNamespace {};
+
+class TestProtoObjectHackedNamespace {};
+
+class TestWorkerExposedInterface : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ nsISupports* GetParentObject();
+
+ void NeedsSubjectPrincipalMethod(Maybe<nsIPrincipal*>);
+ bool NeedsSubjectPrincipalAttr(Maybe<nsIPrincipal*>);
+ void SetNeedsSubjectPrincipalAttr(bool, Maybe<nsIPrincipal*>);
+ void NeedsCallerTypeMethod(CallerType);
+ bool NeedsCallerTypeAttr(CallerType);
+ void SetNeedsCallerTypeAttr(bool, CallerType);
+ void NeedsNonSystemSubjectPrincipalMethod(Maybe<nsIPrincipal*>);
+ bool NeedsNonSystemSubjectPrincipalAttr(Maybe<nsIPrincipal*>);
+ void SetNeedsNonSystemSubjectPrincipalAttr(bool, Maybe<nsIPrincipal*>);
+};
+
+class TestHTMLConstructorInterface : public nsGenericHTMLElement {
+ public:
+ virtual nsISupports* GetParentObject();
+};
+
+class TestThrowingConstructorInterface : public nsISupports,
+ public nsWrapperCache {
+ public:
+ static already_AddRefed<TestThrowingConstructorInterface> Constructor(
+ const GlobalObject&, ErrorResult&);
+ static already_AddRefed<TestThrowingConstructorInterface> Constructor(
+ const GlobalObject&, const nsAString&, ErrorResult&);
+ static already_AddRefed<TestThrowingConstructorInterface> Constructor(
+ const GlobalObject&, uint32_t, const Nullable<bool>&, ErrorResult&);
+ static already_AddRefed<TestThrowingConstructorInterface> Constructor(
+ const GlobalObject&, TestInterface*, ErrorResult&);
+ static already_AddRefed<TestThrowingConstructorInterface> Constructor(
+ const GlobalObject&, uint32_t, TestInterface&, ErrorResult&);
+
+ static already_AddRefed<TestThrowingConstructorInterface> Constructor(
+ const GlobalObject&, const ArrayBuffer&, ErrorResult&);
+ static already_AddRefed<TestThrowingConstructorInterface> Constructor(
+ const GlobalObject&, const Uint8Array&, ErrorResult&);
+ /* static
+ already_AddRefed<TestThrowingConstructorInterface>
+ Constructor(const GlobalObject&, uint32_t, uint32_t,
+ const TestInterfaceOrOnlyForUseInConstructor&, ErrorResult&);
+ */
+
+ virtual nsISupports* GetParentObject();
+};
+
+class TestCEReactionsInterface : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject and GetDocGroup to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+ DocGroup* GetDocGroup() const;
+
+ int32_t Item(uint32_t);
+ uint32_t Length() const;
+ int32_t IndexedGetter(uint32_t, bool&);
+ void IndexedSetter(uint32_t, int32_t);
+ void NamedDeleter(const nsAString&, bool&);
+ void NamedGetter(const nsAString&, bool&, nsString&);
+ void NamedSetter(const nsAString&, const nsAString&);
+ void GetSupportedNames(nsTArray<nsString>&);
+};
+
+class TestAttributesOnTypes : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject and GetDocGroup to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+
+ void Foo(uint8_t arg);
+ void Bar(uint8_t arg);
+ void Baz(const nsAString& arg);
+ uint8_t SomeAttr();
+ void SetSomeAttr(uint8_t);
+ void ArgWithAttr(uint8_t arg1, const Optional<uint8_t>& arg2);
+};
+
+class TestPrefConstructorForInterface : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+ virtual nsISupports* GetParentObject();
+
+ // Since only the constructor is under a pref,
+ // the generated constructor should check for the pref.
+ static already_AddRefed<TestPrefConstructorForInterface> Constructor(
+ const GlobalObject&);
+};
+
+class TestConstructorForPrefInterface : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+ virtual nsISupports* GetParentObject();
+
+ // Since the interface itself is under a Pref, there should be no
+ // check for the pref in the generated constructor.
+ static already_AddRefed<TestConstructorForPrefInterface> Constructor(
+ const GlobalObject&);
+};
+
+class TestPrefConstructorForDifferentPrefInterface : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+ virtual nsISupports* GetParentObject();
+
+ // Since the constructor's pref is different than the interface pref
+ // there should still be a check for the pref in the generated constructor.
+ static already_AddRefed<TestPrefConstructorForDifferentPrefInterface>
+ Constructor(const GlobalObject&);
+};
+
+class TestConstructorForSCInterface : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+ virtual nsISupports* GetParentObject();
+
+ // Since the interface itself is SecureContext, there should be no
+ // check for SecureContext in the constructor.
+ static already_AddRefed<TestConstructorForSCInterface> Constructor(
+ const GlobalObject&);
+};
+
+class TestSCConstructorForInterface : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+ virtual nsISupports* GetParentObject();
+
+ // Since the interface context is unspecified but the constructor is
+ // SecureContext, the generated constructor should check for SecureContext.
+ static already_AddRefed<TestSCConstructorForInterface> Constructor(
+ const GlobalObject&);
+};
+
+class TestConstructorForFuncInterface : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+ virtual nsISupports* GetParentObject();
+
+ // Since the interface has a Func attribute, but the constructor does not,
+ // the generated constructor should not check for the Func.
+ static already_AddRefed<TestConstructorForFuncInterface> Constructor(
+ const GlobalObject&);
+};
+
+class TestFuncConstructorForInterface : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+ virtual nsISupports* GetParentObject();
+
+ // Since the constructor has a Func attribute, but the interface does not,
+ // the generated constructor should check for the Func.
+ static already_AddRefed<TestFuncConstructorForInterface> Constructor(
+ const GlobalObject&);
+};
+
+class TestFuncConstructorForDifferentFuncInterface : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+ virtual nsISupports* GetParentObject();
+
+ // Since the constructor has a different Func attribute from the interface,
+ // the generated constructor should still check for its conditional func.
+ static already_AddRefed<TestFuncConstructorForDifferentFuncInterface>
+ Constructor(const GlobalObject&);
+};
+
+class TestPrefChromeOnlySCFuncConstructorForInterface : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS
+ virtual nsISupports* GetParentObject();
+
+ // There should be checks for all Pref/ChromeOnly/SecureContext/Func
+ // in the generated constructor.
+ static already_AddRefed<TestPrefChromeOnlySCFuncConstructorForInterface>
+ Constructor(const GlobalObject&);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* TestBindingHeader_h */
diff --git a/dom/bindings/test/TestCImplementedInterface.h b/dom/bindings/test/TestCImplementedInterface.h
new file mode 100644
index 0000000000..6512911bd3
--- /dev/null
+++ b/dom/bindings/test/TestCImplementedInterface.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 TestCImplementedInterface_h
+#define TestCImplementedInterface_h
+
+#include "../TestJSImplGenBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+class TestCImplementedInterface : public TestJSImplInterface {
+ public:
+ TestCImplementedInterface(JS::Handle<JSObject*> aJSImpl,
+ JS::Handle<JSObject*> aJSImplGlobal,
+ nsIGlobalObject* aParent)
+ : TestJSImplInterface(aJSImpl, aJSImplGlobal, aParent) {}
+};
+
+class TestCImplementedInterface2 : public nsISupports, public nsWrapperCache {
+ public:
+ explicit TestCImplementedInterface2(nsIGlobalObject* aParent) {}
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TestCImplementedInterface2)
+
+ // We need a GetParentObject to make binding codegen happy
+ nsISupports* GetParentObject();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // TestCImplementedInterface_h
diff --git a/dom/bindings/test/TestCallback.webidl b/dom/bindings/test/TestCallback.webidl
new file mode 100644
index 0000000000..b4dfa83c53
--- /dev/null
+++ b/dom/bindings/test/TestCallback.webidl
@@ -0,0 +1,10 @@
+/* -*- 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/.
+ */
+
+callback TestUnionArguments = undefined((DOMString or long) arg1,
+ (DOMString or long)? arg2,
+ optional (DOMString or long) arg3,
+ optional (DOMString or long)? arg4);
diff --git a/dom/bindings/test/TestCodeGen.webidl b/dom/bindings/test/TestCodeGen.webidl
new file mode 100644
index 0000000000..6c903a0018
--- /dev/null
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -0,0 +1,1529 @@
+/* -*- 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/.
+ */
+
+typedef long myLong;
+typedef TestInterface AnotherNameForTestInterface;
+typedef TestInterface? NullableTestInterface;
+typedef CustomEventInit TestDictionaryTypedef;
+typedef ArrayBufferView ArrayBufferViewTypedef;
+typedef [AllowShared] ArrayBufferView AllowSharedArrayBufferViewTypedef;
+
+interface TestExternalInterface;
+
+// We need a pref name that's in StaticPrefList.h here.
+[Pref="dom.webidl.test1",
+ Exposed=Window]
+interface TestRenamedInterface {
+};
+
+[Exposed=Window]
+callback interface TestCallbackInterface {
+ readonly attribute long foo;
+ attribute DOMString bar;
+ undefined doSomething();
+ long doSomethingElse(DOMString arg, TestInterface otherArg);
+ undefined doSequenceLongArg(sequence<long> arg);
+ undefined doSequenceStringArg(sequence<DOMString> arg);
+ undefined doRecordLongArg(record<DOMString, long> arg);
+ sequence<long> getSequenceOfLong();
+ sequence<TestInterface> getSequenceOfInterfaces();
+ sequence<TestInterface>? getNullableSequenceOfInterfaces();
+ sequence<TestInterface?> getSequenceOfNullableInterfaces();
+ sequence<TestInterface?>? getNullableSequenceOfNullableInterfaces();
+ sequence<TestCallbackInterface> getSequenceOfCallbackInterfaces();
+ sequence<TestCallbackInterface>? getNullableSequenceOfCallbackInterfaces();
+ sequence<TestCallbackInterface?> getSequenceOfNullableCallbackInterfaces();
+ sequence<TestCallbackInterface?>? getNullableSequenceOfNullableCallbackInterfaces();
+ record<DOMString, long> getRecordOfLong();
+ Dict? getDictionary();
+ undefined passArrayBuffer(ArrayBuffer arg);
+ undefined passNullableArrayBuffer(ArrayBuffer? arg);
+ undefined passOptionalArrayBuffer(optional ArrayBuffer arg);
+ undefined passOptionalNullableArrayBuffer(optional ArrayBuffer? arg);
+ undefined passOptionalNullableArrayBufferWithDefaultValue(optional ArrayBuffer? arg= null);
+ undefined passArrayBufferView(ArrayBufferView arg);
+ undefined passInt8Array(Int8Array arg);
+ undefined passInt16Array(Int16Array arg);
+ undefined passInt32Array(Int32Array arg);
+ undefined passUint8Array(Uint8Array arg);
+ undefined passUint16Array(Uint16Array arg);
+ undefined passUint32Array(Uint32Array arg);
+ undefined passUint8ClampedArray(Uint8ClampedArray arg);
+ undefined passFloat32Array(Float32Array arg);
+ undefined passFloat64Array(Float64Array arg);
+ undefined passSequenceOfArrayBuffers(sequence<ArrayBuffer> arg);
+ undefined passSequenceOfNullableArrayBuffers(sequence<ArrayBuffer?> arg);
+ undefined passVariadicTypedArray(Float32Array... arg);
+ undefined passVariadicNullableTypedArray(Float32Array?... arg);
+ Uint8Array receiveUint8Array();
+ attribute Uint8Array uint8ArrayAttr;
+ Promise<undefined> receivePromise();
+};
+
+[Exposed=Window]
+callback interface TestSingleOperationCallbackInterface {
+ TestInterface doSomething(short arg, sequence<double> anotherArg);
+};
+
+enum TestEnum {
+ "1",
+ "a",
+ "b",
+ "1-2",
+ "2d-array"
+};
+
+callback TestCallback = undefined();
+[TreatNonCallableAsNull] callback TestTreatAsNullCallback = undefined();
+
+// Callback return value tests
+callback TestIntegerReturn = long();
+callback TestNullableIntegerReturn = long?();
+callback TestBooleanReturn = boolean();
+callback TestFloatReturn = float();
+callback TestStringReturn = DOMString(long arg);
+callback TestEnumReturn = TestEnum();
+callback TestInterfaceReturn = TestInterface();
+callback TestNullableInterfaceReturn = TestInterface?();
+callback TestExternalInterfaceReturn = TestExternalInterface();
+callback TestNullableExternalInterfaceReturn = TestExternalInterface?();
+callback TestCallbackInterfaceReturn = TestCallbackInterface();
+callback TestNullableCallbackInterfaceReturn = TestCallbackInterface?();
+callback TestCallbackReturn = TestCallback();
+callback TestNullableCallbackReturn = TestCallback?();
+callback TestObjectReturn = object();
+callback TestNullableObjectReturn = object?();
+callback TestTypedArrayReturn = ArrayBuffer();
+callback TestNullableTypedArrayReturn = ArrayBuffer?();
+callback TestSequenceReturn = sequence<boolean>();
+callback TestNullableSequenceReturn = sequence<boolean>?();
+// Callback argument tests
+callback TestIntegerArguments = sequence<long>(long arg1, long? arg2,
+ sequence<long> arg3,
+ sequence<long?>? arg4);
+callback TestInterfaceArguments = undefined(TestInterface arg1,
+ TestInterface? arg2,
+ TestExternalInterface arg3,
+ TestExternalInterface? arg4,
+ TestCallbackInterface arg5,
+ TestCallbackInterface? arg6,
+ sequence<TestInterface> arg7,
+ sequence<TestInterface?>? arg8,
+ sequence<TestExternalInterface> arg9,
+ sequence<TestExternalInterface?>? arg10,
+ sequence<TestCallbackInterface> arg11,
+ sequence<TestCallbackInterface?>? arg12);
+callback TestStringEnumArguments = undefined(DOMString myString, DOMString? nullString,
+ TestEnum myEnum);
+callback TestObjectArguments = undefined(object anObj, object? anotherObj,
+ ArrayBuffer buf, ArrayBuffer? buf2);
+callback TestOptionalArguments = undefined(optional DOMString aString,
+ optional object something,
+ optional sequence<TestInterface> aSeq,
+ optional TestInterface? anInterface,
+ optional TestInterface anotherInterface,
+ optional long aLong);
+// Callback constructor return value tests
+callback constructor TestUndefinedConstruction = undefined(TestDictionaryTypedef arg);
+callback constructor TestIntegerConstruction = unsigned long();
+callback constructor TestBooleanConstruction = boolean(any arg1,
+ optional any arg2);
+callback constructor TestFloatConstruction = unrestricted float(optional object arg1,
+ optional TestDictionaryTypedef arg2);
+callback constructor TestStringConstruction = DOMString(long? arg);
+callback constructor TestEnumConstruction = TestEnum(any... arg);
+callback constructor TestInterfaceConstruction = TestInterface();
+callback constructor TestExternalInterfaceConstruction = TestExternalInterface();
+callback constructor TestCallbackInterfaceConstruction = TestCallbackInterface();
+callback constructor TestCallbackConstruction = TestCallback();
+callback constructor TestObjectConstruction = object();
+callback constructor TestTypedArrayConstruction = ArrayBuffer();
+callback constructor TestSequenceConstruction = sequence<boolean>();
+// If you add a new test callback, add it to the forceCallbackGeneration
+// method on TestInterface so it actually gets tested.
+
+TestInterface includes InterfaceMixin;
+
+// This interface is only for use in the constructor below
+[Exposed=Window]
+interface OnlyForUseInConstructor {
+};
+
+// This enum is only for use in inner unions below
+enum OnlyForUseInInnerUnion {
+ "1",
+};
+
+[LegacyFactoryFunction=Test,
+ LegacyFactoryFunction=Test(DOMString str),
+ LegacyFactoryFunction=Test2(DictForConstructor dict, any any1, object obj1,
+ object? obj2, sequence<Dict> seq, optional any any2,
+ optional object obj3, optional object? obj4),
+ LegacyFactoryFunction=Test3((long or record<DOMString, any>) arg1),
+ LegacyFactoryFunction=Test4(record<DOMString, record<DOMString, any>> arg1),
+ LegacyFactoryFunction=Test5(record<DOMString, sequence<record<DOMString, record<DOMString, sequence<sequence<any>>>>>> arg1),
+ LegacyFactoryFunction=Test6(sequence<record<ByteString, sequence<sequence<record<ByteString, record<USVString, any>>>>>> arg1),
+ Exposed=Window]
+interface TestInterface {
+ constructor();
+ constructor(DOMString str);
+ constructor(unsigned long num, boolean? boolArg);
+ constructor(TestInterface? iface);
+ constructor(unsigned long arg1, TestInterface iface);
+ constructor(ArrayBuffer arrayBuf);
+ constructor(Uint8Array typedArr);
+ // constructor(long arg1, long arg2, (TestInterface or OnlyForUseInConstructor) arg3);
+
+ // Integer types
+ // XXXbz add tests for throwing versions of all the integer stuff
+ readonly attribute byte readonlyByte;
+ attribute byte writableByte;
+ undefined passByte(byte arg);
+ byte receiveByte();
+ undefined passOptionalByte(optional byte arg);
+ undefined passOptionalByteBeforeRequired(optional byte arg1, byte arg2);
+ undefined passOptionalByteWithDefault(optional byte arg = 0);
+ undefined passOptionalByteWithDefaultBeforeRequired(optional byte arg1 = 0, byte arg2);
+ undefined passNullableByte(byte? arg);
+ undefined passOptionalNullableByte(optional byte? arg);
+ undefined passVariadicByte(byte... arg);
+ [StoreInSlot, Pure]
+ readonly attribute byte cachedByte;
+ [StoreInSlot, Constant]
+ readonly attribute byte cachedConstantByte;
+ [StoreInSlot, Pure]
+ attribute byte cachedWritableByte;
+ [Affects=Nothing]
+ attribute byte sideEffectFreeByte;
+ [Affects=Nothing, DependsOn=DOMState]
+ attribute byte domDependentByte;
+ [Affects=Nothing, DependsOn=Nothing]
+ readonly attribute byte constantByte;
+ [DependsOn=DeviceState, Affects=Nothing]
+ readonly attribute byte deviceStateDependentByte;
+ [Affects=Nothing]
+ byte returnByteSideEffectFree();
+ [Affects=Nothing, DependsOn=DOMState]
+ byte returnDOMDependentByte();
+ [Affects=Nothing, DependsOn=Nothing]
+ byte returnConstantByte();
+ [DependsOn=DeviceState, Affects=Nothing]
+ byte returnDeviceStateDependentByte();
+
+ readonly attribute short readonlyShort;
+ attribute short writableShort;
+ undefined passShort(short arg);
+ short receiveShort();
+ undefined passOptionalShort(optional short arg);
+ undefined passOptionalShortWithDefault(optional short arg = 5);
+
+ readonly attribute long readonlyLong;
+ attribute long writableLong;
+ undefined passLong(long arg);
+ long receiveLong();
+ undefined passOptionalLong(optional long arg);
+ undefined passOptionalLongWithDefault(optional long arg = 7);
+
+ readonly attribute long long readonlyLongLong;
+ attribute long long writableLongLong;
+ undefined passLongLong(long long arg);
+ long long receiveLongLong();
+ undefined passOptionalLongLong(optional long long arg);
+ undefined passOptionalLongLongWithDefault(optional long long arg = -12);
+
+ readonly attribute octet readonlyOctet;
+ attribute octet writableOctet;
+ undefined passOctet(octet arg);
+ octet receiveOctet();
+ undefined passOptionalOctet(optional octet arg);
+ undefined passOptionalOctetWithDefault(optional octet arg = 19);
+
+ readonly attribute unsigned short readonlyUnsignedShort;
+ attribute unsigned short writableUnsignedShort;
+ undefined passUnsignedShort(unsigned short arg);
+ unsigned short receiveUnsignedShort();
+ undefined passOptionalUnsignedShort(optional unsigned short arg);
+ undefined passOptionalUnsignedShortWithDefault(optional unsigned short arg = 2);
+
+ readonly attribute unsigned long readonlyUnsignedLong;
+ attribute unsigned long writableUnsignedLong;
+ undefined passUnsignedLong(unsigned long arg);
+ unsigned long receiveUnsignedLong();
+ undefined passOptionalUnsignedLong(optional unsigned long arg);
+ undefined passOptionalUnsignedLongWithDefault(optional unsigned long arg = 6);
+
+ readonly attribute unsigned long long readonlyUnsignedLongLong;
+ attribute unsigned long long writableUnsignedLongLong;
+ undefined passUnsignedLongLong(unsigned long long arg);
+ unsigned long long receiveUnsignedLongLong();
+ undefined passOptionalUnsignedLongLong(optional unsigned long long arg);
+ undefined passOptionalUnsignedLongLongWithDefault(optional unsigned long long arg = 17);
+
+ attribute float writableFloat;
+ attribute unrestricted float writableUnrestrictedFloat;
+ attribute float? writableNullableFloat;
+ attribute unrestricted float? writableNullableUnrestrictedFloat;
+ attribute double writableDouble;
+ attribute unrestricted double writableUnrestrictedDouble;
+ attribute double? writableNullableDouble;
+ attribute unrestricted double? writableNullableUnrestrictedDouble;
+ undefined passFloat(float arg1, unrestricted float arg2,
+ float? arg3, unrestricted float? arg4,
+ double arg5, unrestricted double arg6,
+ double? arg7, unrestricted double? arg8,
+ sequence<float> arg9, sequence<unrestricted float> arg10,
+ sequence<float?> arg11, sequence<unrestricted float?> arg12,
+ sequence<double> arg13, sequence<unrestricted double> arg14,
+ sequence<double?> arg15, sequence<unrestricted double?> arg16);
+ [LenientFloat]
+ undefined passLenientFloat(float arg1, unrestricted float arg2,
+ float? arg3, unrestricted float? arg4,
+ double arg5, unrestricted double arg6,
+ double? arg7, unrestricted double? arg8,
+ sequence<float> arg9,
+ sequence<unrestricted float> arg10,
+ sequence<float?> arg11,
+ sequence<unrestricted float?> arg12,
+ sequence<double> arg13,
+ sequence<unrestricted double> arg14,
+ sequence<double?> arg15,
+ sequence<unrestricted double?> arg16);
+ [LenientFloat]
+ attribute float lenientFloatAttr;
+ [LenientFloat]
+ attribute double lenientDoubleAttr;
+
+ undefined passUnrestricted(optional unrestricted float arg1 = 0,
+ optional unrestricted float arg2 = Infinity,
+ optional unrestricted float arg3 = -Infinity,
+ optional unrestricted float arg4 = NaN,
+ optional unrestricted double arg5 = 0,
+ optional unrestricted double arg6 = Infinity,
+ optional unrestricted double arg7 = -Infinity,
+ optional unrestricted double arg8 = NaN);
+
+ // Castable interface types
+ // XXXbz add tests for throwing versions of all the castable interface stuff
+ TestInterface receiveSelf();
+ TestInterface? receiveNullableSelf();
+ TestInterface receiveWeakSelf();
+ TestInterface? receiveWeakNullableSelf();
+ undefined passSelf(TestInterface arg);
+ undefined passNullableSelf(TestInterface? arg);
+ attribute TestInterface nonNullSelf;
+ attribute TestInterface? nullableSelf;
+ [Cached, Pure]
+ readonly attribute TestInterface cachedSelf;
+ // Optional arguments
+ undefined passOptionalSelf(optional TestInterface? arg);
+ undefined passOptionalNonNullSelf(optional TestInterface arg);
+ undefined passOptionalSelfWithDefault(optional TestInterface? arg = null);
+
+ // Non-wrapper-cache interface types
+ [NewObject]
+ TestNonWrapperCacheInterface receiveNonWrapperCacheInterface();
+ [NewObject]
+ TestNonWrapperCacheInterface? receiveNullableNonWrapperCacheInterface();
+ [NewObject]
+ sequence<TestNonWrapperCacheInterface> receiveNonWrapperCacheInterfaceSequence();
+ [NewObject]
+ sequence<TestNonWrapperCacheInterface?> receiveNullableNonWrapperCacheInterfaceSequence();
+ [NewObject]
+ sequence<TestNonWrapperCacheInterface>? receiveNonWrapperCacheInterfaceNullableSequence();
+ [NewObject]
+ sequence<TestNonWrapperCacheInterface?>? receiveNullableNonWrapperCacheInterfaceNullableSequence();
+
+ // External interface types
+ TestExternalInterface receiveExternal();
+ TestExternalInterface? receiveNullableExternal();
+ TestExternalInterface receiveWeakExternal();
+ TestExternalInterface? receiveWeakNullableExternal();
+ undefined passExternal(TestExternalInterface arg);
+ undefined passNullableExternal(TestExternalInterface? arg);
+ attribute TestExternalInterface nonNullExternal;
+ attribute TestExternalInterface? nullableExternal;
+ // Optional arguments
+ undefined passOptionalExternal(optional TestExternalInterface? arg);
+ undefined passOptionalNonNullExternal(optional TestExternalInterface arg);
+ undefined passOptionalExternalWithDefault(optional TestExternalInterface? arg = null);
+
+ // Callback interface types
+ TestCallbackInterface receiveCallbackInterface();
+ TestCallbackInterface? receiveNullableCallbackInterface();
+ TestCallbackInterface receiveWeakCallbackInterface();
+ TestCallbackInterface? receiveWeakNullableCallbackInterface();
+ undefined passCallbackInterface(TestCallbackInterface arg);
+ undefined passNullableCallbackInterface(TestCallbackInterface? arg);
+ attribute TestCallbackInterface nonNullCallbackInterface;
+ attribute TestCallbackInterface? nullableCallbackInterface;
+ // Optional arguments
+ undefined passOptionalCallbackInterface(optional TestCallbackInterface? arg);
+ undefined passOptionalNonNullCallbackInterface(optional TestCallbackInterface arg);
+ undefined passOptionalCallbackInterfaceWithDefault(optional TestCallbackInterface? arg = null);
+
+ // Sequence types
+ [Cached, Pure]
+ readonly attribute sequence<long> readonlySequence;
+ [Cached, Pure]
+ readonly attribute sequence<Dict> readonlySequenceOfDictionaries;
+ [Cached, Pure]
+ readonly attribute sequence<Dict>? readonlyNullableSequenceOfDictionaries;
+ [Cached, Pure, Frozen]
+ readonly attribute sequence<Dict> readonlyFrozenSequence;
+ [Cached, Pure, Frozen]
+ readonly attribute sequence<Dict>? readonlyFrozenNullableSequence;
+ sequence<long> receiveSequence();
+ sequence<long>? receiveNullableSequence();
+ sequence<long?> receiveSequenceOfNullableInts();
+ sequence<long?>? receiveNullableSequenceOfNullableInts();
+ undefined passSequence(sequence<long> arg);
+ undefined passNullableSequence(sequence<long>? arg);
+ undefined passSequenceOfNullableInts(sequence<long?> arg);
+ undefined passOptionalSequenceOfNullableInts(optional sequence<long?> arg);
+ undefined passOptionalNullableSequenceOfNullableInts(optional sequence<long?>? arg);
+ sequence<TestInterface> receiveCastableObjectSequence();
+ sequence<TestCallbackInterface> receiveCallbackObjectSequence();
+ sequence<TestInterface?> receiveNullableCastableObjectSequence();
+ sequence<TestCallbackInterface?> receiveNullableCallbackObjectSequence();
+ sequence<TestInterface>? receiveCastableObjectNullableSequence();
+ sequence<TestInterface?>? receiveNullableCastableObjectNullableSequence();
+ sequence<TestInterface> receiveWeakCastableObjectSequence();
+ sequence<TestInterface?> receiveWeakNullableCastableObjectSequence();
+ sequence<TestInterface>? receiveWeakCastableObjectNullableSequence();
+ sequence<TestInterface?>? receiveWeakNullableCastableObjectNullableSequence();
+ undefined passCastableObjectSequence(sequence<TestInterface> arg);
+ undefined passNullableCastableObjectSequence(sequence<TestInterface?> arg);
+ undefined passCastableObjectNullableSequence(sequence<TestInterface>? arg);
+ undefined passNullableCastableObjectNullableSequence(sequence<TestInterface?>? arg);
+ undefined passOptionalSequence(optional sequence<long> arg);
+ undefined passOptionalSequenceWithDefaultValue(optional sequence<long> arg = []);
+ undefined passOptionalNullableSequence(optional sequence<long>? arg);
+ undefined passOptionalNullableSequenceWithDefaultValue(optional sequence<long>? arg = null);
+ undefined passOptionalNullableSequenceWithDefaultValue2(optional sequence<long>? arg = []);
+ undefined passOptionalObjectSequence(optional sequence<TestInterface> arg);
+ undefined passExternalInterfaceSequence(sequence<TestExternalInterface> arg);
+ undefined passNullableExternalInterfaceSequence(sequence<TestExternalInterface?> arg);
+
+ sequence<DOMString> receiveStringSequence();
+ undefined passStringSequence(sequence<DOMString> arg);
+
+ sequence<ByteString> receiveByteStringSequence();
+ undefined passByteStringSequence(sequence<ByteString> arg);
+
+ sequence<UTF8String> receiveUTF8StringSequence();
+ undefined passUTF8StringSequence(sequence<UTF8String> arg);
+
+ sequence<any> receiveAnySequence();
+ sequence<any>? receiveNullableAnySequence();
+ sequence<sequence<any>> receiveAnySequenceSequence();
+
+ sequence<object> receiveObjectSequence();
+ sequence<object?> receiveNullableObjectSequence();
+
+ undefined passSequenceOfSequences(sequence<sequence<long>> arg);
+ undefined passSequenceOfSequencesOfSequences(sequence<sequence<sequence<long>>> arg);
+ sequence<sequence<long>> receiveSequenceOfSequences();
+ sequence<sequence<sequence<long>>> receiveSequenceOfSequencesOfSequences();
+
+ // record types
+ undefined passRecord(record<DOMString, long> arg);
+ undefined passNullableRecord(record<DOMString, long>? arg);
+ undefined passRecordOfNullableInts(record<DOMString, long?> arg);
+ undefined passOptionalRecordOfNullableInts(optional record<DOMString, long?> arg);
+ undefined passOptionalNullableRecordOfNullableInts(optional record<DOMString, long?>? arg);
+ undefined passCastableObjectRecord(record<DOMString, TestInterface> arg);
+ undefined passNullableCastableObjectRecord(record<DOMString, TestInterface?> arg);
+ undefined passCastableObjectNullableRecord(record<DOMString, TestInterface>? arg);
+ undefined passNullableCastableObjectNullableRecord(record<DOMString, TestInterface?>? arg);
+ undefined passOptionalRecord(optional record<DOMString, long> arg);
+ undefined passOptionalNullableRecord(optional record<DOMString, long>? arg);
+ undefined passOptionalNullableRecordWithDefaultValue(optional record<DOMString, long>? arg = null);
+ undefined passOptionalObjectRecord(optional record<DOMString, TestInterface> arg);
+ undefined passExternalInterfaceRecord(record<DOMString, TestExternalInterface> arg);
+ undefined passNullableExternalInterfaceRecord(record<DOMString, TestExternalInterface?> arg);
+ undefined passStringRecord(record<DOMString, DOMString> arg);
+ undefined passByteStringRecord(record<DOMString, ByteString> arg);
+ undefined passUTF8StringRecord(record<DOMString, UTF8String> arg);
+ undefined passRecordOfRecords(record<DOMString, record<DOMString, long>> arg);
+ record<DOMString, long> receiveRecord();
+ record<DOMString, long>? receiveNullableRecord();
+ record<DOMString, long?> receiveRecordOfNullableInts();
+ record<DOMString, long?>? receiveNullableRecordOfNullableInts();
+ record<DOMString, record<DOMString, long>> receiveRecordOfRecords();
+ record<DOMString, any> receiveAnyRecord();
+
+ // Typed array types
+ undefined passArrayBuffer(ArrayBuffer arg);
+ undefined passNullableArrayBuffer(ArrayBuffer? arg);
+ undefined passOptionalArrayBuffer(optional ArrayBuffer arg);
+ undefined passOptionalNullableArrayBuffer(optional ArrayBuffer? arg);
+ undefined passOptionalNullableArrayBufferWithDefaultValue(optional ArrayBuffer? arg= null);
+ undefined passArrayBufferView(ArrayBufferView arg);
+ undefined passInt8Array(Int8Array arg);
+ undefined passInt16Array(Int16Array arg);
+ undefined passInt32Array(Int32Array arg);
+ undefined passUint8Array(Uint8Array arg);
+ undefined passUint16Array(Uint16Array arg);
+ undefined passUint32Array(Uint32Array arg);
+ undefined passUint8ClampedArray(Uint8ClampedArray arg);
+ undefined passFloat32Array(Float32Array arg);
+ undefined passFloat64Array(Float64Array arg);
+ undefined passSequenceOfArrayBuffers(sequence<ArrayBuffer> arg);
+ undefined passSequenceOfNullableArrayBuffers(sequence<ArrayBuffer?> arg);
+ undefined passRecordOfArrayBuffers(record<DOMString, ArrayBuffer> arg);
+ undefined passRecordOfNullableArrayBuffers(record<DOMString, ArrayBuffer?> arg);
+ undefined passVariadicTypedArray(Float32Array... arg);
+ undefined passVariadicNullableTypedArray(Float32Array?... arg);
+ Uint8Array receiveUint8Array();
+ attribute Uint8Array uint8ArrayAttr;
+
+ // DOMString types
+ undefined passString(DOMString arg);
+ undefined passNullableString(DOMString? arg);
+ undefined passOptionalString(optional DOMString arg);
+ undefined passOptionalStringWithDefaultValue(optional DOMString arg = "abc");
+ undefined passOptionalNullableString(optional DOMString? arg);
+ undefined passOptionalNullableStringWithDefaultValue(optional DOMString? arg = null);
+ undefined passVariadicString(DOMString... arg);
+ DOMString receiveString();
+
+ // ByteString types
+ undefined passByteString(ByteString arg);
+ undefined passNullableByteString(ByteString? arg);
+ undefined passOptionalByteString(optional ByteString arg);
+ undefined passOptionalByteStringWithDefaultValue(optional ByteString arg = "abc");
+ undefined passOptionalNullableByteString(optional ByteString? arg);
+ undefined passOptionalNullableByteStringWithDefaultValue(optional ByteString? arg = null);
+ undefined passVariadicByteString(ByteString... arg);
+ undefined passOptionalUnionByteString(optional (ByteString or long) arg);
+ undefined passOptionalUnionByteStringWithDefaultValue(optional (ByteString or long) arg = "abc");
+
+ // UTF8String types
+ undefined passUTF8String(UTF8String arg);
+ undefined passNullableUTF8String(UTF8String? arg);
+ undefined passOptionalUTF8String(optional UTF8String arg);
+ undefined passOptionalUTF8StringWithDefaultValue(optional UTF8String arg = "abc");
+ undefined passOptionalNullableUTF8String(optional UTF8String? arg);
+ undefined passOptionalNullableUTF8StringWithDefaultValue(optional UTF8String? arg = null);
+ undefined passVariadicUTF8String(UTF8String... arg);
+ undefined passOptionalUnionUTF8String(optional (UTF8String or long) arg);
+ undefined passOptionalUnionUTF8StringWithDefaultValue(optional (UTF8String or long) arg = "abc");
+
+ // USVString types
+ undefined passUSVS(USVString arg);
+ undefined passNullableUSVS(USVString? arg);
+ undefined passOptionalUSVS(optional USVString arg);
+ undefined passOptionalUSVSWithDefaultValue(optional USVString arg = "abc");
+ undefined passOptionalNullableUSVS(optional USVString? arg);
+ undefined passOptionalNullableUSVSWithDefaultValue(optional USVString? arg = null);
+ undefined passVariadicUSVS(USVString... arg);
+ USVString receiveUSVS();
+
+ // JSString types
+ undefined passJSString(JSString arg);
+ // undefined passNullableJSString(JSString? arg); // NOT SUPPORTED YET
+ // undefined passOptionalJSString(optional JSString arg); // NOT SUPPORTED YET
+ undefined passOptionalJSStringWithDefaultValue(optional JSString arg = "abc");
+ // undefined passOptionalNullableJSString(optional JSString? arg); // NOT SUPPORTED YET
+ // undefined passOptionalNullableJSStringWithDefaultValue(optional JSString? arg = null); // NOT SUPPORTED YET
+ // undefined passVariadicJSString(JSString... arg); // NOT SUPPORTED YET
+ // undefined passRecordOfJSString(record<DOMString, JSString> arg); // NOT SUPPORTED YET
+ // undefined passSequenceOfJSString(sequence<JSString> arg); // NOT SUPPORTED YET
+ // undefined passUnionJSString((JSString or long) arg); // NOT SUPPORTED YET
+ JSString receiveJSString();
+ // sequence<JSString> receiveJSStringSequence(); // NOT SUPPORTED YET
+ // (JSString or long) receiveJSStringUnion(); // NOT SUPPORTED YET
+ // record<DOMString, JSString> receiveJSStringRecord(); // NOT SUPPORTED YET
+ readonly attribute JSString readonlyJSStringAttr;
+ attribute JSString jsStringAttr;
+
+ // Enumerated types
+ undefined passEnum(TestEnum arg);
+ undefined passNullableEnum(TestEnum? arg);
+ undefined passOptionalEnum(optional TestEnum arg);
+ undefined passEnumWithDefault(optional TestEnum arg = "a");
+ undefined passOptionalNullableEnum(optional TestEnum? arg);
+ undefined passOptionalNullableEnumWithDefaultValue(optional TestEnum? arg = null);
+ undefined passOptionalNullableEnumWithDefaultValue2(optional TestEnum? arg = "a");
+ TestEnum receiveEnum();
+ TestEnum? receiveNullableEnum();
+ attribute TestEnum enumAttribute;
+ readonly attribute TestEnum readonlyEnumAttribute;
+
+ // Callback types
+ undefined passCallback(TestCallback arg);
+ undefined passNullableCallback(TestCallback? arg);
+ undefined passOptionalCallback(optional TestCallback arg);
+ undefined passOptionalNullableCallback(optional TestCallback? arg);
+ undefined passOptionalNullableCallbackWithDefaultValue(optional TestCallback? arg = null);
+ TestCallback receiveCallback();
+ TestCallback? receiveNullableCallback();
+ undefined passNullableTreatAsNullCallback(TestTreatAsNullCallback? arg);
+ undefined passOptionalNullableTreatAsNullCallback(optional TestTreatAsNullCallback? arg);
+ undefined passOptionalNullableTreatAsNullCallbackWithDefaultValue(optional TestTreatAsNullCallback? arg = null);
+ attribute TestTreatAsNullCallback treatAsNullCallback;
+ attribute TestTreatAsNullCallback? nullableTreatAsNullCallback;
+
+ // Force code generation of the various test callbacks we have.
+ undefined forceCallbackGeneration(TestIntegerReturn arg1,
+ TestNullableIntegerReturn arg2,
+ TestBooleanReturn arg3,
+ TestFloatReturn arg4,
+ TestStringReturn arg5,
+ TestEnumReturn arg6,
+ TestInterfaceReturn arg7,
+ TestNullableInterfaceReturn arg8,
+ TestExternalInterfaceReturn arg9,
+ TestNullableExternalInterfaceReturn arg10,
+ TestCallbackInterfaceReturn arg11,
+ TestNullableCallbackInterfaceReturn arg12,
+ TestCallbackReturn arg13,
+ TestNullableCallbackReturn arg14,
+ TestObjectReturn arg15,
+ TestNullableObjectReturn arg16,
+ TestTypedArrayReturn arg17,
+ TestNullableTypedArrayReturn arg18,
+ TestSequenceReturn arg19,
+ TestNullableSequenceReturn arg20,
+ TestIntegerArguments arg21,
+ TestInterfaceArguments arg22,
+ TestStringEnumArguments arg23,
+ TestObjectArguments arg24,
+ TestOptionalArguments arg25,
+ TestUnionArguments arg26,
+ TestUndefinedConstruction arg27,
+ TestIntegerConstruction arg28,
+ TestBooleanConstruction arg29,
+ TestFloatConstruction arg30,
+ TestStringConstruction arg31,
+ TestEnumConstruction arg32,
+ TestInterfaceConstruction arg33,
+ TestExternalInterfaceConstruction arg34,
+ TestCallbackInterfaceConstruction arg35,
+ TestCallbackConstruction arg36,
+ TestObjectConstruction arg37,
+ TestTypedArrayConstruction arg38,
+ TestSequenceConstruction arg39);
+
+ // Any types
+ undefined passAny(any arg);
+ undefined passVariadicAny(any... arg);
+ undefined passOptionalAny(optional any arg);
+ undefined passAnyDefaultNull(optional any arg = null);
+ undefined passSequenceOfAny(sequence<any> arg);
+ undefined passNullableSequenceOfAny(sequence<any>? arg);
+ undefined passOptionalSequenceOfAny(optional sequence<any> arg);
+ undefined passOptionalNullableSequenceOfAny(optional sequence<any>? arg);
+ undefined passOptionalSequenceOfAnyWithDefaultValue(optional sequence<any>? arg = null);
+ undefined passSequenceOfSequenceOfAny(sequence<sequence<any>> arg);
+ undefined passSequenceOfNullableSequenceOfAny(sequence<sequence<any>?> arg);
+ undefined passNullableSequenceOfNullableSequenceOfAny(sequence<sequence<any>?>? arg);
+ undefined passOptionalNullableSequenceOfNullableSequenceOfAny(optional sequence<sequence<any>?>? arg);
+ undefined passRecordOfAny(record<DOMString, any> arg);
+ undefined passNullableRecordOfAny(record<DOMString, any>? arg);
+ undefined passOptionalRecordOfAny(optional record<DOMString, any> arg);
+ undefined passOptionalNullableRecordOfAny(optional record<DOMString, any>? arg);
+ undefined passOptionalRecordOfAnyWithDefaultValue(optional record<DOMString, any>? arg = null);
+ undefined passRecordOfRecordOfAny(record<DOMString, record<DOMString, any>> arg);
+ undefined passRecordOfNullableRecordOfAny(record<DOMString, record<DOMString, any>?> arg);
+ undefined passNullableRecordOfNullableRecordOfAny(record<DOMString, record<DOMString, any>?>? arg);
+ undefined passOptionalNullableRecordOfNullableRecordOfAny(optional record<DOMString, record<DOMString, any>?>? arg);
+ undefined passOptionalNullableRecordOfNullableSequenceOfAny(optional record<DOMString, sequence<any>?>? arg);
+ undefined passOptionalNullableSequenceOfNullableRecordOfAny(optional sequence<record<DOMString, any>?>? arg);
+ any receiveAny();
+
+ // object types
+ undefined passObject(object arg);
+ undefined passVariadicObject(object... arg);
+ undefined passNullableObject(object? arg);
+ undefined passVariadicNullableObject(object... arg);
+ undefined passOptionalObject(optional object arg);
+ undefined passOptionalNullableObject(optional object? arg);
+ undefined passOptionalNullableObjectWithDefaultValue(optional object? arg = null);
+ undefined passSequenceOfObject(sequence<object> arg);
+ undefined passSequenceOfNullableObject(sequence<object?> arg);
+ undefined passNullableSequenceOfObject(sequence<object>? arg);
+ undefined passOptionalNullableSequenceOfNullableSequenceOfObject(optional sequence<sequence<object>?>? arg);
+ undefined passOptionalNullableSequenceOfNullableSequenceOfNullableObject(optional sequence<sequence<object?>?>? arg);
+ undefined passRecordOfObject(record<DOMString, object> arg);
+ object receiveObject();
+ object? receiveNullableObject();
+
+ // Union types
+ undefined passUnion((object or long) arg);
+ // Some union tests are debug-only to avoid creating all those
+ // unused union types in opt builds.
+#ifdef DEBUG
+ undefined passUnion2((long or boolean) arg);
+ undefined passUnion3((object or long or boolean) arg);
+ undefined passUnion4((Node or long or boolean) arg);
+ undefined passUnion5((object or boolean) arg);
+ undefined passUnion6((object or DOMString) arg);
+ undefined passUnion7((object or DOMString or long) arg);
+ undefined passUnion8((object or DOMString or boolean) arg);
+ undefined passUnion9((object or DOMString or long or boolean) arg);
+ undefined passUnion10(optional (EventInit or long) arg = {});
+ undefined passUnion11(optional (CustomEventInit or long) arg = {});
+ undefined passUnion12(optional (EventInit or long) arg = 5);
+ undefined passUnion13(optional (object or long?) arg = null);
+ undefined passUnion14(optional (object or long?) arg = 5);
+ undefined passUnion15((sequence<long> or long) arg);
+ undefined passUnion16(optional (sequence<long> or long) arg);
+ undefined passUnion17(optional (sequence<long>? or long) arg = 5);
+ undefined passUnion18((sequence<object> or long) arg);
+ undefined passUnion19(optional (sequence<object> or long) arg);
+ undefined passUnion20(optional (sequence<object> or long) arg = []);
+ undefined passUnion21((record<DOMString, long> or long) arg);
+ undefined passUnion22((record<DOMString, object> or long) arg);
+ undefined passUnion23((sequence<ImageData> or long) arg);
+ undefined passUnion24((sequence<ImageData?> or long) arg);
+ undefined passUnion25((sequence<sequence<ImageData>> or long) arg);
+ undefined passUnion26((sequence<sequence<ImageData?>> or long) arg);
+ undefined passUnion27(optional (sequence<DOMString> or EventInit) arg = {});
+ undefined passUnion28(optional (EventInit or sequence<DOMString>) arg = {});
+ undefined passUnionWithCallback((EventHandler or long) arg);
+ undefined passUnionWithByteString((ByteString or long) arg);
+ undefined passUnionWithUTF8String((UTF8String or long) arg);
+ undefined passUnionWithRecord((record<DOMString, DOMString> or DOMString) arg);
+ undefined passUnionWithRecordAndSequence((record<DOMString, DOMString> or sequence<DOMString>) arg);
+ undefined passUnionWithSequenceAndRecord((sequence<DOMString> or record<DOMString, DOMString>) arg);
+ undefined passUnionWithUSVS((USVString or long) arg);
+#endif
+ undefined passUnionWithNullable((object? or long) arg);
+ undefined passNullableUnion((object or long)? arg);
+ undefined passOptionalUnion(optional (object or long) arg);
+ undefined passOptionalNullableUnion(optional (object or long)? arg);
+ undefined passOptionalNullableUnionWithDefaultValue(optional (object or long)? arg = null);
+ //undefined passUnionWithInterfaces((TestInterface or TestExternalInterface) arg);
+ //undefined passUnionWithInterfacesAndNullable((TestInterface? or TestExternalInterface) arg);
+ //undefined passUnionWithSequence((sequence<object> or long) arg);
+ undefined passUnionWithArrayBuffer((ArrayBuffer or long) arg);
+ undefined passUnionWithString((DOMString or object) arg);
+ // Using an enum in a union. Note that we use some enum not declared in our
+ // binding file, because UnionTypes.h will need to include the binding header
+ // for this enum. Pick an enum from an interface that won't drag in too much
+ // stuff.
+ undefined passUnionWithEnum((SupportedType or object) arg);
+
+ // Trying to use a callback in a union won't include the test
+ // headers, unfortunately, so won't compile.
+ //undefined passUnionWithCallback((TestCallback or long) arg);
+ undefined passUnionWithObject((object or long) arg);
+ //undefined passUnionWithDict((Dict or long) arg);
+
+ undefined passUnionWithDefaultValue1(optional (double or DOMString) arg = "");
+ undefined passUnionWithDefaultValue2(optional (double or DOMString) arg = 1);
+ undefined passUnionWithDefaultValue3(optional (double or DOMString) arg = 1.5);
+ undefined passUnionWithDefaultValue4(optional (float or DOMString) arg = "");
+ undefined passUnionWithDefaultValue5(optional (float or DOMString) arg = 1);
+ undefined passUnionWithDefaultValue6(optional (float or DOMString) arg = 1.5);
+ undefined passUnionWithDefaultValue7(optional (unrestricted double or DOMString) arg = "");
+ undefined passUnionWithDefaultValue8(optional (unrestricted double or DOMString) arg = 1);
+ undefined passUnionWithDefaultValue9(optional (unrestricted double or DOMString) arg = 1.5);
+ undefined passUnionWithDefaultValue10(optional (unrestricted double or DOMString) arg = Infinity);
+ undefined passUnionWithDefaultValue11(optional (unrestricted float or DOMString) arg = "");
+ undefined passUnionWithDefaultValue12(optional (unrestricted float or DOMString) arg = 1);
+ undefined passUnionWithDefaultValue13(optional (unrestricted float or DOMString) arg = Infinity);
+ undefined passUnionWithDefaultValue14(optional (double or ByteString) arg = "");
+ undefined passUnionWithDefaultValue15(optional (double or ByteString) arg = 1);
+ undefined passUnionWithDefaultValue16(optional (double or ByteString) arg = 1.5);
+ undefined passUnionWithDefaultValue17(optional (double or SupportedType) arg = "text/html");
+ undefined passUnionWithDefaultValue18(optional (double or SupportedType) arg = 1);
+ undefined passUnionWithDefaultValue19(optional (double or SupportedType) arg = 1.5);
+ undefined passUnionWithDefaultValue20(optional (double or USVString) arg = "abc");
+ undefined passUnionWithDefaultValue21(optional (double or USVString) arg = 1);
+ undefined passUnionWithDefaultValue22(optional (double or USVString) arg = 1.5);
+ undefined passUnionWithDefaultValue23(optional (double or UTF8String) arg = "");
+ undefined passUnionWithDefaultValue24(optional (double or UTF8String) arg = 1);
+ undefined passUnionWithDefaultValue25(optional (double or UTF8String) arg = 1.5);
+
+ undefined passNullableUnionWithDefaultValue1(optional (double or DOMString)? arg = "");
+ undefined passNullableUnionWithDefaultValue2(optional (double or DOMString)? arg = 1);
+ undefined passNullableUnionWithDefaultValue3(optional (double or DOMString)? arg = null);
+ undefined passNullableUnionWithDefaultValue4(optional (float or DOMString)? arg = "");
+ undefined passNullableUnionWithDefaultValue5(optional (float or DOMString)? arg = 1);
+ undefined passNullableUnionWithDefaultValue6(optional (float or DOMString)? arg = null);
+ undefined passNullableUnionWithDefaultValue7(optional (unrestricted double or DOMString)? arg = "");
+ undefined passNullableUnionWithDefaultValue8(optional (unrestricted double or DOMString)? arg = 1);
+ undefined passNullableUnionWithDefaultValue9(optional (unrestricted double or DOMString)? arg = null);
+ undefined passNullableUnionWithDefaultValue10(optional (unrestricted float or DOMString)? arg = "");
+ undefined passNullableUnionWithDefaultValue11(optional (unrestricted float or DOMString)? arg = 1);
+ undefined passNullableUnionWithDefaultValue12(optional (unrestricted float or DOMString)? arg = null);
+ undefined passNullableUnionWithDefaultValue13(optional (double or ByteString)? arg = "");
+ undefined passNullableUnionWithDefaultValue14(optional (double or ByteString)? arg = 1);
+ undefined passNullableUnionWithDefaultValue15(optional (double or ByteString)? arg = 1.5);
+ undefined passNullableUnionWithDefaultValue16(optional (double or ByteString)? arg = null);
+ undefined passNullableUnionWithDefaultValue17(optional (double or SupportedType)? arg = "text/html");
+ undefined passNullableUnionWithDefaultValue18(optional (double or SupportedType)? arg = 1);
+ undefined passNullableUnionWithDefaultValue19(optional (double or SupportedType)? arg = 1.5);
+ undefined passNullableUnionWithDefaultValue20(optional (double or SupportedType)? arg = null);
+ undefined passNullableUnionWithDefaultValue21(optional (double or USVString)? arg = "abc");
+ undefined passNullableUnionWithDefaultValue22(optional (double or USVString)? arg = 1);
+ undefined passNullableUnionWithDefaultValue23(optional (double or USVString)? arg = 1.5);
+ undefined passNullableUnionWithDefaultValue24(optional (double or USVString)? arg = null);
+
+ undefined passNullableUnionWithDefaultValue25(optional (double or UTF8String)? arg = "abc");
+ undefined passNullableUnionWithDefaultValue26(optional (double or UTF8String)? arg = 1);
+ undefined passNullableUnionWithDefaultValue27(optional (double or UTF8String)? arg = 1.5);
+ undefined passNullableUnionWithDefaultValue28(optional (double or UTF8String)? arg = null);
+
+ undefined passSequenceOfUnions(sequence<(CanvasPattern or CanvasGradient)> arg);
+ undefined passSequenceOfUnions2(sequence<(object or long)> arg);
+ undefined passVariadicUnion((CanvasPattern or CanvasGradient)... arg);
+
+ undefined passSequenceOfNullableUnions(sequence<(CanvasPattern or CanvasGradient)?> arg);
+ undefined passVariadicNullableUnion((CanvasPattern or CanvasGradient)?... arg);
+ undefined passRecordOfUnions(record<DOMString, (CanvasPattern or CanvasGradient)> arg);
+
+ // Each inner union in the following APIs should have a unique set
+ // of union member types, not used in any other API.
+ undefined passUnionWithSequenceOfUnions((DOMString or sequence<(OnlyForUseInInnerUnion or CanvasPattern)>) arg);
+ //undefined passUnionWithFrozenArrayOfUnions((DOMString or FrozenArray<(OnlyForUseInInnerUnion or CanvasGradient)>) arg);
+ undefined passUnionWithRecordOfUnions((sequence<long> or record<DOMString, (OnlyForUseInInnerUnion or sequence<long>)>) arg);
+
+ // XXXbz no move constructor on some unions
+ // undefined passRecordOfUnions2(record<DOMString, (object or long)> arg);
+
+ (CanvasPattern or CanvasGradient) receiveUnion();
+ (object or long) receiveUnion2();
+ (CanvasPattern? or CanvasGradient) receiveUnionContainingNull();
+ (CanvasPattern or CanvasGradient)? receiveNullableUnion();
+ (object or long)? receiveNullableUnion2();
+ (undefined or CanvasPattern) receiveUnionWithUndefined();
+ (undefined? or CanvasPattern) receiveUnionWithNullableUndefined();
+ (undefined or CanvasPattern?) receiveUnionWithUndefinedAndNullable();
+ (undefined or CanvasPattern)? receiveNullableUnionWithUndefined();
+
+ attribute (CanvasPattern or CanvasGradient) writableUnion;
+ attribute (CanvasPattern? or CanvasGradient) writableUnionContainingNull;
+ attribute (CanvasPattern or CanvasGradient)? writableNullableUnion;
+ attribute (undefined or CanvasPattern) writableUnionWithUndefined;
+ attribute (undefined? or CanvasPattern) writableUnionWithNullableUndefined;
+ attribute (undefined or CanvasPattern?) writableUnionWithUndefinedAndNullable;
+ attribute (undefined or CanvasPattern)? writableNullableUnionWithUndefined;
+
+ // Promise types
+ undefined passPromise(Promise<any> arg);
+ undefined passOptionalPromise(optional Promise<any> arg);
+ undefined passPromiseSequence(sequence<Promise<any>> arg);
+ Promise<any> receivePromise();
+ Promise<any> receiveAddrefedPromise();
+
+ // ObservableArray types
+ attribute ObservableArray<boolean> booleanObservableArray;
+ attribute ObservableArray<object> objectObservableArray;
+ attribute ObservableArray<any> anyObservableArray;
+ attribute ObservableArray<TestInterface> interfaceObservableArray;
+ attribute ObservableArray<long?> nullableObservableArray;
+
+ // binaryNames tests
+ [BinaryName="methodRenamedTo"]
+ undefined methodRenamedFrom();
+ [BinaryName="methodRenamedTo"]
+ undefined methodRenamedFrom(byte argument);
+ [BinaryName="attributeGetterRenamedTo"]
+ readonly attribute byte attributeGetterRenamedFrom;
+ [BinaryName="attributeRenamedTo"]
+ attribute byte attributeRenamedFrom;
+
+ undefined passDictionary(optional Dict x = {});
+ undefined passDictionary2(Dict x);
+ [Cached, Pure]
+ readonly attribute Dict readonlyDictionary;
+ [Cached, Pure]
+ readonly attribute Dict? readonlyNullableDictionary;
+ [Cached, Pure]
+ attribute Dict writableDictionary;
+ [Cached, Pure, Frozen]
+ readonly attribute Dict readonlyFrozenDictionary;
+ [Cached, Pure, Frozen]
+ readonly attribute Dict? readonlyFrozenNullableDictionary;
+ [Cached, Pure, Frozen]
+ attribute Dict writableFrozenDictionary;
+ Dict receiveDictionary();
+ Dict? receiveNullableDictionary();
+ undefined passOtherDictionary(optional GrandparentDict x = {});
+ undefined passSequenceOfDictionaries(sequence<Dict> x);
+ undefined passRecordOfDictionaries(record<DOMString, GrandparentDict> x);
+ // No support for nullable dictionaries inside a sequence (nor should there be)
+ // undefined passSequenceOfNullableDictionaries(sequence<Dict?> x);
+ undefined passDictionaryOrLong(optional Dict x = {});
+ undefined passDictionaryOrLong(long x);
+
+ undefined passDictContainingDict(optional DictContainingDict arg = {});
+ undefined passDictContainingSequence(optional DictContainingSequence arg = {});
+ DictContainingSequence receiveDictContainingSequence();
+ undefined passVariadicDictionary(Dict... arg);
+
+ // EnforceRange/Clamp tests
+ undefined dontEnforceRangeOrClamp(byte arg);
+ undefined doEnforceRange([EnforceRange] byte arg);
+ undefined doEnforceRangeNullable([EnforceRange] byte? arg);
+ undefined doClamp([Clamp] byte arg);
+ undefined doClampNullable([Clamp] byte? arg);
+ attribute [EnforceRange] byte enforcedByte;
+ attribute [EnforceRange] byte? enforcedNullableByte;
+ attribute [Clamp] byte clampedByte;
+ attribute [Clamp] byte? clampedNullableByte;
+
+ // Typedefs
+ const myLong myLongConstant = 5;
+ undefined exerciseTypedefInterfaces1(AnotherNameForTestInterface arg);
+ AnotherNameForTestInterface exerciseTypedefInterfaces2(NullableTestInterface arg);
+ undefined exerciseTypedefInterfaces3(YetAnotherNameForTestInterface arg);
+
+ // Deprecated methods and attributes
+ [Deprecated="Components"]
+ attribute byte deprecatedAttribute;
+ [Deprecated="Components"]
+ byte deprecatedMethod();
+ [Deprecated="Components"]
+ byte deprecatedMethodWithContext(any arg);
+
+ // Static methods and attributes
+ static attribute boolean staticAttribute;
+ static undefined staticMethod(boolean arg);
+ static undefined staticMethodWithContext(any arg);
+
+ // Testing static method with a reserved C++ keyword as the name
+ static undefined assert(boolean arg);
+
+ // Deprecated static methods and attributes
+ [Deprecated="Components"]
+ static attribute byte staticDeprecatedAttribute;
+ [Deprecated="Components"]
+ static undefined staticDeprecatedMethod();
+ [Deprecated="Components"]
+ static undefined staticDeprecatedMethodWithContext(any arg);
+
+ // Overload resolution tests
+ //undefined overload1(DOMString... strs);
+ boolean overload1(TestInterface arg);
+ TestInterface overload1(DOMString strs, TestInterface arg);
+ undefined overload2(TestInterface arg);
+ undefined overload2(optional Dict arg = {});
+ undefined overload2(boolean arg);
+ undefined overload2(DOMString arg);
+ undefined overload3(TestInterface arg);
+ undefined overload3(TestCallback arg);
+ undefined overload3(boolean arg);
+ undefined overload4(TestInterface arg);
+ undefined overload4(TestCallbackInterface arg);
+ undefined overload4(DOMString arg);
+ undefined overload5(long arg);
+ undefined overload5(TestEnum arg);
+ undefined overload6(long arg);
+ undefined overload6(boolean arg);
+ undefined overload7(long arg);
+ undefined overload7(boolean arg);
+ undefined overload7(ByteString arg);
+ undefined overload8(long arg);
+ undefined overload8(TestInterface arg);
+ undefined overload9(long? arg);
+ undefined overload9(DOMString arg);
+ undefined overload10(long? arg);
+ undefined overload10(object arg);
+ undefined overload11(long arg);
+ undefined overload11(DOMString? arg);
+ undefined overload12(long arg);
+ undefined overload12(boolean? arg);
+ undefined overload13(long? arg);
+ undefined overload13(boolean arg);
+ undefined overload14(optional long arg);
+ undefined overload14(TestInterface arg);
+ undefined overload15(long arg);
+ undefined overload15(optional TestInterface arg);
+ undefined overload16(long arg);
+ undefined overload16(optional TestInterface? arg);
+ undefined overload17(sequence<long> arg);
+ undefined overload17(record<DOMString, long> arg);
+ undefined overload18(record<DOMString, DOMString> arg);
+ undefined overload18(sequence<DOMString> arg);
+ undefined overload19(sequence<long> arg);
+ undefined overload19(optional Dict arg = {});
+ undefined overload20(optional Dict arg = {});
+ undefined overload20(sequence<long> arg);
+
+ // Variadic handling
+ undefined passVariadicThirdArg(DOMString arg1, long arg2, TestInterface... arg3);
+
+ // Conditionally exposed methods/attributes
+ [Pref="dom.webidl.test1"]
+ readonly attribute boolean prefable1;
+ [Pref="dom.webidl.test1"]
+ readonly attribute boolean prefable2;
+ [Pref="dom.webidl.test2"]
+ readonly attribute boolean prefable3;
+ [Pref="dom.webidl.test2"]
+ readonly attribute boolean prefable4;
+ [Pref="dom.webidl.test1"]
+ readonly attribute boolean prefable5;
+ [Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ readonly attribute boolean prefable6;
+ [Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ readonly attribute boolean prefable7;
+ [Pref="dom.webidl.test2", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ readonly attribute boolean prefable8;
+ [Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ readonly attribute boolean prefable9;
+ [Pref="dom.webidl.test1"]
+ undefined prefable10();
+ [Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ undefined prefable11();
+ [Pref="dom.webidl.test1", Func="TestFuncControlledMember"]
+ readonly attribute boolean prefable12;
+ [Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ undefined prefable13();
+ [Pref="dom.webidl.test1", Func="TestFuncControlledMember"]
+ readonly attribute boolean prefable14;
+ [Func="TestFuncControlledMember"]
+ readonly attribute boolean prefable15;
+ [Func="TestFuncControlledMember"]
+ readonly attribute boolean prefable16;
+ [Pref="dom.webidl.test1", Func="TestFuncControlledMember"]
+ undefined prefable17();
+ [Func="TestFuncControlledMember"]
+ undefined prefable18();
+ [Func="TestFuncControlledMember"]
+ undefined prefable19();
+ [Pref="dom.webidl.test1", Func="TestFuncControlledMember", ChromeOnly]
+ undefined prefable20();
+ [Trial="TestTrial"]
+ undefined prefable21();
+ [Pref="dom.webidl.test1", Trial="TestTrial"]
+ undefined prefable22();
+
+ // Conditionally exposed methods/attributes involving [SecureContext]
+ [SecureContext]
+ readonly attribute boolean conditionalOnSecureContext1;
+ [SecureContext, Pref="dom.webidl.test1"]
+ readonly attribute boolean conditionalOnSecureContext2;
+ [SecureContext, Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ readonly attribute boolean conditionalOnSecureContext3;
+ [SecureContext, Pref="dom.webidl.test1", Func="TestFuncControlledMember"]
+ readonly attribute boolean conditionalOnSecureContext4;
+ [SecureContext]
+ undefined conditionalOnSecureContext5();
+ [SecureContext, Pref="dom.webidl.test1"]
+ undefined conditionalOnSecureContext6();
+ [SecureContext, Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ undefined conditionalOnSecureContext7();
+ [SecureContext, Pref="dom.webidl.test1", Func="TestFuncControlledMember"]
+ undefined conditionalOnSecureContext8();
+ [SecureContext, Trial="TestTrial"]
+ readonly attribute boolean conditionalOnSecureContext9;
+ [SecureContext, Pref="dom.webidl.test1", Func="TestFuncControlledMember", Trial="TestTrial"]
+ undefined conditionalOnSecureContext10();
+
+ // Miscellania
+ [LegacyLenientThis] attribute long attrWithLenientThis;
+ [LegacyUnforgeable] readonly attribute long unforgeableAttr;
+ [LegacyUnforgeable, ChromeOnly] readonly attribute long unforgeableAttr2;
+ [LegacyUnforgeable] long unforgeableMethod();
+ [LegacyUnforgeable, ChromeOnly] long unforgeableMethod2();
+ stringifier;
+ undefined passRenamedInterface(TestRenamedInterface arg);
+ [PutForwards=writableByte] readonly attribute TestInterface putForwardsAttr;
+ [PutForwards=writableByte, LegacyLenientThis] readonly attribute TestInterface putForwardsAttr2;
+ [PutForwards=writableByte, ChromeOnly] readonly attribute TestInterface putForwardsAttr3;
+ [Throws] undefined throwingMethod();
+ [Throws] attribute boolean throwingAttr;
+ [GetterThrows] attribute boolean throwingGetterAttr;
+ [SetterThrows] attribute boolean throwingSetterAttr;
+ [CanOOM] undefined canOOMMethod();
+ [CanOOM] attribute boolean canOOMAttr;
+ [GetterCanOOM] attribute boolean canOOMGetterAttr;
+ [SetterCanOOM] attribute boolean canOOMSetterAttr;
+ [NeedsSubjectPrincipal] undefined needsSubjectPrincipalMethod();
+ [NeedsSubjectPrincipal] attribute boolean needsSubjectPrincipalAttr;
+ [NeedsCallerType] undefined needsCallerTypeMethod();
+ [NeedsCallerType] attribute boolean needsCallerTypeAttr;
+ [NeedsSubjectPrincipal=NonSystem] undefined needsNonSystemSubjectPrincipalMethod();
+ [NeedsSubjectPrincipal=NonSystem] attribute boolean needsNonSystemSubjectPrincipalAttr;
+ [CEReactions] undefined ceReactionsMethod();
+ [CEReactions] undefined ceReactionsMethodOverload();
+ [CEReactions] undefined ceReactionsMethodOverload(DOMString bar);
+ [CEReactions] attribute boolean ceReactionsAttr;
+ legacycaller short(unsigned long arg1, TestInterface arg2);
+ undefined passArgsWithDefaults(optional long arg1,
+ optional TestInterface? arg2 = null,
+ optional Dict arg3 = {}, optional double arg4 = 5.0,
+ optional float arg5);
+
+ attribute any toJSONShouldSkipThis;
+ attribute TestParentInterface toJSONShouldSkipThis2;
+ attribute TestCallbackInterface toJSONShouldSkipThis3;
+ [Default] object toJSON();
+
+ attribute byte dashed-attribute;
+ undefined dashed-method();
+
+ // [NonEnumerable] tests
+ [NonEnumerable]
+ attribute boolean nonEnumerableAttr;
+ [NonEnumerable]
+ const boolean nonEnumerableConst = true;
+ [NonEnumerable]
+ undefined nonEnumerableMethod();
+
+ // [AllowShared] tests
+ attribute [AllowShared] ArrayBufferViewTypedef allowSharedArrayBufferViewTypedef;
+ attribute [AllowShared] ArrayBufferView allowSharedArrayBufferView;
+ attribute [AllowShared] ArrayBufferView? allowSharedNullableArrayBufferView;
+ attribute [AllowShared] ArrayBuffer allowSharedArrayBuffer;
+ attribute [AllowShared] ArrayBuffer? allowSharedNullableArrayBuffer;
+
+ undefined passAllowSharedArrayBufferViewTypedef(AllowSharedArrayBufferViewTypedef foo);
+ undefined passAllowSharedArrayBufferView([AllowShared] ArrayBufferView foo);
+ undefined passAllowSharedNullableArrayBufferView([AllowShared] ArrayBufferView? foo);
+ undefined passAllowSharedArrayBuffer([AllowShared] ArrayBuffer foo);
+ undefined passAllowSharedNullableArrayBuffer([AllowShared] ArrayBuffer? foo);
+ undefined passUnionArrayBuffer((DOMString or ArrayBuffer) foo);
+ undefined passUnionAllowSharedArrayBuffer((DOMString or [AllowShared] ArrayBuffer) foo);
+
+ // If you add things here, add them to TestExampleGen and TestJSImplGen as well
+};
+
+[Exposed=Window]
+interface TestParentInterface {
+};
+
+[Exposed=Window]
+interface TestChildInterface : TestParentInterface {
+};
+
+[Exposed=Window]
+interface TestNonWrapperCacheInterface {
+};
+
+interface mixin InterfaceMixin {
+ undefined mixedInMethod();
+ attribute boolean mixedInProperty;
+
+ const long mixedInConstant = 5;
+};
+
+dictionary Dict : ParentDict {
+ TestEnum someEnum;
+ long x;
+ long a;
+ long b = 8;
+ long z = 9;
+ [EnforceRange] unsigned long enforcedUnsignedLong;
+ [Clamp] unsigned long clampedUnsignedLong;
+ DOMString str;
+ DOMString empty = "";
+ TestEnum otherEnum = "b";
+ DOMString otherStr = "def";
+ DOMString? yetAnotherStr = null;
+ DOMString template;
+ ByteString byteStr;
+ ByteString emptyByteStr = "";
+ ByteString otherByteStr = "def";
+ // JSString jsStr; // NOT SUPPORTED YET
+ object someObj;
+ boolean prototype;
+ object? anotherObj = null;
+ TestCallback? someCallback = null;
+ any someAny;
+ any anotherAny = null;
+
+ unrestricted float urFloat = 0;
+ unrestricted float urFloat2 = 1.1;
+ unrestricted float urFloat3 = -1.1;
+ unrestricted float? urFloat4 = null;
+ unrestricted float infUrFloat = Infinity;
+ unrestricted float negativeInfUrFloat = -Infinity;
+ unrestricted float nanUrFloat = NaN;
+
+ unrestricted double urDouble = 0;
+ unrestricted double urDouble2 = 1.1;
+ unrestricted double urDouble3 = -1.1;
+ unrestricted double? urDouble4 = null;
+ unrestricted double infUrDouble = Infinity;
+ unrestricted double negativeInfUrDouble = -Infinity;
+ unrestricted double nanUrDouble = NaN;
+
+ (float or DOMString) floatOrString = "str";
+ (float or DOMString)? nullableFloatOrString = "str";
+ (object or long) objectOrLong;
+#ifdef DEBUG
+ (EventInit or long) eventInitOrLong;
+ (EventInit or long)? nullableEventInitOrLong;
+ (HTMLElement or long)? nullableHTMLElementOrLong;
+ // CustomEventInit is useful to test because it needs rooting.
+ (CustomEventInit or long) eventInitOrLong2;
+ (CustomEventInit or long)? nullableEventInitOrLong2;
+ (EventInit or long) eventInitOrLongWithDefaultValue = {};
+ (CustomEventInit or long) eventInitOrLongWithDefaultValue2 = {};
+ (EventInit or long) eventInitOrLongWithDefaultValue3 = 5;
+ (CustomEventInit or long) eventInitOrLongWithDefaultValue4 = 5;
+ (EventInit or long)? nullableEventInitOrLongWithDefaultValue = null;
+ (CustomEventInit or long)? nullableEventInitOrLongWithDefaultValue2 = null;
+ (EventInit or long)? nullableEventInitOrLongWithDefaultValue3 = 5;
+ (CustomEventInit or long)? nullableEventInitOrLongWithDefaultValue4 = 5;
+ (sequence<object> or long) objectSequenceOrLong;
+ (sequence<object> or long) objectSequenceOrLongWithDefaultValue1 = 1;
+ (sequence<object> or long) objectSequenceOrLongWithDefaultValue2 = [];
+ (sequence<object> or long)? nullableObjectSequenceOrLong;
+ (sequence<object> or long)? nullableObjectSequenceOrLongWithDefaultValue1 = 1;
+ (sequence<object> or long)? nullableObjectSequenceOrLongWithDefaultValue2 = [];
+#endif
+
+ ArrayBuffer arrayBuffer;
+ ArrayBuffer? nullableArrayBuffer;
+ Uint8Array uint8Array;
+ Float64Array? float64Array = null;
+
+ sequence<long> seq1;
+ sequence<long> seq2 = [];
+ sequence<long>? seq3;
+ sequence<long>? seq4 = null;
+ sequence<long>? seq5 = [];
+
+ long dashed-name;
+
+ required long requiredLong;
+ required object requiredObject;
+
+ CustomEventInit customEventInit;
+ TestDictionaryTypedef dictionaryTypedef;
+
+ Promise<undefined> promise;
+ sequence<Promise<undefined>> promiseSequence;
+
+ record<DOMString, long> recordMember;
+ record<DOMString, long>? nullableRecord;
+ record<DOMString, DOMString>? nullableRecordWithDefault = null;
+ record<USVString, long> usvStringRecord;
+ record<USVString, long>? nullableUSVStringRecordWithDefault = null;
+ record<ByteString, long> byteStringRecord;
+ record<ByteString, long>? nullableByteStringRecordWithDefault = null;
+ record<UTF8String, long> utf8StringRecord;
+ record<UTF8String, long>? nullableUTF8StringRecordWithDefault = null;
+ required record<DOMString, TestInterface> requiredRecord;
+ required record<USVString, TestInterface> requiredUSVRecord;
+ required record<ByteString, TestInterface> requiredByteRecord;
+ required record<UTF8String, TestInterface> requiredUTF8Record;
+};
+
+dictionary ParentDict : GrandparentDict {
+ long c = 5;
+ TestInterface someInterface;
+ TestInterface? someNullableInterface = null;
+ TestExternalInterface someExternalInterface;
+ any parentAny;
+};
+
+dictionary DictContainingDict {
+ Dict memberDict;
+};
+
+dictionary DictContainingSequence {
+ sequence<long> ourSequence;
+ sequence<TestInterface> ourSequence2;
+ sequence<any> ourSequence3;
+ sequence<object> ourSequence4;
+ sequence<object?> ourSequence5;
+ sequence<object>? ourSequence6;
+ sequence<object?>? ourSequence7;
+ sequence<object>? ourSequence8 = null;
+ sequence<object?>? ourSequence9 = null;
+ sequence<(float or DOMString)> ourSequence10;
+};
+
+dictionary DictForConstructor {
+ Dict dict;
+ DictContainingDict dict2;
+ sequence<Dict> seq1;
+ sequence<sequence<Dict>>? seq2;
+ sequence<sequence<Dict>?> seq3;
+ sequence<any> seq4;
+ sequence<any> seq5;
+ sequence<DictContainingSequence> seq6;
+ object obj1;
+ object? obj2;
+ any any1 = null;
+};
+
+dictionary DictWithConditionalMembers {
+ [ChromeOnly]
+ long chromeOnlyMember;
+ [Func="TestFuncControlledMember"]
+ long funcControlledMember;
+ [ChromeOnly, Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ long chromeOnlyFuncControlledMember;
+ // We need a pref name that's in StaticPrefList.h here.
+ [Pref="dom.webidl.test1"]
+ long prefControlledMember;
+ [Pref="dom.webidl.test1", ChromeOnly, Func="TestFuncControlledMember"]
+ long chromeOnlyFuncAndPrefControlledMember;
+};
+
+dictionary DictWithAllowSharedMembers {
+ [AllowShared] ArrayBufferView a;
+ [AllowShared] ArrayBufferView? b;
+ [AllowShared] ArrayBuffer c;
+ [AllowShared] ArrayBuffer? d;
+ [AllowShared] ArrayBufferViewTypedef e;
+ AllowSharedArrayBufferViewTypedef f;
+};
+
+[Exposed=Window]
+interface TestIndexedGetterInterface {
+ getter long item(unsigned long idx);
+ readonly attribute unsigned long length;
+ legacycaller undefined();
+ [Cached, Pure] readonly attribute long cachedAttr;
+ [StoreInSlot, Pure] readonly attribute long storeInSlotAttr;
+};
+
+[Exposed=Window]
+interface TestNamedGetterInterface {
+ getter DOMString (DOMString name);
+};
+
+[Exposed=Window]
+interface TestIndexedGetterAndSetterAndNamedGetterInterface {
+ getter DOMString (DOMString myName);
+ getter long (unsigned long index);
+ setter undefined (unsigned long index, long arg);
+ readonly attribute unsigned long length;
+};
+
+[Exposed=Window]
+interface TestIndexedAndNamedGetterInterface {
+ getter long (unsigned long index);
+ getter DOMString namedItem(DOMString name);
+ readonly attribute unsigned long length;
+};
+
+[Exposed=Window]
+interface TestIndexedSetterInterface {
+ setter undefined setItem(unsigned long idx, DOMString item);
+ getter DOMString (unsigned long idx);
+ readonly attribute unsigned long length;
+};
+
+[Exposed=Window]
+interface TestNamedSetterInterface {
+ setter undefined (DOMString myName, TestIndexedSetterInterface item);
+ getter TestIndexedSetterInterface (DOMString name);
+};
+
+[Exposed=Window]
+interface TestIndexedAndNamedSetterInterface {
+ setter undefined (unsigned long index, TestIndexedSetterInterface item);
+ getter TestIndexedSetterInterface (unsigned long index);
+ readonly attribute unsigned long length;
+ setter undefined setNamedItem(DOMString name, TestIndexedSetterInterface item);
+ getter TestIndexedSetterInterface (DOMString name);
+};
+
+[Exposed=Window]
+interface TestIndexedAndNamedGetterAndSetterInterface : TestIndexedSetterInterface {
+ getter long item(unsigned long index);
+ getter DOMString namedItem(DOMString name);
+ setter undefined (unsigned long index, long item);
+ setter undefined (DOMString name, DOMString item);
+ stringifier DOMString ();
+ readonly attribute unsigned long length;
+};
+
+[Exposed=Window]
+interface TestNamedDeleterInterface {
+ deleter undefined (DOMString name);
+ getter long (DOMString name);
+};
+
+[Exposed=Window]
+interface TestNamedDeleterWithRetvalInterface {
+ deleter boolean delNamedItem(DOMString name);
+ getter long (DOMString name);
+};
+
+[Exposed=Window]
+interface TestCppKeywordNamedMethodsInterface {
+ boolean continue();
+ boolean delete();
+ long volatile();
+};
+
+[Deprecated="Components",
+ Exposed=Window]
+interface TestDeprecatedInterface {
+ constructor();
+
+ static undefined alsoDeprecated();
+};
+
+
+[Exposed=Window]
+interface TestInterfaceWithPromiseConstructorArg {
+ constructor(Promise<undefined> promise);
+};
+
+[Exposed=Window]
+namespace TestNamespace {
+ readonly attribute boolean foo;
+ long bar();
+};
+
+partial namespace TestNamespace {
+ undefined baz();
+};
+
+[ClassString="RenamedNamespaceClassName",
+ Exposed=Window]
+namespace TestRenamedNamespace {
+};
+
+[ProtoObjectHack,
+ Exposed=Window]
+namespace TestProtoObjectHackedNamespace {
+};
+
+[SecureContext,
+ Exposed=Window]
+interface TestSecureContextInterface {
+ static undefined alsoSecureContext();
+};
+
+[Exposed=(Window,Worker)]
+interface TestWorkerExposedInterface {
+ [NeedsSubjectPrincipal] undefined needsSubjectPrincipalMethod();
+ [NeedsSubjectPrincipal] attribute boolean needsSubjectPrincipalAttr;
+ [NeedsCallerType] undefined needsCallerTypeMethod();
+ [NeedsCallerType] attribute boolean needsCallerTypeAttr;
+ [NeedsSubjectPrincipal=NonSystem] undefined needsNonSystemSubjectPrincipalMethod();
+ [NeedsSubjectPrincipal=NonSystem] attribute boolean needsNonSystemSubjectPrincipalAttr;
+};
+
+[Exposed=Window]
+interface TestHTMLConstructorInterface {
+ [HTMLConstructor] constructor();
+};
+
+[Exposed=Window]
+interface TestThrowingConstructorInterface {
+ [Throws]
+ constructor();
+ [Throws]
+ constructor(DOMString str);
+ [Throws]
+ constructor(unsigned long num, boolean? boolArg);
+ [Throws]
+ constructor(TestInterface? iface);
+ [Throws]
+ constructor(unsigned long arg1, TestInterface iface);
+ [Throws]
+ constructor(ArrayBuffer arrayBuf);
+ [Throws]
+ constructor(Uint8Array typedArr);
+ // [Throws] constructor(long arg1, long arg2, (TestInterface or OnlyForUseInConstructor) arg3);
+};
+
+[Exposed=Window]
+interface TestCEReactionsInterface {
+ [CEReactions] setter undefined (unsigned long index, long item);
+ [CEReactions] setter undefined (DOMString name, DOMString item);
+ [CEReactions] deleter undefined (DOMString name);
+ getter long item(unsigned long index);
+ getter DOMString (DOMString name);
+ readonly attribute unsigned long length;
+};
+
+typedef [EnforceRange] octet OctetRange;
+typedef [Clamp] octet OctetClamp;
+typedef [LegacyNullToEmptyString] DOMString NullEmptyString;
+// typedef [TreatNullAs=EmptyString] JSString NullEmptyJSString;
+
+dictionary TestAttributesOnDictionaryMembers {
+ [Clamp] octet a;
+ [ChromeOnly, Clamp] octet b;
+ required [Clamp] octet c;
+ [ChromeOnly] octet d;
+ // ChromeOnly doesn't mix with required, so we can't
+ // test [ChromeOnly] required [Clamp] octet e
+};
+
+[Exposed=Window]
+interface TestAttributesOnTypes {
+ undefined foo(OctetClamp thingy);
+ undefined bar(OctetRange thingy);
+ undefined baz(NullEmptyString thingy);
+ // undefined qux(NullEmptyJSString thingy);
+ attribute [Clamp] octet someAttr;
+ undefined argWithAttr([Clamp] octet arg0, optional [Clamp] octet arg1);
+ // There aren't any argument-only attributes that we can test here,
+ // TreatNonCallableAsNull isn't compatible with Clamp-able types
+};
+
+[Exposed=Window]
+interface TestPrefConstructorForInterface {
+ // Since only the constructor is under a pref,
+ // the generated constructor should check for the pref.
+ [Pref="dom.webidl.test1"] constructor();
+};
+
+[Exposed=Window, Pref="dom.webidl.test1"]
+interface TestConstructorForPrefInterface {
+ // Since the interface itself is under a Pref, there should be no
+ // check for the pref in the generated constructor.
+ constructor();
+};
+
+[Exposed=Window, Pref="dom.webidl.test1"]
+interface TestPrefConstructorForDifferentPrefInterface {
+ // Since the constructor's pref is different than the interface pref
+ // there should still be a check for the pref in the generated constructor.
+ [Pref="dom.webidl.test2"] constructor();
+};
+
+[Exposed=Window, SecureContext]
+interface TestConstructorForSCInterface {
+ // Since the interface itself is SecureContext, there should be no
+ // runtime check for SecureContext in the generated constructor.
+ constructor();
+};
+
+[Exposed=Window]
+interface TestSCConstructorForInterface {
+ // Since the interface context is unspecified but the constructor is
+ // SecureContext, the generated constructor should check for SecureContext.
+ [SecureContext] constructor();
+};
+
+[Exposed=Window, Func="Document::IsWebAnimationsEnabled"]
+interface TestConstructorForFuncInterface {
+ // Since the interface has a Func attribute, but the constructor does not,
+ // the generated constructor should not check for the Func.
+ constructor();
+};
+
+[Exposed=Window]
+interface TestFuncConstructorForInterface {
+ // Since the constructor has a Func attribute, but the interface does not,
+ // the generated constructor should check for the Func.
+ [Func="Document::IsWebAnimationsEnabled"]
+ constructor();
+};
+
+[Exposed=Window, Func="Document::AreWebAnimationsTimelinesEnabled"]
+interface TestFuncConstructorForDifferentFuncInterface {
+ // Since the constructor has a different Func attribute from the interface,
+ // the generated constructor should still check for its conditional func.
+ [Func="Document::IsWebAnimationsEnabled"]
+ constructor();
+};
+
+[Exposed=Window]
+interface TestPrefChromeOnlySCFuncConstructorForInterface {
+ [Pref="dom.webidl.test1", ChromeOnly, SecureContext, Func="Document::IsWebAnimationsEnabled"]
+ // There should be checks for all Pref/ChromeOnly/SecureContext/Func
+ // in the generated constructor.
+ constructor();
+};
diff --git a/dom/bindings/test/TestDictionary.webidl b/dom/bindings/test/TestDictionary.webidl
new file mode 100644
index 0000000000..37b7f5e84f
--- /dev/null
+++ b/dom/bindings/test/TestDictionary.webidl
@@ -0,0 +1,9 @@
+/* -*- 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/.
+ */
+
+dictionary GrandparentDict {
+ double someNum;
+};
diff --git a/dom/bindings/test/TestExampleGen.webidl b/dom/bindings/test/TestExampleGen.webidl
new file mode 100644
index 0000000000..1fbad8683e
--- /dev/null
+++ b/dom/bindings/test/TestExampleGen.webidl
@@ -0,0 +1,911 @@
+/* -*- 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/.
+ */
+[LegacyFactoryFunction=Example,
+ LegacyFactoryFunction=Example(DOMString str),
+ LegacyFactoryFunction=Example2(DictForConstructor dict, any any1, object obj1,
+ object? obj2, sequence<Dict> seq, optional any any2,
+ optional object obj3, optional object? obj4),
+ LegacyFactoryFunction=Example2((long or record<DOMString, any>) arg1),
+ Exposed=Window]
+interface TestExampleInterface {
+ constructor();
+ constructor(DOMString str);
+ constructor(unsigned long num, boolean? boolArg);
+ constructor(TestInterface? iface);
+ constructor(unsigned long arg1, TestInterface iface);
+ constructor(ArrayBuffer arrayBuf);
+ constructor(Uint8Array typedArr);
+ // constructor(long arg1, long arg2, (TestInterface or OnlyForUseInConstructor) arg3);
+
+ // Integer types
+ // XXXbz add tests for throwing versions of all the integer stuff
+ readonly attribute byte readonlyByte;
+ attribute byte writableByte;
+ undefined passByte(byte arg);
+ byte receiveByte();
+ undefined passOptionalByte(optional byte arg);
+ undefined passOptionalByteBeforeRequired(optional byte arg1, byte arg2);
+ undefined passOptionalByteWithDefault(optional byte arg = 0);
+ undefined passOptionalByteWithDefaultBeforeRequired(optional byte arg1 = 0, byte arg2);
+ undefined passNullableByte(byte? arg);
+ undefined passOptionalNullableByte(optional byte? arg);
+ undefined passVariadicByte(byte... arg);
+ [Cached, Pure]
+ readonly attribute byte cachedByte;
+ [StoreInSlot, Constant]
+ readonly attribute byte cachedConstantByte;
+ [Cached, Pure]
+ attribute byte cachedWritableByte;
+ [Affects=Nothing]
+ attribute byte sideEffectFreeByte;
+ [Affects=Nothing, DependsOn=DOMState]
+ attribute byte domDependentByte;
+ [Affects=Nothing, DependsOn=Nothing]
+ readonly attribute byte constantByte;
+ [DependsOn=DeviceState, Affects=Nothing]
+ readonly attribute byte deviceStateDependentByte;
+ [Affects=Nothing]
+ byte returnByteSideEffectFree();
+ [Affects=Nothing, DependsOn=DOMState]
+ byte returnDOMDependentByte();
+ [Affects=Nothing, DependsOn=Nothing]
+ byte returnConstantByte();
+ [DependsOn=DeviceState, Affects=Nothing]
+ byte returnDeviceStateDependentByte();
+
+ readonly attribute short readonlyShort;
+ attribute short writableShort;
+ undefined passShort(short arg);
+ short receiveShort();
+ undefined passOptionalShort(optional short arg);
+ undefined passOptionalShortWithDefault(optional short arg = 5);
+
+ readonly attribute long readonlyLong;
+ attribute long writableLong;
+ undefined passLong(long arg);
+ long receiveLong();
+ undefined passOptionalLong(optional long arg);
+ undefined passOptionalLongWithDefault(optional long arg = 7);
+
+ readonly attribute long long readonlyLongLong;
+ attribute long long writableLongLong;
+ undefined passLongLong(long long arg);
+ long long receiveLongLong();
+ undefined passOptionalLongLong(optional long long arg);
+ undefined passOptionalLongLongWithDefault(optional long long arg = -12);
+
+ readonly attribute octet readonlyOctet;
+ attribute octet writableOctet;
+ undefined passOctet(octet arg);
+ octet receiveOctet();
+ undefined passOptionalOctet(optional octet arg);
+ undefined passOptionalOctetWithDefault(optional octet arg = 19);
+
+ readonly attribute unsigned short readonlyUnsignedShort;
+ attribute unsigned short writableUnsignedShort;
+ undefined passUnsignedShort(unsigned short arg);
+ unsigned short receiveUnsignedShort();
+ undefined passOptionalUnsignedShort(optional unsigned short arg);
+ undefined passOptionalUnsignedShortWithDefault(optional unsigned short arg = 2);
+
+ readonly attribute unsigned long readonlyUnsignedLong;
+ attribute unsigned long writableUnsignedLong;
+ undefined passUnsignedLong(unsigned long arg);
+ unsigned long receiveUnsignedLong();
+ undefined passOptionalUnsignedLong(optional unsigned long arg);
+ undefined passOptionalUnsignedLongWithDefault(optional unsigned long arg = 6);
+
+ readonly attribute unsigned long long readonlyUnsignedLongLong;
+ attribute unsigned long long writableUnsignedLongLong;
+ undefined passUnsignedLongLong(unsigned long long arg);
+ unsigned long long receiveUnsignedLongLong();
+ undefined passOptionalUnsignedLongLong(optional unsigned long long arg);
+ undefined passOptionalUnsignedLongLongWithDefault(optional unsigned long long arg = 17);
+
+ attribute float writableFloat;
+ attribute unrestricted float writableUnrestrictedFloat;
+ attribute float? writableNullableFloat;
+ attribute unrestricted float? writableNullableUnrestrictedFloat;
+ attribute double writableDouble;
+ attribute unrestricted double writableUnrestrictedDouble;
+ attribute double? writableNullableDouble;
+ attribute unrestricted double? writableNullableUnrestrictedDouble;
+ undefined passFloat(float arg1, unrestricted float arg2,
+ float? arg3, unrestricted float? arg4,
+ double arg5, unrestricted double arg6,
+ double? arg7, unrestricted double? arg8,
+ sequence<float> arg9, sequence<unrestricted float> arg10,
+ sequence<float?> arg11, sequence<unrestricted float?> arg12,
+ sequence<double> arg13, sequence<unrestricted double> arg14,
+ sequence<double?> arg15, sequence<unrestricted double?> arg16);
+ [LenientFloat]
+ undefined passLenientFloat(float arg1, unrestricted float arg2,
+ float? arg3, unrestricted float? arg4,
+ double arg5, unrestricted double arg6,
+ double? arg7, unrestricted double? arg8,
+ sequence<float> arg9,
+ sequence<unrestricted float> arg10,
+ sequence<float?> arg11,
+ sequence<unrestricted float?> arg12,
+ sequence<double> arg13,
+ sequence<unrestricted double> arg14,
+ sequence<double?> arg15,
+ sequence<unrestricted double?> arg16);
+ [LenientFloat]
+ attribute float lenientFloatAttr;
+ [LenientFloat]
+ attribute double lenientDoubleAttr;
+
+ // Castable interface types
+ // XXXbz add tests for throwing versions of all the castable interface stuff
+ TestInterface receiveSelf();
+ TestInterface? receiveNullableSelf();
+ TestInterface receiveWeakSelf();
+ TestInterface? receiveWeakNullableSelf();
+ undefined passSelf(TestInterface arg);
+ undefined passNullableSelf(TestInterface? arg);
+ attribute TestInterface nonNullSelf;
+ attribute TestInterface? nullableSelf;
+ [Cached, Pure]
+ readonly attribute TestInterface cachedSelf;
+ // Optional arguments
+ undefined passOptionalSelf(optional TestInterface? arg);
+ undefined passOptionalNonNullSelf(optional TestInterface arg);
+ undefined passOptionalSelfWithDefault(optional TestInterface? arg = null);
+
+ // Non-wrapper-cache interface types
+ [NewObject]
+ TestNonWrapperCacheInterface receiveNonWrapperCacheInterface();
+ [NewObject]
+ TestNonWrapperCacheInterface? receiveNullableNonWrapperCacheInterface();
+ [NewObject]
+ sequence<TestNonWrapperCacheInterface> receiveNonWrapperCacheInterfaceSequence();
+ [NewObject]
+ sequence<TestNonWrapperCacheInterface?> receiveNullableNonWrapperCacheInterfaceSequence();
+ [NewObject]
+ sequence<TestNonWrapperCacheInterface>? receiveNonWrapperCacheInterfaceNullableSequence();
+ [NewObject]
+ sequence<TestNonWrapperCacheInterface?>? receiveNullableNonWrapperCacheInterfaceNullableSequence();
+
+ // External interface types
+ TestExternalInterface receiveExternal();
+ TestExternalInterface? receiveNullableExternal();
+ TestExternalInterface receiveWeakExternal();
+ TestExternalInterface? receiveWeakNullableExternal();
+ undefined passExternal(TestExternalInterface arg);
+ undefined passNullableExternal(TestExternalInterface? arg);
+ attribute TestExternalInterface nonNullExternal;
+ attribute TestExternalInterface? nullableExternal;
+ // Optional arguments
+ undefined passOptionalExternal(optional TestExternalInterface? arg);
+ undefined passOptionalNonNullExternal(optional TestExternalInterface arg);
+ undefined passOptionalExternalWithDefault(optional TestExternalInterface? arg = null);
+
+ // Callback interface types
+ TestCallbackInterface receiveCallbackInterface();
+ TestCallbackInterface? receiveNullableCallbackInterface();
+ TestCallbackInterface receiveWeakCallbackInterface();
+ TestCallbackInterface? receiveWeakNullableCallbackInterface();
+ undefined passCallbackInterface(TestCallbackInterface arg);
+ undefined passNullableCallbackInterface(TestCallbackInterface? arg);
+ attribute TestCallbackInterface nonNullCallbackInterface;
+ attribute TestCallbackInterface? nullableCallbackInterface;
+ // Optional arguments
+ undefined passOptionalCallbackInterface(optional TestCallbackInterface? arg);
+ undefined passOptionalNonNullCallbackInterface(optional TestCallbackInterface arg);
+ undefined passOptionalCallbackInterfaceWithDefault(optional TestCallbackInterface? arg = null);
+
+ // Sequence types
+ [Cached, Pure]
+ readonly attribute sequence<long> readonlySequence;
+ [Cached, Pure]
+ readonly attribute sequence<Dict> readonlySequenceOfDictionaries;
+ [Cached, Pure]
+ readonly attribute sequence<Dict>? readonlyNullableSequenceOfDictionaries;
+ [Cached, Pure, Frozen]
+ readonly attribute sequence<long> readonlyFrozenSequence;
+ [Cached, Pure, Frozen]
+ readonly attribute sequence<long>? readonlyFrozenNullableSequence;
+ sequence<long> receiveSequence();
+ sequence<long>? receiveNullableSequence();
+ sequence<long?> receiveSequenceOfNullableInts();
+ sequence<long?>? receiveNullableSequenceOfNullableInts();
+ undefined passSequence(sequence<long> arg);
+ undefined passNullableSequence(sequence<long>? arg);
+ undefined passSequenceOfNullableInts(sequence<long?> arg);
+ undefined passOptionalSequenceOfNullableInts(optional sequence<long?> arg);
+ undefined passOptionalNullableSequenceOfNullableInts(optional sequence<long?>? arg);
+ sequence<TestInterface> receiveCastableObjectSequence();
+ sequence<TestCallbackInterface> receiveCallbackObjectSequence();
+ sequence<TestInterface?> receiveNullableCastableObjectSequence();
+ sequence<TestCallbackInterface?> receiveNullableCallbackObjectSequence();
+ sequence<TestInterface>? receiveCastableObjectNullableSequence();
+ sequence<TestInterface?>? receiveNullableCastableObjectNullableSequence();
+ sequence<TestInterface> receiveWeakCastableObjectSequence();
+ sequence<TestInterface?> receiveWeakNullableCastableObjectSequence();
+ sequence<TestInterface>? receiveWeakCastableObjectNullableSequence();
+ sequence<TestInterface?>? receiveWeakNullableCastableObjectNullableSequence();
+ undefined passCastableObjectSequence(sequence<TestInterface> arg);
+ undefined passNullableCastableObjectSequence(sequence<TestInterface?> arg);
+ undefined passCastableObjectNullableSequence(sequence<TestInterface>? arg);
+ undefined passNullableCastableObjectNullableSequence(sequence<TestInterface?>? arg);
+ undefined passOptionalSequence(optional sequence<long> arg);
+ undefined passOptionalSequenceWithDefaultValue(optional sequence<long> arg = []);
+ undefined passOptionalNullableSequence(optional sequence<long>? arg);
+ undefined passOptionalNullableSequenceWithDefaultValue(optional sequence<long>? arg = null);
+ undefined passOptionalNullableSequenceWithDefaultValue2(optional sequence<long>? arg = []);
+ undefined passOptionalObjectSequence(optional sequence<TestInterface> arg);
+ undefined passExternalInterfaceSequence(sequence<TestExternalInterface> arg);
+ undefined passNullableExternalInterfaceSequence(sequence<TestExternalInterface?> arg);
+
+ sequence<DOMString> receiveStringSequence();
+ undefined passStringSequence(sequence<DOMString> arg);
+
+ sequence<ByteString> receiveByteStringSequence();
+ undefined passByteStringSequence(sequence<ByteString> arg);
+
+ sequence<UTF8String> receiveUTF8StringSequence();
+ undefined passUTF8StringSequence(sequence<UTF8String> arg);
+
+ sequence<any> receiveAnySequence();
+ sequence<any>? receiveNullableAnySequence();
+ //XXXbz No support for sequence of sequence return values yet.
+ //sequence<sequence<any>> receiveAnySequenceSequence();
+
+ sequence<object> receiveObjectSequence();
+ sequence<object?> receiveNullableObjectSequence();
+
+ undefined passSequenceOfSequences(sequence<sequence<long>> arg);
+ undefined passSequenceOfSequencesOfSequences(sequence<sequence<sequence<long>>> arg);
+ //XXXbz No support for sequence of sequence return values yet.
+ //sequence<sequence<long>> receiveSequenceOfSequences();
+
+ // record types
+ undefined passRecord(record<DOMString, long> arg);
+ undefined passNullableRecord(record<DOMString, long>? arg);
+ undefined passRecordOfNullableInts(record<DOMString, long?> arg);
+ undefined passOptionalRecordOfNullableInts(optional record<DOMString, long?> arg);
+ undefined passOptionalNullableRecordOfNullableInts(optional record<DOMString, long?>? arg);
+ undefined passCastableObjectRecord(record<DOMString, TestInterface> arg);
+ undefined passNullableCastableObjectRecord(record<DOMString, TestInterface?> arg);
+ undefined passCastableObjectNullableRecord(record<DOMString, TestInterface>? arg);
+ undefined passNullableCastableObjectNullableRecord(record<DOMString, TestInterface?>? arg);
+ undefined passOptionalRecord(optional record<DOMString, long> arg);
+ undefined passOptionalNullableRecord(optional record<DOMString, long>? arg);
+ undefined passOptionalNullableRecordWithDefaultValue(optional record<DOMString, long>? arg = null);
+ undefined passOptionalObjectRecord(optional record<DOMString, TestInterface> arg);
+ undefined passExternalInterfaceRecord(record<DOMString, TestExternalInterface> arg);
+ undefined passNullableExternalInterfaceRecord(record<DOMString, TestExternalInterface?> arg);
+ undefined passStringRecord(record<DOMString, DOMString> arg);
+ undefined passByteStringRecord(record<DOMString, ByteString> arg);
+ undefined passUTF8StringRecord(record<DOMString, UTF8String> arg);
+ undefined passRecordOfRecords(record<DOMString, record<DOMString, long>> arg);
+ record<DOMString, long> receiveRecord();
+ record<DOMString, long>? receiveNullableRecord();
+ record<DOMString, long?> receiveRecordOfNullableInts();
+ record<DOMString, long?>? receiveNullableRecordOfNullableInts();
+ //XXXbz No support for record of records return values yet.
+ //record<DOMString, record<DOMString, long>> receiveRecordOfRecords();
+ record<DOMString, any> receiveAnyRecord();
+
+ // Typed array types
+ undefined passArrayBuffer(ArrayBuffer arg);
+ undefined passNullableArrayBuffer(ArrayBuffer? arg);
+ undefined passOptionalArrayBuffer(optional ArrayBuffer arg);
+ undefined passOptionalNullableArrayBuffer(optional ArrayBuffer? arg);
+ undefined passOptionalNullableArrayBufferWithDefaultValue(optional ArrayBuffer? arg= null);
+ undefined passArrayBufferView(ArrayBufferView arg);
+ undefined passInt8Array(Int8Array arg);
+ undefined passInt16Array(Int16Array arg);
+ undefined passInt32Array(Int32Array arg);
+ undefined passUint8Array(Uint8Array arg);
+ undefined passUint16Array(Uint16Array arg);
+ undefined passUint32Array(Uint32Array arg);
+ undefined passUint8ClampedArray(Uint8ClampedArray arg);
+ undefined passFloat32Array(Float32Array arg);
+ undefined passFloat64Array(Float64Array arg);
+ undefined passSequenceOfArrayBuffers(sequence<ArrayBuffer> arg);
+ undefined passSequenceOfNullableArrayBuffers(sequence<ArrayBuffer?> arg);
+ undefined passRecordOfArrayBuffers(record<DOMString, ArrayBuffer> arg);
+ undefined passRecordOfNullableArrayBuffers(record<DOMString, ArrayBuffer?> arg);
+ undefined passVariadicTypedArray(Float32Array... arg);
+ undefined passVariadicNullableTypedArray(Float32Array?... arg);
+ Uint8Array receiveUint8Array();
+ attribute Uint8Array uint8ArrayAttr;
+
+ // DOMString types
+ undefined passString(DOMString arg);
+ undefined passNullableString(DOMString? arg);
+ undefined passOptionalString(optional DOMString arg);
+ undefined passOptionalStringWithDefaultValue(optional DOMString arg = "abc");
+ undefined passOptionalNullableString(optional DOMString? arg);
+ undefined passOptionalNullableStringWithDefaultValue(optional DOMString? arg = null);
+ undefined passVariadicString(DOMString... arg);
+
+ // ByteString types
+ undefined passByteString(ByteString arg);
+ undefined passNullableByteString(ByteString? arg);
+ undefined passOptionalByteString(optional ByteString arg);
+ undefined passOptionalByteStringWithDefaultValue(optional ByteString arg = "abc");
+ undefined passOptionalNullableByteString(optional ByteString? arg);
+ undefined passOptionalNullableByteStringWithDefaultValue(optional ByteString? arg = null);
+ undefined passVariadicByteString(ByteString... arg);
+ undefined passUnionByteString((ByteString or long) arg);
+ undefined passOptionalUnionByteString(optional (ByteString or long) arg);
+ undefined passOptionalUnionByteStringWithDefaultValue(optional (ByteString or long) arg = "abc");
+
+ // UTF8String types
+ undefined passUTF8String(UTF8String arg);
+ undefined passNullableUTF8String(UTF8String? arg);
+ undefined passOptionalUTF8String(optional UTF8String arg);
+ undefined passOptionalUTF8StringWithDefaultValue(optional UTF8String arg = "abc");
+ undefined passOptionalNullableUTF8String(optional UTF8String? arg);
+ undefined passOptionalNullableUTF8StringWithDefaultValue(optional UTF8String? arg = null);
+ undefined passVariadicUTF8String(UTF8String... arg);
+ undefined passUnionUTF8String((UTF8String or long) arg);
+ undefined passOptionalUnionUTF8String(optional (UTF8String or long) arg);
+ undefined passOptionalUnionUTF8StringWithDefaultValue(optional (UTF8String or long) arg = "abc");
+
+ // USVString types
+ undefined passSVS(USVString arg);
+ undefined passNullableSVS(USVString? arg);
+ undefined passOptionalSVS(optional USVString arg);
+ undefined passOptionalSVSWithDefaultValue(optional USVString arg = "abc");
+ undefined passOptionalNullableSVS(optional USVString? arg);
+ undefined passOptionalNullableSVSWithDefaultValue(optional USVString? arg = null);
+ undefined passVariadicSVS(USVString... arg);
+ USVString receiveSVS();
+
+ // JSString types
+ undefined passJSString(JSString arg);
+ // undefined passNullableJSString(JSString? arg); // NOT SUPPORTED YET
+ // undefined passOptionalJSString(optional JSString arg); // NOT SUPPORTED YET
+ undefined passOptionalJSStringWithDefaultValue(optional JSString arg = "abc");
+ // undefined passOptionalNullableJSString(optional JSString? arg); // NOT SUPPORTED YET
+ // undefined passOptionalNullableJSStringWithDefaultValue(optional JSString? arg = null); // NOT SUPPORTED YET
+ // undefined passVariadicJSString(JSString... arg); // NOT SUPPORTED YET
+ // undefined passRecordOfJSString(record<DOMString, JSString> arg); // NOT SUPPORTED YET
+ // undefined passSequenceOfJSString(sequence<JSString> arg); // NOT SUPPORTED YET
+ // undefined passUnionJSString((JSString or long) arg); // NOT SUPPORTED YET
+ JSString receiveJSString();
+ // sequence<JSString> receiveJSStringSequence(); // NOT SUPPORTED YET
+ // (JSString or long) receiveJSStringUnion(); // NOT SUPPORTED YET
+ // record<DOMString, JSString> receiveJSStringRecord(); // NOT SUPPORTED YET
+ readonly attribute JSString readonlyJSStringAttr;
+ attribute JSString jsStringAttr;
+
+ // Enumerated types
+ undefined passEnum(TestEnum arg);
+ undefined passNullableEnum(TestEnum? arg);
+ undefined passOptionalEnum(optional TestEnum arg);
+ undefined passEnumWithDefault(optional TestEnum arg = "a");
+ undefined passOptionalNullableEnum(optional TestEnum? arg);
+ undefined passOptionalNullableEnumWithDefaultValue(optional TestEnum? arg = null);
+ undefined passOptionalNullableEnumWithDefaultValue2(optional TestEnum? arg = "a");
+ TestEnum receiveEnum();
+ TestEnum? receiveNullableEnum();
+ attribute TestEnum enumAttribute;
+ readonly attribute TestEnum readonlyEnumAttribute;
+
+ // Callback types
+ undefined passCallback(TestCallback arg);
+ undefined passNullableCallback(TestCallback? arg);
+ undefined passOptionalCallback(optional TestCallback arg);
+ undefined passOptionalNullableCallback(optional TestCallback? arg);
+ undefined passOptionalNullableCallbackWithDefaultValue(optional TestCallback? arg = null);
+ TestCallback receiveCallback();
+ TestCallback? receiveNullableCallback();
+ undefined passNullableTreatAsNullCallback(TestTreatAsNullCallback? arg);
+ undefined passOptionalNullableTreatAsNullCallback(optional TestTreatAsNullCallback? arg);
+ undefined passOptionalNullableTreatAsNullCallbackWithDefaultValue(optional TestTreatAsNullCallback? arg = null);
+
+ // Any types
+ undefined passAny(any arg);
+ undefined passVariadicAny(any... arg);
+ undefined passOptionalAny(optional any arg);
+ undefined passAnyDefaultNull(optional any arg = null);
+ undefined passSequenceOfAny(sequence<any> arg);
+ undefined passNullableSequenceOfAny(sequence<any>? arg);
+ undefined passOptionalSequenceOfAny(optional sequence<any> arg);
+ undefined passOptionalNullableSequenceOfAny(optional sequence<any>? arg);
+ undefined passOptionalSequenceOfAnyWithDefaultValue(optional sequence<any>? arg = null);
+ undefined passSequenceOfSequenceOfAny(sequence<sequence<any>> arg);
+ undefined passSequenceOfNullableSequenceOfAny(sequence<sequence<any>?> arg);
+ undefined passNullableSequenceOfNullableSequenceOfAny(sequence<sequence<any>?>? arg);
+ undefined passOptionalNullableSequenceOfNullableSequenceOfAny(optional sequence<sequence<any>?>? arg);
+ undefined passRecordOfAny(record<DOMString, any> arg);
+ undefined passNullableRecordOfAny(record<DOMString, any>? arg);
+ undefined passOptionalRecordOfAny(optional record<DOMString, any> arg);
+ undefined passOptionalNullableRecordOfAny(optional record<DOMString, any>? arg);
+ undefined passOptionalRecordOfAnyWithDefaultValue(optional record<DOMString, any>? arg = null);
+ undefined passRecordOfRecordOfAny(record<DOMString, record<DOMString, any>> arg);
+ undefined passRecordOfNullableRecordOfAny(record<DOMString, record<DOMString, any>?> arg);
+ undefined passNullableRecordOfNullableRecordOfAny(record<DOMString, record<DOMString, any>?>? arg);
+ undefined passOptionalNullableRecordOfNullableRecordOfAny(optional record<DOMString, record<DOMString, any>?>? arg);
+ undefined passOptionalNullableRecordOfNullableSequenceOfAny(optional record<DOMString, sequence<any>?>? arg);
+ undefined passOptionalNullableSequenceOfNullableRecordOfAny(optional sequence<record<DOMString, any>?>? arg);
+ any receiveAny();
+
+ // object types
+ undefined passObject(object arg);
+ undefined passVariadicObject(object... arg);
+ undefined passNullableObject(object? arg);
+ undefined passVariadicNullableObject(object... arg);
+ undefined passOptionalObject(optional object arg);
+ undefined passOptionalNullableObject(optional object? arg);
+ undefined passOptionalNullableObjectWithDefaultValue(optional object? arg = null);
+ undefined passSequenceOfObject(sequence<object> arg);
+ undefined passSequenceOfNullableObject(sequence<object?> arg);
+ undefined passNullableSequenceOfObject(sequence<object>? arg);
+ undefined passOptionalNullableSequenceOfNullableSequenceOfObject(optional sequence<sequence<object>?>? arg);
+ undefined passOptionalNullableSequenceOfNullableSequenceOfNullableObject(optional sequence<sequence<object?>?>? arg);
+ undefined passRecordOfObject(record<DOMString, object> arg);
+ object receiveObject();
+ object? receiveNullableObject();
+
+ // Union types
+ undefined passUnion((object or long) arg);
+ // Some union tests are debug-only to avoid creating all those
+ // unused union types in opt builds.
+
+#ifdef DEBUG
+ undefined passUnion2((long or boolean) arg);
+ undefined passUnion3((object or long or boolean) arg);
+ undefined passUnion4((Node or long or boolean) arg);
+ undefined passUnion5((object or boolean) arg);
+ undefined passUnion6((object or DOMString) arg);
+ undefined passUnion7((object or DOMString or long) arg);
+ undefined passUnion8((object or DOMString or boolean) arg);
+ undefined passUnion9((object or DOMString or long or boolean) arg);
+ undefined passUnion10(optional (EventInit or long) arg = {});
+ undefined passUnion11(optional (CustomEventInit or long) arg = {});
+ undefined passUnion12(optional (EventInit or long) arg = 5);
+ undefined passUnion13(optional (object or long?) arg = null);
+ undefined passUnion14(optional (object or long?) arg = 5);
+ undefined passUnion15((sequence<long> or long) arg);
+ undefined passUnion16(optional (sequence<long> or long) arg);
+ undefined passUnion17(optional (sequence<long>? or long) arg = 5);
+ undefined passUnion18((sequence<object> or long) arg);
+ undefined passUnion19(optional (sequence<object> or long) arg);
+ undefined passUnion20(optional (sequence<object> or long) arg = []);
+ undefined passUnion21((record<DOMString, long> or long) arg);
+ undefined passUnion22((record<DOMString, object> or long) arg);
+ undefined passUnion23((sequence<ImageData> or long) arg);
+ undefined passUnion24((sequence<ImageData?> or long) arg);
+ undefined passUnion25((sequence<sequence<ImageData>> or long) arg);
+ undefined passUnion26((sequence<sequence<ImageData?>> or long) arg);
+ undefined passUnion27(optional (sequence<DOMString> or EventInit) arg = {});
+ undefined passUnion28(optional (EventInit or sequence<DOMString>) arg = {});
+ undefined passUnionWithCallback((EventHandler or long) arg);
+ undefined passUnionWithByteString((ByteString or long) arg);
+ undefined passUnionWithUTF8String((UTF8String or long) arg);
+ undefined passUnionWithRecord((record<DOMString, DOMString> or DOMString) arg);
+ undefined passUnionWithRecordAndSequence((record<DOMString, DOMString> or sequence<DOMString>) arg);
+ undefined passUnionWithSequenceAndRecord((sequence<DOMString> or record<DOMString, DOMString>) arg);
+ undefined passUnionWithSVS((USVString or long) arg);
+#endif
+ undefined passUnionWithNullable((object? or long) arg);
+ undefined passNullableUnion((object or long)? arg);
+ undefined passOptionalUnion(optional (object or long) arg);
+ undefined passOptionalNullableUnion(optional (object or long)? arg);
+ undefined passOptionalNullableUnionWithDefaultValue(optional (object or long)? arg = null);
+ //undefined passUnionWithInterfaces((TestInterface or TestExternalInterface) arg);
+ //undefined passUnionWithInterfacesAndNullable((TestInterface? or TestExternalInterface) arg);
+ //undefined passUnionWithSequence((sequence<object> or long) arg);
+ undefined passUnionWithArrayBuffer((ArrayBuffer or long) arg);
+ undefined passUnionWithString((DOMString or object) arg);
+ // Using an enum in a union. Note that we use some enum not declared in our
+ // binding file, because UnionTypes.h will need to include the binding header
+ // for this enum. Pick an enum from an interface that won't drag in too much
+ // stuff.
+ undefined passUnionWithEnum((SupportedType or object) arg);
+
+ // Trying to use a callback in a union won't include the test
+ // headers, unfortunately, so won't compile.
+ // undefined passUnionWithCallback((TestCallback or long) arg);
+ undefined passUnionWithObject((object or long) arg);
+ //undefined passUnionWithDict((Dict or long) arg);
+
+ undefined passUnionWithDefaultValue1(optional (double or DOMString) arg = "");
+ undefined passUnionWithDefaultValue2(optional (double or DOMString) arg = 1);
+ undefined passUnionWithDefaultValue3(optional (double or DOMString) arg = 1.5);
+ undefined passUnionWithDefaultValue4(optional (float or DOMString) arg = "");
+ undefined passUnionWithDefaultValue5(optional (float or DOMString) arg = 1);
+ undefined passUnionWithDefaultValue6(optional (float or DOMString) arg = 1.5);
+ undefined passUnionWithDefaultValue7(optional (unrestricted double or DOMString) arg = "");
+ undefined passUnionWithDefaultValue8(optional (unrestricted double or DOMString) arg = 1);
+ undefined passUnionWithDefaultValue9(optional (unrestricted double or DOMString) arg = 1.5);
+ undefined passUnionWithDefaultValue10(optional (unrestricted double or DOMString) arg = Infinity);
+ undefined passUnionWithDefaultValue11(optional (unrestricted float or DOMString) arg = "");
+ undefined passUnionWithDefaultValue12(optional (unrestricted float or DOMString) arg = 1);
+ undefined passUnionWithDefaultValue13(optional (unrestricted float or DOMString) arg = Infinity);
+ undefined passUnionWithDefaultValue14(optional (double or ByteString) arg = "");
+ undefined passUnionWithDefaultValue15(optional (double or ByteString) arg = 1);
+ undefined passUnionWithDefaultValue16(optional (double or ByteString) arg = 1.5);
+ undefined passUnionWithDefaultValue17(optional (double or SupportedType) arg = "text/html");
+ undefined passUnionWithDefaultValue18(optional (double or SupportedType) arg = 1);
+ undefined passUnionWithDefaultValue19(optional (double or SupportedType) arg = 1.5);
+ undefined passUnionWithDefaultValue20(optional (double or USVString) arg = "abc");
+ undefined passUnionWithDefaultValue21(optional (double or USVString) arg = 1);
+ undefined passUnionWithDefaultValue22(optional (double or USVString) arg = 1.5);
+ undefined passUnionWithDefaultValue23(optional (double or UTF8String) arg = "");
+ undefined passUnionWithDefaultValue24(optional (double or UTF8String) arg = 1);
+ undefined passUnionWithDefaultValue25(optional (double or UTF8String) arg = 1.5);
+
+ undefined passNullableUnionWithDefaultValue1(optional (double or DOMString)? arg = "");
+ undefined passNullableUnionWithDefaultValue2(optional (double or DOMString)? arg = 1);
+ undefined passNullableUnionWithDefaultValue3(optional (double or DOMString)? arg = null);
+ undefined passNullableUnionWithDefaultValue4(optional (float or DOMString)? arg = "");
+ undefined passNullableUnionWithDefaultValue5(optional (float or DOMString)? arg = 1);
+ undefined passNullableUnionWithDefaultValue6(optional (float or DOMString)? arg = null);
+ undefined passNullableUnionWithDefaultValue7(optional (unrestricted double or DOMString)? arg = "");
+ undefined passNullableUnionWithDefaultValue8(optional (unrestricted double or DOMString)? arg = 1);
+ undefined passNullableUnionWithDefaultValue9(optional (unrestricted double or DOMString)? arg = null);
+ undefined passNullableUnionWithDefaultValue10(optional (unrestricted float or DOMString)? arg = "");
+ undefined passNullableUnionWithDefaultValue11(optional (unrestricted float or DOMString)? arg = 1);
+ undefined passNullableUnionWithDefaultValue12(optional (unrestricted float or DOMString)? arg = null);
+ undefined passNullableUnionWithDefaultValue13(optional (double or ByteString)? arg = "");
+ undefined passNullableUnionWithDefaultValue14(optional (double or ByteString)? arg = 1);
+ undefined passNullableUnionWithDefaultValue15(optional (double or ByteString)? arg = 1.5);
+ undefined passNullableUnionWithDefaultValue16(optional (double or ByteString)? arg = null);
+ undefined passNullableUnionWithDefaultValue17(optional (double or SupportedType)? arg = "text/html");
+ undefined passNullableUnionWithDefaultValue18(optional (double or SupportedType)? arg = 1);
+ undefined passNullableUnionWithDefaultValue19(optional (double or SupportedType)? arg = 1.5);
+ undefined passNullableUnionWithDefaultValue20(optional (double or SupportedType)? arg = null);
+ undefined passNullableUnionWithDefaultValue21(optional (double or USVString)? arg = "abc");
+ undefined passNullableUnionWithDefaultValue22(optional (double or USVString)? arg = 1);
+ undefined passNullableUnionWithDefaultValue23(optional (double or USVString)? arg = 1.5);
+ undefined passNullableUnionWithDefaultValue24(optional (double or USVString)? arg = null);
+ undefined passNullableUnionWithDefaultValue25(optional (double or UTF8String)? arg = "");
+ undefined passNullableUnionWithDefaultValue26(optional (double or UTF8String)? arg = 1);
+ undefined passNullableUnionWithDefaultValue27(optional (double or UTF8String)? arg = 1.5);
+ undefined passNullableUnionWithDefaultValue28(optional (double or UTF8String)? arg = null);
+
+ undefined passSequenceOfUnions(sequence<(CanvasPattern or CanvasGradient)> arg);
+ undefined passSequenceOfUnions2(sequence<(object or long)> arg);
+ undefined passVariadicUnion((CanvasPattern or CanvasGradient)... arg);
+
+ undefined passSequenceOfNullableUnions(sequence<(CanvasPattern or CanvasGradient)?> arg);
+ undefined passVariadicNullableUnion((CanvasPattern or CanvasGradient)?... arg);
+ undefined passRecordOfUnions(record<DOMString, (CanvasPattern or CanvasGradient)> arg);
+ // XXXbz no move constructor on some unions
+ // undefined passRecordOfUnions2(record<DOMString, (object or long)> arg);
+
+ (CanvasPattern or CanvasGradient) receiveUnion();
+ (object or long) receiveUnion2();
+ (CanvasPattern? or CanvasGradient) receiveUnionContainingNull();
+ (CanvasPattern or CanvasGradient)? receiveNullableUnion();
+ (object or long)? receiveNullableUnion2();
+ (undefined or CanvasPattern) receiveUnionWithUndefined();
+ (undefined? or CanvasPattern) receiveUnionWithNullableUndefined();
+ (undefined or CanvasPattern?) receiveUnionWithUndefinedAndNullable();
+ (undefined or CanvasPattern)? receiveNullableUnionWithUndefined();
+
+ attribute (CanvasPattern or CanvasGradient) writableUnion;
+ attribute (CanvasPattern? or CanvasGradient) writableUnionContainingNull;
+ attribute (CanvasPattern or CanvasGradient)? writableNullableUnion;
+ attribute (undefined or CanvasPattern) writableUnionWithUndefined;
+ attribute (undefined? or CanvasPattern) writableUnionWithNullableUndefined;
+ attribute (undefined or CanvasPattern?) writableUnionWithUndefinedAndNullable;
+ attribute (undefined or CanvasPattern)? writableNullableUnionWithUndefined;
+
+ // Promise types
+ undefined passPromise(Promise<any> arg);
+ undefined passOptionalPromise(optional Promise<any> arg);
+ undefined passPromiseSequence(sequence<Promise<any>> arg);
+ Promise<any> receivePromise();
+ Promise<any> receiveAddrefedPromise();
+
+ // ObservableArray types
+ attribute ObservableArray<boolean> booleanObservableArray;
+ attribute ObservableArray<object> objectObservableArray;
+ attribute ObservableArray<any> anyObservableArray;
+ attribute ObservableArray<TestInterface> interfaceObservableArray;
+ attribute ObservableArray<long?> nullableObservableArray;
+
+ // binaryNames tests
+ [BinaryName="methodRenamedTo"]
+ undefined methodRenamedFrom();
+ [BinaryName="methodRenamedTo"]
+ undefined methodRenamedFrom(byte argument);
+ [BinaryName="attributeGetterRenamedTo"]
+ readonly attribute byte attributeGetterRenamedFrom;
+ [BinaryName="attributeRenamedTo"]
+ attribute byte attributeRenamedFrom;
+
+ undefined passDictionary(optional Dict x = {});
+ undefined passDictionary2(Dict x);
+ [Cached, Pure]
+ readonly attribute Dict readonlyDictionary;
+ [Cached, Pure]
+ readonly attribute Dict? readonlyNullableDictionary;
+ [Cached, Pure]
+ attribute Dict writableDictionary;
+ [Cached, Pure, Frozen]
+ readonly attribute Dict readonlyFrozenDictionary;
+ [Cached, Pure, Frozen]
+ readonly attribute Dict? readonlyFrozenNullableDictionary;
+ [Cached, Pure, Frozen]
+ attribute Dict writableFrozenDictionary;
+ Dict receiveDictionary();
+ Dict? receiveNullableDictionary();
+ undefined passOtherDictionary(optional GrandparentDict x = {});
+ undefined passSequenceOfDictionaries(sequence<Dict> x);
+ undefined passRecordOfDictionaries(record<DOMString, GrandparentDict> x);
+ // No support for nullable dictionaries inside a sequence (nor should there be)
+ // undefined passSequenceOfNullableDictionaries(sequence<Dict?> x);
+ undefined passDictionaryOrLong(optional Dict x = {});
+ undefined passDictionaryOrLong(long x);
+
+ undefined passDictContainingDict(optional DictContainingDict arg = {});
+ undefined passDictContainingSequence(optional DictContainingSequence arg = {});
+ DictContainingSequence receiveDictContainingSequence();
+ undefined passVariadicDictionary(Dict... arg);
+
+ // EnforceRange/Clamp tests
+ undefined dontEnforceRangeOrClamp(byte arg);
+ undefined doEnforceRange([EnforceRange] byte arg);
+ undefined doEnforceRangeNullable([EnforceRange] byte? arg);
+ undefined doClamp([Clamp] byte arg);
+ undefined doClampNullable([Clamp] byte? arg);
+ attribute [EnforceRange] byte enforcedByte;
+ attribute [EnforceRange] byte? enforcedByteNullable;
+ attribute [Clamp] byte clampedByte;
+ attribute [Clamp] byte? clampedByteNullable;
+
+ // Typedefs
+ const myLong myLongConstant = 5;
+ undefined exerciseTypedefInterfaces1(AnotherNameForTestInterface arg);
+ AnotherNameForTestInterface exerciseTypedefInterfaces2(NullableTestInterface arg);
+ undefined exerciseTypedefInterfaces3(YetAnotherNameForTestInterface arg);
+
+ // Deprecated methods and attributes
+ [Deprecated="Components"]
+ attribute boolean deprecatedAttribute;
+ [Deprecated="Components"]
+ undefined deprecatedMethod(boolean arg);
+ [Deprecated="Components"]
+ undefined deprecatedMethodWithContext(any arg);
+
+ // Static methods and attributes
+ static attribute boolean staticAttribute;
+ static undefined staticMethod(boolean arg);
+ static undefined staticMethodWithContext(any arg);
+
+ // Deprecated methods and attributes;
+ [Deprecated="Components"]
+ static attribute boolean staticDeprecatedAttribute;
+ [Deprecated="Components"]
+ static undefined staticDeprecatedMethod(boolean arg);
+ [Deprecated="Components"]
+ static undefined staticDeprecatedMethodWithContext(any arg);
+
+ // Overload resolution tests
+ //undefined overload1(DOMString... strs);
+ boolean overload1(TestInterface arg);
+ TestInterface overload1(DOMString strs, TestInterface arg);
+ undefined overload2(TestInterface arg);
+ undefined overload2(optional Dict arg = {});
+ undefined overload2(boolean arg);
+ undefined overload2(DOMString arg);
+ undefined overload3(TestInterface arg);
+ undefined overload3(TestCallback arg);
+ undefined overload3(boolean arg);
+ undefined overload4(TestInterface arg);
+ undefined overload4(TestCallbackInterface arg);
+ undefined overload4(DOMString arg);
+ undefined overload5(long arg);
+ undefined overload5(TestEnum arg);
+ undefined overload6(long arg);
+ undefined overload6(boolean arg);
+ undefined overload7(long arg);
+ undefined overload7(boolean arg);
+ undefined overload7(ByteString arg);
+ undefined overload8(long arg);
+ undefined overload8(TestInterface arg);
+ undefined overload9(long? arg);
+ undefined overload9(DOMString arg);
+ undefined overload10(long? arg);
+ undefined overload10(object arg);
+ undefined overload11(long arg);
+ undefined overload11(DOMString? arg);
+ undefined overload12(long arg);
+ undefined overload12(boolean? arg);
+ undefined overload13(long? arg);
+ undefined overload13(boolean arg);
+ undefined overload14(optional long arg);
+ undefined overload14(TestInterface arg);
+ undefined overload15(long arg);
+ undefined overload15(optional TestInterface arg);
+ undefined overload16(long arg);
+ undefined overload16(optional TestInterface? arg);
+ undefined overload17(sequence<long> arg);
+ undefined overload17(record<DOMString, long> arg);
+ undefined overload18(record<DOMString, DOMString> arg);
+ undefined overload18(sequence<DOMString> arg);
+ undefined overload19(sequence<long> arg);
+ undefined overload19(optional Dict arg = {});
+ undefined overload20(optional Dict arg = {});
+ undefined overload20(sequence<long> arg);
+
+ // Variadic handling
+ undefined passVariadicThirdArg(DOMString arg1, long arg2, TestInterface... arg3);
+
+ // Conditionally exposed methods/attributes
+ [Pref="dom.webidl.test1"]
+ readonly attribute boolean prefable1;
+ [Pref="dom.webidl.test1"]
+ readonly attribute boolean prefable2;
+ [Pref="dom.webidl.test2"]
+ readonly attribute boolean prefable3;
+ [Pref="dom.webidl.test2"]
+ readonly attribute boolean prefable4;
+ [Pref="dom.webidl.test1"]
+ readonly attribute boolean prefable5;
+ [Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ readonly attribute boolean prefable6;
+ [Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ readonly attribute boolean prefable7;
+ [Pref="dom.webidl.test2", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ readonly attribute boolean prefable8;
+ [Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ readonly attribute boolean prefable9;
+ [Pref="dom.webidl.test1"]
+ undefined prefable10();
+ [Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ undefined prefable11();
+ [Pref="dom.webidl.test1", Func="TestFuncControlledMember"]
+ readonly attribute boolean prefable12;
+ [Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ undefined prefable13();
+ [Pref="dom.webidl.test1", Func="TestFuncControlledMember"]
+ readonly attribute boolean prefable14;
+ [Func="TestFuncControlledMember"]
+ readonly attribute boolean prefable15;
+ [Func="TestFuncControlledMember"]
+ readonly attribute boolean prefable16;
+ [Pref="dom.webidl.test1", Func="TestFuncControlledMember"]
+ undefined prefable17();
+ [Func="TestFuncControlledMember"]
+ undefined prefable18();
+ [Func="TestFuncControlledMember"]
+ undefined prefable19();
+ [Trial="TestTrial"]
+ undefined prefable20();
+ [Trial="TestTrial"]
+ readonly attribute boolean prefable21;
+ [Trial="TestTrial", Func="TestFuncControlledMember"]
+ readonly attribute boolean prefable22;
+
+ // Conditionally exposed methods/attributes involving [SecureContext]
+ [SecureContext]
+ readonly attribute boolean conditionalOnSecureContext1;
+ [SecureContext, Pref="dom.webidl.test1"]
+ readonly attribute boolean conditionalOnSecureContext2;
+ [SecureContext, Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ readonly attribute boolean conditionalOnSecureContext3;
+ [SecureContext, Pref="dom.webidl.test1", Func="TestFuncControlledMember"]
+ readonly attribute boolean conditionalOnSecureContext4;
+ [SecureContext]
+ undefined conditionalOnSecureContext5();
+ [SecureContext, Pref="dom.webidl.test1"]
+ undefined conditionalOnSecureContext6();
+ [SecureContext, Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ undefined conditionalOnSecureContext7();
+ [SecureContext, Pref="dom.webidl.test1", Func="TestFuncControlledMember"]
+ undefined conditionalOnSecureContext8();
+ [SecureContext, Trial="TestTrial"]
+ readonly attribute boolean conditionalOnSecureContext9;
+ [SecureContext, Trial="TestTrial"]
+ undefined conditionalOnSecureContext10();
+
+ // Miscellania
+ [LegacyLenientThis] attribute long attrWithLenientThis;
+ [LegacyUnforgeable] readonly attribute long unforgeableAttr;
+ [LegacyUnforgeable, ChromeOnly] readonly attribute long unforgeableAttr2;
+ [LegacyUnforgeable] long unforgeableMethod();
+ [LegacyUnforgeable, ChromeOnly] long unforgeableMethod2();
+ stringifier;
+ undefined passRenamedInterface(TestRenamedInterface arg);
+ [PutForwards=writableByte] readonly attribute TestExampleInterface putForwardsAttr;
+ [PutForwards=writableByte, LegacyLenientThis] readonly attribute TestExampleInterface putForwardsAttr2;
+ [PutForwards=writableByte, ChromeOnly] readonly attribute TestExampleInterface putForwardsAttr3;
+ [Throws] undefined throwingMethod();
+ [Throws] attribute boolean throwingAttr;
+ [GetterThrows] attribute boolean throwingGetterAttr;
+ [SetterThrows] attribute boolean throwingSetterAttr;
+ [CanOOM] undefined canOOMMethod();
+ [CanOOM] attribute boolean canOOMAttr;
+ [GetterCanOOM] attribute boolean canOOMGetterAttr;
+ [SetterCanOOM] attribute boolean canOOMSetterAttr;
+ [NeedsSubjectPrincipal] undefined needsSubjectPrincipalMethod();
+ [NeedsSubjectPrincipal] attribute boolean needsSubjectPrincipalAttr;
+ [NeedsSubjectPrincipal=NonSystem] undefined needsNonSystemSubjectPrincipalMethod();
+ [NeedsSubjectPrincipal=NonSystem] attribute boolean needsNonSystemSubjectPrincipalAttr;
+ [NeedsCallerType] undefined needsCallerTypeMethod();
+ [NeedsCallerType] attribute boolean needsCallerTypeAttr;
+ [CEReactions] undefined ceReactionsMethod();
+ [CEReactions] undefined ceReactionsMethodOverload();
+ [CEReactions] undefined ceReactionsMethodOverload(DOMString bar);
+ [CEReactions] attribute boolean ceReactionsAttr;
+ legacycaller short(unsigned long arg1, TestInterface arg2);
+ undefined passArgsWithDefaults(optional long arg1,
+ optional TestInterface? arg2 = null,
+ optional Dict arg3 = {}, optional double arg4 = 5.0,
+ optional float arg5);
+ attribute any toJSONShouldSkipThis;
+ attribute TestParentInterface toJSONShouldSkipThis2;
+ attribute TestCallbackInterface toJSONShouldSkipThis3;
+ [Default] object toJSON();
+
+ attribute byte dashed-attribute;
+ undefined dashed-method();
+
+ // [NonEnumerable] tests
+ [NonEnumerable]
+ attribute boolean nonEnumerableAttr;
+ [NonEnumerable]
+ const boolean nonEnumerableConst = true;
+ [NonEnumerable]
+ undefined nonEnumerableMethod();
+
+ // [AllowShared] tests
+ attribute [AllowShared] ArrayBufferViewTypedef allowSharedArrayBufferViewTypedef;
+ attribute [AllowShared] ArrayBufferView allowSharedArrayBufferView;
+ attribute [AllowShared] ArrayBufferView? allowSharedNullableArrayBufferView;
+ attribute [AllowShared] ArrayBuffer allowSharedArrayBuffer;
+ attribute [AllowShared] ArrayBuffer? allowSharedNullableArrayBuffer;
+
+ undefined passAllowSharedArrayBufferViewTypedef(AllowSharedArrayBufferViewTypedef foo);
+ undefined passAllowSharedArrayBufferView([AllowShared] ArrayBufferView foo);
+ undefined passAllowSharedNullableArrayBufferView([AllowShared] ArrayBufferView? foo);
+ undefined passAllowSharedArrayBuffer([AllowShared] ArrayBuffer foo);
+ undefined passAllowSharedNullableArrayBuffer([AllowShared] ArrayBuffer? foo);
+ undefined passUnionArrayBuffer((DOMString or ArrayBuffer) foo);
+ undefined passUnionAllowSharedArrayBuffer((DOMString or [AllowShared] ArrayBuffer) foo);
+
+ // If you add things here, add them to TestCodeGen and TestJSImplGen as well
+};
+
+[Exposed=Window]
+interface TestExampleProxyInterface {
+ getter long longIndexedGetter(unsigned long ix);
+ setter undefined longIndexedSetter(unsigned long y, long z);
+ readonly attribute unsigned long length;
+ stringifier DOMString myStringifier();
+ getter short shortNameGetter(DOMString nom);
+ deleter undefined (DOMString nomnom);
+ setter undefined shortNamedSetter(DOMString me, short value);
+};
+
+[Exposed=(Window,Worker)]
+interface TestExampleWorkerInterface {
+ [NeedsSubjectPrincipal] undefined needsSubjectPrincipalMethod();
+ [NeedsSubjectPrincipal] attribute boolean needsSubjectPrincipalAttr;
+ [NeedsCallerType] undefined needsCallerTypeMethod();
+ [NeedsCallerType] attribute boolean needsCallerTypeAttr;
+ [NeedsSubjectPrincipal=NonSystem] undefined needsNonSystemSubjectPrincipalMethod();
+ [NeedsSubjectPrincipal=NonSystem] attribute boolean needsNonSystemSubjectPrincipalAttr;
+};
+
+[Exposed=Window]
+interface TestExampleThrowingConstructorInterface {
+ [Throws]
+ constructor();
+ [Throws]
+ constructor(DOMString str);
+ [Throws]
+ constructor(unsigned long num, boolean? boolArg);
+ [Throws]
+ constructor(TestInterface? iface);
+ [Throws]
+ constructor(unsigned long arg1, TestInterface iface);
+ [Throws]
+ constructor(ArrayBuffer arrayBuf);
+ [Throws]
+ constructor(Uint8Array typedArr);
+ // [Throws] constructor(long arg1, long arg2, (TestInterface or OnlyForUseInConstructor) arg3);
+};
diff --git a/dom/bindings/test/TestFunctions.cpp b/dom/bindings/test/TestFunctions.cpp
new file mode 100644
index 0000000000..c50b408704
--- /dev/null
+++ b/dom/bindings/test/TestFunctions.cpp
@@ -0,0 +1,315 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/BindingUtils.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/TestFunctions.h"
+#include "mozilla/dom/TestFunctionsBinding.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "mozilla/dom/WrapperCachedNonISupportsTestInterface.h"
+#include "nsStringBuffer.h"
+#include "mozITestInterfaceJS.h"
+#include "nsComponentManagerUtils.h"
+#include "nsGlobalWindowInner.h"
+
+namespace mozilla::dom {
+
+/* static */
+TestFunctions* TestFunctions::Constructor(GlobalObject& aGlobal) {
+ return new TestFunctions;
+}
+
+/* static */
+void TestFunctions::ThrowUncatchableException(GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ aRv.ThrowUncatchableException();
+}
+
+/* static */
+Promise* TestFunctions::PassThroughPromise(GlobalObject& aGlobal,
+ Promise& aPromise) {
+ return &aPromise;
+}
+
+/* static */
+already_AddRefed<Promise> TestFunctions::PassThroughCallbackPromise(
+ GlobalObject& aGlobal, PromiseReturner& aCallback, ErrorResult& aRv) {
+ return aCallback.Call(aRv);
+}
+
+void TestFunctions::SetStringData(const nsAString& aString) {
+ mStringData = aString;
+}
+
+void TestFunctions::GetStringDataAsAString(nsAString& aString) {
+ aString = mStringData;
+}
+
+void TestFunctions::GetStringDataAsAString(uint32_t aLength,
+ nsAString& aString) {
+ MOZ_RELEASE_ASSERT(aLength <= mStringData.Length(),
+ "Bogus test passing in a too-big length");
+ aString.Assign(mStringData.BeginReading(), aLength);
+}
+
+void TestFunctions::GetStringDataAsDOMString(const Optional<uint32_t>& aLength,
+ DOMString& aString) {
+ uint32_t length;
+ if (aLength.WasPassed()) {
+ length = aLength.Value();
+ MOZ_RELEASE_ASSERT(length <= mStringData.Length(),
+ "Bogus test passing in a too-big length");
+ } else {
+ length = mStringData.Length();
+ }
+
+ nsStringBuffer* buf = nsStringBuffer::FromString(mStringData);
+ if (buf) {
+ aString.SetKnownLiveStringBuffer(buf, length);
+ return;
+ }
+
+ // We better have an empty mStringData; otherwise why did we not have a string
+ // buffer?
+ MOZ_RELEASE_ASSERT(length == 0, "Why no stringbuffer?");
+ // No need to do anything here; aString is already empty.
+}
+
+void TestFunctions::GetShortLiteralString(nsAString& aString) {
+ // JS inline strings can hold 2 * sizeof(void*) chars, which on 32-bit means 8
+ // chars. Return fewer than that.
+ aString.AssignLiteral(u"012345");
+}
+
+void TestFunctions::GetMediumLiteralString(nsAString& aString) {
+ // JS inline strings are at most 2 * sizeof(void*) chars, so at most 16 on
+ // 64-bit. FakeString can hold 63 chars in its inline buffer (plus the null
+ // terminator). Let's return 40 chars; that way if we ever move to 128-bit
+ // void* or something this test will still be valid.
+ aString.AssignLiteral(u"0123456789012345678901234567890123456789");
+}
+
+void TestFunctions::GetLongLiteralString(nsAString& aString) {
+ // Need more than 64 chars.
+ aString.AssignLiteral(
+ u"0123456789012345678901234567890123456789" // 40
+ "0123456789012345678901234567890123456789" // 80
+ );
+}
+
+void TestFunctions::GetStringbufferString(const nsAString& aInput,
+ nsAString& aRetval) {
+ // We have to be a bit careful: if aRetval is an autostring, if we just assign
+ // it won't cause stringbuffer allocation. So we have to round-trip through
+ // something that definitely causes a stringbuffer allocation.
+ nsString str;
+ // Can't use operator= here, because if aInput is a literal string then str
+ // would end up the same way.
+ str.Assign(aInput.BeginReading(), aInput.Length());
+
+ // Now we might end up hitting our external string cache and getting the wrong
+ // sort of external string, so replace the last char by a different value
+ // (replacing, not just appending, to preserve the length). If we have an
+ // empty string, our caller screwed up and there's not much we can do for
+ // them.
+ if (str.Length() > 1) {
+ char16_t last = str[str.Length() - 1];
+ str.Truncate(str.Length() - 1);
+ if (last == 'x') {
+ str.Append('y');
+ } else {
+ str.Append('x');
+ }
+ }
+
+ // Here we use operator= to preserve stringbufferness.
+ aRetval = str;
+}
+
+StringType TestFunctions::GetStringType(const nsAString& aString) {
+ if (aString.IsLiteral()) {
+ return StringType::Literal;
+ }
+
+ if (nsStringBuffer::FromString(aString)) {
+ return StringType::Stringbuffer;
+ }
+
+ if (aString.GetDataFlags() & nsAString::DataFlags::INLINE) {
+ return StringType::Inline;
+ }
+
+ return StringType::Other;
+}
+
+bool TestFunctions::StringbufferMatchesStored(const nsAString& aString) {
+ return nsStringBuffer::FromString(aString) &&
+ nsStringBuffer::FromString(aString) ==
+ nsStringBuffer::FromString(mStringData);
+}
+
+void TestFunctions::TestThrowNsresult(ErrorResult& aError) {
+ nsCOMPtr<mozITestInterfaceJS> impl =
+ do_CreateInstance("@mozilla.org/dom/test-interface-js;1");
+ aError = impl->TestThrowNsresult();
+}
+
+void TestFunctions::TestThrowNsresultFromNative(ErrorResult& aError) {
+ nsCOMPtr<mozITestInterfaceJS> impl =
+ do_CreateInstance("@mozilla.org/dom/test-interface-js;1");
+ aError = impl->TestThrowNsresultFromNative();
+}
+
+already_AddRefed<Promise> TestFunctions::ThrowToRejectPromise(
+ GlobalObject& aGlobal, ErrorResult& aError) {
+ aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+}
+
+int32_t TestFunctions::One() const { return 1; }
+
+int32_t TestFunctions::Two() const { return 2; }
+
+void TestFunctions::SetClampedNullableOctet(const Nullable<uint8_t>& aOctet) {
+ mClampedNullableOctet = aOctet;
+}
+
+Nullable<uint8_t> TestFunctions::GetClampedNullableOctet() const {
+ return mClampedNullableOctet;
+}
+
+void TestFunctions::SetEnforcedNullableOctet(const Nullable<uint8_t>& aOctet) {
+ mEnforcedNullableOctet = aOctet;
+}
+
+Nullable<uint8_t> TestFunctions::GetEnforcedNullableOctet() const {
+ return mEnforcedNullableOctet;
+}
+
+void TestFunctions::SetArrayBufferView(const ArrayBufferView& aBuffer) {}
+
+void TestFunctions::GetArrayBufferView(JSContext* aCx,
+ JS::Handle<JSObject*> aObj,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aError) {
+ aError.Throw(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+void TestFunctions::SetAllowSharedArrayBufferView(
+ const ArrayBufferView& aBuffer) {}
+
+void TestFunctions::GetAllowSharedArrayBufferView(
+ JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::MutableHandle<JSObject*> aRetval, ErrorResult& aError) {
+ aError.Throw(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+void TestFunctions::SetSequenceOfArrayBufferView(
+ const Sequence<ArrayBufferView>& aBuffers) {}
+
+void TestFunctions::GetSequenceOfArrayBufferView(JSContext* aCx,
+ JS::Handle<JSObject*> aObj,
+ nsTArray<JSObject*>& aRetval,
+ ErrorResult& aError) {
+ aError.Throw(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+void TestFunctions::SetSequenceOfAllowSharedArrayBufferView(
+ const Sequence<ArrayBufferView>& aBuffers) {}
+
+void TestFunctions::GetSequenceOfAllowSharedArrayBufferView(
+ JSContext* aCx, JS::Handle<JSObject*> aObj, nsTArray<JSObject*>& aRetval,
+ ErrorResult& aError) {
+ aError.Throw(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+void TestFunctions::SetArrayBuffer(const ArrayBuffer& aBuffer) {}
+
+void TestFunctions::GetArrayBuffer(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aError) {
+ aError.Throw(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+void TestFunctions::SetAllowSharedArrayBuffer(const ArrayBuffer& aBuffer) {}
+
+void TestFunctions::GetAllowSharedArrayBuffer(
+ JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::MutableHandle<JSObject*> aRetval, ErrorResult& aError) {
+ aError.Throw(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+void TestFunctions::SetSequenceOfArrayBuffer(
+ const Sequence<ArrayBuffer>& aBuffers) {}
+
+void TestFunctions::GetSequenceOfArrayBuffer(JSContext* aCx,
+ JS::Handle<JSObject*> aObj,
+ nsTArray<JSObject*>& aRetval,
+ ErrorResult& aError) {
+ aError.Throw(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+void TestFunctions::SetSequenceOfAllowSharedArrayBuffer(
+ const Sequence<ArrayBuffer>& aBuffers) {}
+
+void TestFunctions::GetSequenceOfAllowSharedArrayBuffer(
+ JSContext* aCx, JS::Handle<JSObject*> aObj, nsTArray<JSObject*>& aRetval,
+ ErrorResult& aError) {
+ aError.Throw(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+void TestFunctions::TestNotAllowShared(const ArrayBufferView& aBuffer) {}
+
+void TestFunctions::TestNotAllowShared(const ArrayBuffer& aBuffer) {}
+
+void TestFunctions::TestNotAllowShared(const nsAString& aBuffer) {}
+
+void TestFunctions::TestAllowShared(const ArrayBufferView& aBuffer) {}
+
+void TestFunctions::TestAllowShared(const ArrayBuffer& aBuffer) {}
+
+void TestFunctions::TestDictWithAllowShared(
+ const DictWithAllowSharedBufferSource& aDict) {}
+
+void TestFunctions::TestUnionOfBuffferSource(
+ const ArrayBufferOrArrayBufferViewOrString& aUnion) {}
+
+void TestFunctions::TestUnionOfAllowSharedBuffferSource(
+ const MaybeSharedArrayBufferOrMaybeSharedArrayBufferView& aUnion) {}
+
+bool TestFunctions::ObjectFromAboutBlank(JSContext* aCx, JSObject* aObj) {
+ // We purposefully don't use WindowOrNull here, because we want to
+ // demonstrate the incorrect behavior we get, not just fail some asserts.
+ RefPtr<nsGlobalWindowInner> win;
+ UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT(Window, aObj, win, aCx);
+ if (!win) {
+ return false;
+ }
+
+ Document* doc = win->GetDoc();
+ if (!doc) {
+ return false;
+ }
+
+ return doc->GetDocumentURI()->GetSpecOrDefault().EqualsLiteral("about:blank");
+}
+
+WrapperCachedNonISupportsTestInterface*
+TestFunctions::WrapperCachedNonISupportsObject() {
+ if (!mWrapperCachedNonISupportsTestInterface) {
+ mWrapperCachedNonISupportsTestInterface =
+ new WrapperCachedNonISupportsTestInterface();
+ }
+ return mWrapperCachedNonISupportsTestInterface;
+}
+
+bool TestFunctions::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aWrapper) {
+ return TestFunctions_Binding::Wrap(aCx, this, aGivenProto, aWrapper);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/test/TestFunctions.h b/dom/bindings/test/TestFunctions.h
new file mode 100644
index 0000000000..16560be0c1
--- /dev/null
+++ b/dom/bindings/test/TestFunctions.h
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_TestFunctions_h
+#define mozilla_dom_TestFunctions_h
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/NonRefcountedDOMObject.h"
+#include "mozilla/dom/TestFunctionsBinding.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+class PromiseReturner;
+class WrapperCachedNonISupportsTestInterface;
+
+class TestFunctions : public NonRefcountedDOMObject {
+ public:
+ static TestFunctions* Constructor(GlobalObject& aGlobal);
+
+ static void ThrowUncatchableException(GlobalObject& aGlobal,
+ ErrorResult& aRv);
+
+ static Promise* PassThroughPromise(GlobalObject& aGlobal, Promise& aPromise);
+
+ MOZ_CAN_RUN_SCRIPT
+ static already_AddRefed<Promise> PassThroughCallbackPromise(
+ GlobalObject& aGlobal, PromiseReturner& aCallback, ErrorResult& aRv);
+
+ void SetStringData(const nsAString& aString);
+
+ void GetStringDataAsAString(nsAString& aString);
+ void GetStringDataAsAString(uint32_t aLength, nsAString& aString);
+ void GetStringDataAsDOMString(const Optional<uint32_t>& aLength,
+ DOMString& aString);
+
+ void GetShortLiteralString(nsAString& aString);
+ void GetMediumLiteralString(nsAString& aString);
+ void GetLongLiteralString(nsAString& aString);
+
+ void GetStringbufferString(const nsAString& aInput, nsAString& aRetval);
+
+ StringType GetStringType(const nsAString& aString);
+
+ bool StringbufferMatchesStored(const nsAString& aString);
+
+ void TestThrowNsresult(ErrorResult& aError);
+ void TestThrowNsresultFromNative(ErrorResult& aError);
+ static already_AddRefed<Promise> ThrowToRejectPromise(GlobalObject& aGlobal,
+ ErrorResult& aError);
+
+ int32_t One() const;
+ int32_t Two() const;
+
+ void SetClampedNullableOctet(const Nullable<uint8_t>& aOctet);
+ Nullable<uint8_t> GetClampedNullableOctet() const;
+ void SetEnforcedNullableOctet(const Nullable<uint8_t>& aOctet);
+ Nullable<uint8_t> GetEnforcedNullableOctet() const;
+
+ void SetArrayBufferView(const ArrayBufferView& aBuffer);
+ void GetArrayBufferView(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aError);
+ void SetAllowSharedArrayBufferView(const ArrayBufferView& aBuffer);
+ void GetAllowSharedArrayBufferView(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aError);
+ void SetSequenceOfArrayBufferView(const Sequence<ArrayBufferView>& aBuffers);
+ void GetSequenceOfArrayBufferView(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ nsTArray<JSObject*>& aRetval,
+ ErrorResult& aError);
+ void SetSequenceOfAllowSharedArrayBufferView(
+ const Sequence<ArrayBufferView>& aBuffers);
+ void GetSequenceOfAllowSharedArrayBufferView(JSContext* aCx,
+ JS::Handle<JSObject*> aObj,
+ nsTArray<JSObject*>& aRetval,
+ ErrorResult& aError);
+ void SetArrayBuffer(const ArrayBuffer& aBuffer);
+ void GetArrayBuffer(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aError);
+ void SetAllowSharedArrayBuffer(const ArrayBuffer& aBuffer);
+ void GetAllowSharedArrayBuffer(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aError);
+ void SetSequenceOfArrayBuffer(const Sequence<ArrayBuffer>& aBuffers);
+ void GetSequenceOfArrayBuffer(JSContext* aCx, JS::Handle<JSObject*> aObj,
+ nsTArray<JSObject*>& aRetval,
+ ErrorResult& aError);
+ void SetSequenceOfAllowSharedArrayBuffer(
+ const Sequence<ArrayBuffer>& aBuffers);
+ void GetSequenceOfAllowSharedArrayBuffer(JSContext* aCx,
+ JS::Handle<JSObject*> aObj,
+ nsTArray<JSObject*>& aRetval,
+ ErrorResult& aError);
+ void TestNotAllowShared(const ArrayBufferView& aBuffer);
+ void TestNotAllowShared(const ArrayBuffer& aBuffer);
+ void TestNotAllowShared(const nsAString& aBuffer);
+ void TestAllowShared(const ArrayBufferView& aBuffer);
+ void TestAllowShared(const ArrayBuffer& aBuffer);
+ void TestDictWithAllowShared(const DictWithAllowSharedBufferSource& aDict);
+ void TestUnionOfBuffferSource(
+ const ArrayBufferOrArrayBufferViewOrString& aUnion);
+ void TestUnionOfAllowSharedBuffferSource(
+ const MaybeSharedArrayBufferOrMaybeSharedArrayBufferView& aUnion);
+
+ bool StaticAndNonStaticOverload() { return false; }
+ static bool StaticAndNonStaticOverload(GlobalObject& aGlobal,
+ const Optional<uint32_t>& aLength) {
+ return true;
+ }
+
+ static bool ObjectFromAboutBlank(JSContext* aCx, JSObject* aObj);
+
+ WrapperCachedNonISupportsTestInterface* WrapperCachedNonISupportsObject();
+
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aWrapper);
+
+ private:
+ nsString mStringData;
+ RefPtr<WrapperCachedNonISupportsTestInterface>
+ mWrapperCachedNonISupportsTestInterface;
+
+ Nullable<uint8_t> mClampedNullableOctet;
+ Nullable<uint8_t> mEnforcedNullableOctet;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TestFunctions_h
diff --git a/dom/bindings/test/TestInterfaceAsyncIterableDouble.cpp b/dom/bindings/test/TestInterfaceAsyncIterableDouble.cpp
new file mode 100644
index 0000000000..a0ec0d64f0
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceAsyncIterableDouble.cpp
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/TestInterfaceAsyncIterableDouble.h"
+#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/BindingUtils.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceAsyncIterableDouble, mParent)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceAsyncIterableDouble)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceAsyncIterableDouble)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceAsyncIterableDouble)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TestInterfaceAsyncIterableDouble::TestInterfaceAsyncIterableDouble(
+ nsPIDOMWindowInner* aParent)
+ : mParent(aParent) {
+ mValues.AppendElement(std::pair<nsString, nsString>(u"a"_ns, u"b"_ns));
+ mValues.AppendElement(std::pair<nsString, nsString>(u"c"_ns, u"d"_ns));
+ mValues.AppendElement(std::pair<nsString, nsString>(u"e"_ns, u"f"_ns));
+}
+
+// static
+already_AddRefed<TestInterfaceAsyncIterableDouble>
+TestInterfaceAsyncIterableDouble::Constructor(const GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<TestInterfaceAsyncIterableDouble> r =
+ new TestInterfaceAsyncIterableDouble(window);
+ return r.forget();
+}
+
+JSObject* TestInterfaceAsyncIterableDouble::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return TestInterfaceAsyncIterableDouble_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner* TestInterfaceAsyncIterableDouble::GetParentObject() const {
+ return mParent;
+}
+
+already_AddRefed<Promise>
+TestInterfaceAsyncIterableDouble::GetNextIterationResult(Iterator* aIterator,
+ ErrorResult& aRv) {
+ RefPtr<Promise> promise = Promise::Create(mParent->AsGlobal(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ NS_DispatchToMainThread(NewRunnableMethod<RefPtr<Iterator>, RefPtr<Promise>>(
+ "TestInterfaceAsyncIterableDouble::GetNextIterationResult", this,
+ &TestInterfaceAsyncIterableDouble::ResolvePromise, aIterator, promise));
+
+ return promise.forget();
+}
+
+void TestInterfaceAsyncIterableDouble::ResolvePromise(Iterator* aIterator,
+ Promise* aPromise) {
+ IteratorData& data = aIterator->Data();
+
+ // Test data: ['a', 'b'], ['c', 'd'], ['e', 'f']
+ uint32_t idx = data.mIndex;
+ if (idx >= mValues.Length()) {
+ iterator_utils::ResolvePromiseForFinished(aPromise);
+ } else {
+ switch (aIterator->GetIteratorType()) {
+ case IterableIteratorBase::IteratorType::Keys:
+ aPromise->MaybeResolve(mValues[idx].first);
+ break;
+ case IterableIteratorBase::IteratorType::Values:
+ aPromise->MaybeResolve(mValues[idx].second);
+ break;
+ case IterableIteratorBase::IteratorType::Entries:
+ iterator_utils::ResolvePromiseWithKeyAndValue(
+ aPromise, mValues[idx].first, mValues[idx].second);
+ break;
+ }
+
+ data.mIndex++;
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/test/TestInterfaceAsyncIterableDouble.h b/dom/bindings/test/TestInterfaceAsyncIterableDouble.h
new file mode 100644
index 0000000000..4b458617dc
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceAsyncIterableDouble.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_TestInterfaceAsyncIterableDouble_h
+#define mozilla_dom_TestInterfaceAsyncIterableDouble_h
+
+#include "IterableIterator.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+
+// Implementation of test binding for webidl iterable interfaces, using
+// primitives for value type
+class TestInterfaceAsyncIterableDouble final : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(TestInterfaceAsyncIterableDouble)
+
+ explicit TestInterfaceAsyncIterableDouble(nsPIDOMWindowInner* aParent);
+ nsPIDOMWindowInner* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ static already_AddRefed<TestInterfaceAsyncIterableDouble> Constructor(
+ const GlobalObject& aGlobal, ErrorResult& rv);
+
+ struct IteratorData {
+ uint32_t mIndex = 0;
+ };
+
+ using Iterator = AsyncIterableIterator<TestInterfaceAsyncIterableDouble>;
+
+ void InitAsyncIteratorData(IteratorData& aData, Iterator::IteratorType aType,
+ ErrorResult& aError) {}
+
+ already_AddRefed<Promise> GetNextIterationResult(Iterator* aIterator,
+ ErrorResult& aRv);
+
+ private:
+ virtual ~TestInterfaceAsyncIterableDouble() = default;
+ void ResolvePromise(Iterator* aIterator, Promise* aPromise);
+
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+ nsTArray<std::pair<nsString, nsString>> mValues;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TestInterfaceAsyncIterableDouble_h
diff --git a/dom/bindings/test/TestInterfaceAsyncIterableDoubleUnion.cpp b/dom/bindings/test/TestInterfaceAsyncIterableDoubleUnion.cpp
new file mode 100644
index 0000000000..b0b16532ed
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceAsyncIterableDoubleUnion.cpp
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/TestInterfaceAsyncIterableDoubleUnion.h"
+#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/UnionTypes.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceAsyncIterableDoubleUnion,
+ mParent)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceAsyncIterableDoubleUnion)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceAsyncIterableDoubleUnion)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceAsyncIterableDoubleUnion)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TestInterfaceAsyncIterableDoubleUnion::TestInterfaceAsyncIterableDoubleUnion(
+ nsPIDOMWindowInner* aParent)
+ : mParent(aParent) {
+ OwningStringOrLong a;
+ a.SetAsLong() = 1;
+ mValues.AppendElement(std::pair<nsString, OwningStringOrLong>(u"long"_ns, a));
+ a.SetAsString() = u"a"_ns;
+ mValues.AppendElement(
+ std::pair<nsString, OwningStringOrLong>(u"string"_ns, a));
+}
+
+// static
+already_AddRefed<TestInterfaceAsyncIterableDoubleUnion>
+TestInterfaceAsyncIterableDoubleUnion::Constructor(const GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<TestInterfaceAsyncIterableDoubleUnion> r =
+ new TestInterfaceAsyncIterableDoubleUnion(window);
+ return r.forget();
+}
+
+JSObject* TestInterfaceAsyncIterableDoubleUnion::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return TestInterfaceAsyncIterableDoubleUnion_Binding::Wrap(aCx, this,
+ aGivenProto);
+}
+
+nsPIDOMWindowInner* TestInterfaceAsyncIterableDoubleUnion::GetParentObject()
+ const {
+ return mParent;
+}
+
+already_AddRefed<Promise>
+TestInterfaceAsyncIterableDoubleUnion::GetNextIterationResult(
+ Iterator* aIterator, ErrorResult& aRv) {
+ RefPtr<Promise> promise = Promise::Create(mParent->AsGlobal(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ NS_DispatchToMainThread(NewRunnableMethod<RefPtr<Iterator>, RefPtr<Promise>>(
+ "TestInterfaceAsyncIterableDoubleUnion::GetNextIterationResult", this,
+ &TestInterfaceAsyncIterableDoubleUnion::ResolvePromise, aIterator,
+ promise));
+
+ return promise.forget();
+}
+
+void TestInterfaceAsyncIterableDoubleUnion::ResolvePromise(Iterator* aIterator,
+ Promise* aPromise) {
+ IteratorData& data = aIterator->Data();
+
+ // Test data:
+ // [long, 1], [string, "a"]
+ uint32_t idx = data.mIndex;
+ if (idx >= mValues.Length()) {
+ iterator_utils::ResolvePromiseForFinished(aPromise);
+ } else {
+ switch (aIterator->GetIteratorType()) {
+ case IterableIteratorBase::IteratorType::Keys:
+ aPromise->MaybeResolve(mValues[idx].first);
+ break;
+ case IterableIteratorBase::IteratorType::Values:
+ aPromise->MaybeResolve(mValues[idx].second);
+ break;
+ case IterableIteratorBase::IteratorType::Entries:
+ iterator_utils::ResolvePromiseWithKeyAndValue(
+ aPromise, mValues[idx].first, mValues[idx].second);
+ break;
+ }
+
+ data.mIndex++;
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/test/TestInterfaceAsyncIterableDoubleUnion.h b/dom/bindings/test/TestInterfaceAsyncIterableDoubleUnion.h
new file mode 100644
index 0000000000..902c99b1a9
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceAsyncIterableDoubleUnion.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_TestInterfaceAsyncIterableDoubleUnion_h
+#define mozilla_dom_TestInterfaceAsyncIterableDoubleUnion_h
+
+#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
+#include "IterableIterator.h"
+#include "nsCOMPtr.h"
+#include "nsWrapperCache.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+
+// Implementation of test binding for webidl iterable interfaces, using
+// primitives for value type
+class TestInterfaceAsyncIterableDoubleUnion final : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(
+ TestInterfaceAsyncIterableDoubleUnion)
+
+ explicit TestInterfaceAsyncIterableDoubleUnion(nsPIDOMWindowInner* aParent);
+ nsPIDOMWindowInner* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ static already_AddRefed<TestInterfaceAsyncIterableDoubleUnion> Constructor(
+ const GlobalObject& aGlobal, ErrorResult& rv);
+
+ struct IteratorData {
+ uint32_t mIndex = 0;
+ };
+
+ using Iterator = AsyncIterableIterator<TestInterfaceAsyncIterableDoubleUnion>;
+
+ void InitAsyncIteratorData(IteratorData& aData, Iterator::IteratorType aType,
+ ErrorResult& aError) {}
+
+ already_AddRefed<Promise> GetNextIterationResult(Iterator* aIterator,
+ ErrorResult& aRv);
+
+ private:
+ virtual ~TestInterfaceAsyncIterableDoubleUnion() = default;
+ void ResolvePromise(Iterator* aIterator, Promise* aPromise);
+
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+ nsTArray<std::pair<nsString, OwningStringOrLong>> mValues;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TestInterfaceAsyncIterableDoubleUnion_h
diff --git a/dom/bindings/test/TestInterfaceAsyncIterableSingle.cpp b/dom/bindings/test/TestInterfaceAsyncIterableSingle.cpp
new file mode 100644
index 0000000000..f8d049b840
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceAsyncIterableSingle.cpp
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/TestInterfaceAsyncIterableSingle.h"
+#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/IterableIterator.h"
+#include "mozilla/dom/Promise-inl.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceAsyncIterableSingle, mParent)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceAsyncIterableSingle)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceAsyncIterableSingle)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceAsyncIterableSingle)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TestInterfaceAsyncIterableSingle::TestInterfaceAsyncIterableSingle(
+ nsPIDOMWindowInner* aParent, bool aFailToInit)
+ : mParent(aParent), mFailToInit(aFailToInit) {}
+
+// static
+already_AddRefed<TestInterfaceAsyncIterableSingle>
+TestInterfaceAsyncIterableSingle::Constructor(
+ const GlobalObject& aGlobal,
+ const TestInterfaceAsyncIterableSingleOptions& aOptions, ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<TestInterfaceAsyncIterableSingle> r =
+ new TestInterfaceAsyncIterableSingle(window, aOptions.mFailToInit);
+ return r.forget();
+}
+
+JSObject* TestInterfaceAsyncIterableSingle::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return TestInterfaceAsyncIterableSingle_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner* TestInterfaceAsyncIterableSingle::GetParentObject() const {
+ return mParent;
+}
+
+void TestInterfaceAsyncIterableSingle::InitAsyncIteratorData(
+ IteratorData& aData, Iterator::IteratorType aType, ErrorResult& aError) {
+ if (mFailToInit) {
+ aError.ThrowTypeError("Caller asked us to fail");
+ return;
+ }
+
+ // Nothing else to do.
+ MOZ_ASSERT(aData.mIndex == 0);
+ MOZ_ASSERT(aData.mMultiplier == 1);
+}
+
+already_AddRefed<Promise>
+TestInterfaceAsyncIterableSingle::GetNextIterationResult(Iterator* aIterator,
+ ErrorResult& aRv) {
+ return GetNextIterationResult(aIterator, aIterator->Data(), aRv);
+}
+
+already_AddRefed<Promise>
+TestInterfaceAsyncIterableSingle::GetNextIterationResult(
+ IterableIteratorBase* aIterator, IteratorData& aData, ErrorResult& aRv) {
+ RefPtr<Promise> promise = Promise::Create(mParent->AsGlobal(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIRunnable> callResolvePromise =
+ NewRunnableMethod<RefPtr<IterableIteratorBase>, IteratorData&,
+ RefPtr<Promise>>(
+ "TestInterfaceAsyncIterableSingle::GetNextIterationResult", this,
+ &TestInterfaceAsyncIterableSingle::ResolvePromise, aIterator, aData,
+ promise);
+ if (aData.mBlockingPromisesIndex < aData.mBlockingPromises.Length()) {
+ aData.mBlockingPromises[aData.mBlockingPromisesIndex]
+ ->AddCallbacksWithCycleCollectedArgs(
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ nsIRunnable* aCallResolvePromise) {
+ NS_DispatchToMainThread(aCallResolvePromise);
+ },
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ nsIRunnable* aCallResolvePromise) {},
+ std::move(callResolvePromise));
+ ++aData.mBlockingPromisesIndex;
+ } else {
+ NS_DispatchToMainThread(callResolvePromise);
+ }
+
+ return promise.forget();
+}
+
+void TestInterfaceAsyncIterableSingle::ResolvePromise(
+ IterableIteratorBase* aIterator, IteratorData& aData, Promise* aPromise) {
+ if (aData.mIndex >= 10) {
+ iterator_utils::ResolvePromiseForFinished(aPromise);
+ } else {
+ aPromise->MaybeResolve(int32_t(aData.mIndex * 9 % 7 * aData.mMultiplier));
+
+ aData.mIndex++;
+ }
+}
+
+void TestInterfaceAsyncIterableSingle::IteratorData::Traverse(
+ nsCycleCollectionTraversalCallback& cb) {
+ TestInterfaceAsyncIterableSingle::IteratorData* tmp = this;
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBlockingPromises);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mThrowFromReturn);
+}
+void TestInterfaceAsyncIterableSingle::IteratorData::Unlink() {
+ TestInterfaceAsyncIterableSingle::IteratorData* tmp = this;
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mBlockingPromises);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mThrowFromReturn);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/test/TestInterfaceAsyncIterableSingle.h b/dom/bindings/test/TestInterfaceAsyncIterableSingle.h
new file mode 100644
index 0000000000..c92b35cc7b
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceAsyncIterableSingle.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_TestInterfaceAsyncIterableSingle_h
+#define mozilla_dom_TestInterfaceAsyncIterableSingle_h
+
+#include "IterableIterator.h"
+#include "nsCOMPtr.h"
+#include "nsWrapperCache.h"
+#include "nsTArray.h"
+#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+struct TestInterfaceAsyncIterableSingleOptions;
+
+// Implementation of test binding for webidl iterable interfaces, using
+// primitives for value type
+class TestInterfaceAsyncIterableSingle : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(TestInterfaceAsyncIterableSingle)
+
+ explicit TestInterfaceAsyncIterableSingle(nsPIDOMWindowInner* aParent,
+ bool aFailToInit = false);
+ nsPIDOMWindowInner* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ static already_AddRefed<TestInterfaceAsyncIterableSingle> Constructor(
+ const GlobalObject& aGlobal,
+ const TestInterfaceAsyncIterableSingleOptions& aOptions, ErrorResult& rv);
+
+ using Iterator = AsyncIterableIterator<TestInterfaceAsyncIterableSingle>;
+ already_AddRefed<Promise> GetNextIterationResult(Iterator* aIterator,
+ ErrorResult& aRv);
+
+ struct IteratorData {
+ void Traverse(nsCycleCollectionTraversalCallback& cb);
+ void Unlink();
+
+ uint32_t mIndex = 0;
+ uint32_t mMultiplier = 1;
+ Sequence<OwningNonNull<Promise>> mBlockingPromises;
+ size_t mBlockingPromisesIndex = 0;
+ uint32_t mFailNextAfter = 4294967295;
+ bool mThrowFromNext = false;
+ RefPtr<TestThrowingCallback> mThrowFromReturn;
+ };
+
+ void InitAsyncIteratorData(IteratorData& aData, Iterator::IteratorType aType,
+ ErrorResult& aError);
+
+ protected:
+ already_AddRefed<Promise> GetNextIterationResult(
+ IterableIteratorBase* aIterator, IteratorData& aData, ErrorResult& aRv);
+
+ virtual ~TestInterfaceAsyncIterableSingle() = default;
+
+ private:
+ void ResolvePromise(IterableIteratorBase* aIterator, IteratorData& aData,
+ Promise* aPromise);
+
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+ bool mFailToInit;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TestInterfaceAsyncIterableSingle_h
diff --git a/dom/bindings/test/TestInterfaceAsyncIterableSingleWithArgs.cpp b/dom/bindings/test/TestInterfaceAsyncIterableSingleWithArgs.cpp
new file mode 100644
index 0000000000..6c02829186
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceAsyncIterableSingleWithArgs.cpp
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/TestInterfaceAsyncIterableSingleWithArgs.h"
+#include "ScriptSettings.h"
+#include "js/Value.h"
+#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/IterableIterator.h"
+#include "mozilla/dom/Promise-inl.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(TestInterfaceAsyncIterableSingleWithArgs)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(
+ TestInterfaceAsyncIterableSingleWithArgs, TestInterfaceAsyncIterableSingle)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(
+ TestInterfaceAsyncIterableSingleWithArgs, TestInterfaceAsyncIterableSingle)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(
+ TestInterfaceAsyncIterableSingleWithArgs, TestInterfaceAsyncIterableSingle)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReturnLastCalledWith)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_ADDREF_INHERITED(TestInterfaceAsyncIterableSingleWithArgs,
+ TestInterfaceAsyncIterableSingle)
+NS_IMPL_RELEASE_INHERITED(TestInterfaceAsyncIterableSingleWithArgs,
+ TestInterfaceAsyncIterableSingle)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
+ TestInterfaceAsyncIterableSingleWithArgs)
+NS_INTERFACE_MAP_END_INHERITING(TestInterfaceAsyncIterableSingle)
+
+// static
+already_AddRefed<TestInterfaceAsyncIterableSingleWithArgs>
+TestInterfaceAsyncIterableSingleWithArgs::Constructor(
+ const GlobalObject& aGlobal, ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<TestInterfaceAsyncIterableSingleWithArgs> r =
+ new TestInterfaceAsyncIterableSingleWithArgs(window);
+ return r.forget();
+}
+
+JSObject* TestInterfaceAsyncIterableSingleWithArgs::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return TestInterfaceAsyncIterableSingleWithArgs_Binding::Wrap(aCx, this,
+ aGivenProto);
+}
+
+void TestInterfaceAsyncIterableSingleWithArgs::InitAsyncIteratorData(
+ IteratorData& aData, Iterator::IteratorType aType,
+ const TestInterfaceAsyncIteratorOptions& aOptions, ErrorResult& aError) {
+ aData.mMultiplier = aOptions.mMultiplier;
+ aData.mBlockingPromises = aOptions.mBlockingPromises;
+ aData.mFailNextAfter = aOptions.mFailNextAfter;
+ aData.mThrowFromNext = aOptions.mThrowFromNext;
+ if (aOptions.mThrowFromReturn.WasPassed()) {
+ aData.mThrowFromReturn = &aOptions.mThrowFromReturn.Value();
+ }
+}
+
+already_AddRefed<Promise>
+TestInterfaceAsyncIterableSingleWithArgs::GetNextIterationResult(
+ Iterator* aIterator, ErrorResult& aRv) {
+ if (aIterator->Data().mThrowFromNext) {
+ AutoJSAPI jsapi;
+ MOZ_RELEASE_ASSERT(jsapi.Init(GetParentObject()));
+ JS_ReportErrorASCII(jsapi.cx(), "Throwing from next().");
+ aRv.MightThrowJSException();
+ aRv.StealExceptionFromJSContext(jsapi.cx());
+ return nullptr;
+ }
+
+ if (aIterator->Data().mIndex == aIterator->Data().mFailNextAfter) {
+ aRv.ThrowTypeError("Failed because we of 'failNextAfter'.");
+ return nullptr;
+ }
+ return TestInterfaceAsyncIterableSingle::GetNextIterationResult(
+ aIterator, aIterator->Data(), aRv);
+}
+
+already_AddRefed<Promise>
+TestInterfaceAsyncIterableSingleWithArgs::IteratorReturn(
+ JSContext* aCx, Iterator* aIterator, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ ++mReturnCallCount;
+
+ if (RefPtr<TestThrowingCallback> throwFromReturn =
+ aIterator->Data().mThrowFromReturn) {
+ throwFromReturn->Call(aRv, nullptr, CallbackFunction::eRethrowExceptions);
+ return nullptr;
+ }
+
+ RefPtr<Promise> promise = Promise::Create(GetParentObject()->AsGlobal(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ mReturnLastCalledWith = aValue;
+ promise->MaybeResolve(JS::UndefinedHandleValue);
+ return promise.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/test/TestInterfaceAsyncIterableSingleWithArgs.h b/dom/bindings/test/TestInterfaceAsyncIterableSingleWithArgs.h
new file mode 100644
index 0000000000..b9ef4853b8
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceAsyncIterableSingleWithArgs.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_TestInterfaceAsyncIterableSingleWithArgs_h
+#define mozilla_dom_TestInterfaceAsyncIterableSingleWithArgs_h
+
+#include "mozilla/dom/TestInterfaceAsyncIterableSingle.h"
+
+namespace mozilla::dom {
+
+struct TestInterfaceAsyncIteratorOptions;
+
+// Implementation of test binding for webidl iterable interfaces, using
+// primitives for value type
+class TestInterfaceAsyncIterableSingleWithArgs final
+ : public TestInterfaceAsyncIterableSingle {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
+ TestInterfaceAsyncIterableSingleWithArgs,
+ TestInterfaceAsyncIterableSingle)
+
+ using TestInterfaceAsyncIterableSingle::TestInterfaceAsyncIterableSingle;
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ static already_AddRefed<TestInterfaceAsyncIterableSingleWithArgs> Constructor(
+ const GlobalObject& aGlobal, ErrorResult& rv);
+
+ using Iterator =
+ AsyncIterableIterator<TestInterfaceAsyncIterableSingleWithArgs>;
+
+ void InitAsyncIteratorData(IteratorData& aData, Iterator::IteratorType aType,
+ const TestInterfaceAsyncIteratorOptions& aOptions,
+ ErrorResult& aError);
+
+ already_AddRefed<Promise> GetNextIterationResult(
+ Iterator* aIterator, ErrorResult& aRv) MOZ_CAN_RUN_SCRIPT;
+ already_AddRefed<Promise> IteratorReturn(JSContext* aCx, Iterator* aIterator,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) MOZ_CAN_RUN_SCRIPT;
+
+ uint32_t ReturnCallCount() { return mReturnCallCount; }
+ void GetReturnLastCalledWith(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aReturnCalledWith) {
+ aReturnCalledWith.set(mReturnLastCalledWith);
+ }
+
+ private:
+ ~TestInterfaceAsyncIterableSingleWithArgs() = default;
+
+ JS::Heap<JS::Value> mReturnLastCalledWith;
+ uint32_t mReturnCallCount = 0;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_TestInterfaceAsyncIterableSingleWithArgs_h
diff --git a/dom/bindings/test/TestInterfaceIterableDouble.cpp b/dom/bindings/test/TestInterfaceIterableDouble.cpp
new file mode 100644
index 0000000000..8882518399
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceIterableDouble.cpp
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/TestInterfaceIterableDouble.h"
+#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/BindingUtils.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceIterableDouble, mParent)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceIterableDouble)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceIterableDouble)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceIterableDouble)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TestInterfaceIterableDouble::TestInterfaceIterableDouble(
+ nsPIDOMWindowInner* aParent)
+ : mParent(aParent) {
+ mValues.AppendElement(std::pair<nsString, nsString>(u"a"_ns, u"b"_ns));
+ mValues.AppendElement(std::pair<nsString, nsString>(u"c"_ns, u"d"_ns));
+ mValues.AppendElement(std::pair<nsString, nsString>(u"e"_ns, u"f"_ns));
+}
+
+// static
+already_AddRefed<TestInterfaceIterableDouble>
+TestInterfaceIterableDouble::Constructor(const GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<TestInterfaceIterableDouble> r =
+ new TestInterfaceIterableDouble(window);
+ return r.forget();
+}
+
+JSObject* TestInterfaceIterableDouble::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return TestInterfaceIterableDouble_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner* TestInterfaceIterableDouble::GetParentObject() const {
+ return mParent;
+}
+
+size_t TestInterfaceIterableDouble::GetIterableLength() {
+ return mValues.Length();
+}
+
+nsAString& TestInterfaceIterableDouble::GetKeyAtIndex(uint32_t aIndex) {
+ MOZ_ASSERT(aIndex < mValues.Length());
+ return mValues.ElementAt(aIndex).first;
+}
+
+nsAString& TestInterfaceIterableDouble::GetValueAtIndex(uint32_t aIndex) {
+ MOZ_ASSERT(aIndex < mValues.Length());
+ return mValues.ElementAt(aIndex).second;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/test/TestInterfaceIterableDouble.h b/dom/bindings/test/TestInterfaceIterableDouble.h
new file mode 100644
index 0000000000..ba38b47e89
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceIterableDouble.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_TestInterfaceIterableDouble_h
+#define mozilla_dom_TestInterfaceIterableDouble_h
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+
+// Implementation of test binding for webidl iterable interfaces, using
+// primitives for value type
+class TestInterfaceIterableDouble final : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(TestInterfaceIterableDouble)
+
+ explicit TestInterfaceIterableDouble(nsPIDOMWindowInner* aParent);
+ nsPIDOMWindowInner* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ static already_AddRefed<TestInterfaceIterableDouble> Constructor(
+ const GlobalObject& aGlobal, ErrorResult& rv);
+
+ size_t GetIterableLength();
+ nsAString& GetKeyAtIndex(uint32_t aIndex);
+ nsAString& GetValueAtIndex(uint32_t aIndex);
+
+ private:
+ virtual ~TestInterfaceIterableDouble() = default;
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+ nsTArray<std::pair<nsString, nsString>> mValues;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TestInterfaceIterableDouble_h
diff --git a/dom/bindings/test/TestInterfaceIterableDoubleUnion.cpp b/dom/bindings/test/TestInterfaceIterableDoubleUnion.cpp
new file mode 100644
index 0000000000..56b968e549
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceIterableDoubleUnion.cpp
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/TestInterfaceIterableDoubleUnion.h"
+#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/BindingUtils.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceIterableDoubleUnion, mParent)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceIterableDoubleUnion)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceIterableDoubleUnion)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceIterableDoubleUnion)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TestInterfaceIterableDoubleUnion::TestInterfaceIterableDoubleUnion(
+ nsPIDOMWindowInner* aParent)
+ : mParent(aParent) {
+ OwningStringOrLong a;
+ a.SetAsLong() = 1;
+ mValues.AppendElement(std::pair<nsString, OwningStringOrLong>(u"long"_ns, a));
+ a.SetAsString() = u"a"_ns;
+ mValues.AppendElement(
+ std::pair<nsString, OwningStringOrLong>(u"string"_ns, a));
+}
+
+// static
+already_AddRefed<TestInterfaceIterableDoubleUnion>
+TestInterfaceIterableDoubleUnion::Constructor(const GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<TestInterfaceIterableDoubleUnion> r =
+ new TestInterfaceIterableDoubleUnion(window);
+ return r.forget();
+}
+
+JSObject* TestInterfaceIterableDoubleUnion::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return TestInterfaceIterableDoubleUnion_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner* TestInterfaceIterableDoubleUnion::GetParentObject() const {
+ return mParent;
+}
+
+size_t TestInterfaceIterableDoubleUnion::GetIterableLength() {
+ return mValues.Length();
+}
+
+nsAString& TestInterfaceIterableDoubleUnion::GetKeyAtIndex(uint32_t aIndex) {
+ MOZ_ASSERT(aIndex < mValues.Length());
+ return mValues.ElementAt(aIndex).first;
+}
+
+OwningStringOrLong& TestInterfaceIterableDoubleUnion::GetValueAtIndex(
+ uint32_t aIndex) {
+ MOZ_ASSERT(aIndex < mValues.Length());
+ return mValues.ElementAt(aIndex).second;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/test/TestInterfaceIterableDoubleUnion.h b/dom/bindings/test/TestInterfaceIterableDoubleUnion.h
new file mode 100644
index 0000000000..d35744922e
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceIterableDoubleUnion.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_TestInterfaceIterableDoubleUnion_h
+#define mozilla_dom_TestInterfaceIterableDoubleUnion_h
+
+#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
+#include "nsCOMPtr.h"
+#include "nsWrapperCache.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+
+// Implementation of test binding for webidl iterable interfaces, using
+// primitives for value type
+class TestInterfaceIterableDoubleUnion final : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(TestInterfaceIterableDoubleUnion)
+
+ explicit TestInterfaceIterableDoubleUnion(nsPIDOMWindowInner* aParent);
+ nsPIDOMWindowInner* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ static already_AddRefed<TestInterfaceIterableDoubleUnion> Constructor(
+ const GlobalObject& aGlobal, ErrorResult& rv);
+
+ size_t GetIterableLength();
+ nsAString& GetKeyAtIndex(uint32_t aIndex);
+ OwningStringOrLong& GetValueAtIndex(uint32_t aIndex);
+
+ private:
+ virtual ~TestInterfaceIterableDoubleUnion() = default;
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+ nsTArray<std::pair<nsString, OwningStringOrLong>> mValues;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TestInterfaceIterableDoubleUnion_h
diff --git a/dom/bindings/test/TestInterfaceIterableSingle.cpp b/dom/bindings/test/TestInterfaceIterableSingle.cpp
new file mode 100644
index 0000000000..4da02e9093
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceIterableSingle.cpp
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/TestInterfaceIterableSingle.h"
+#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/BindingUtils.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceIterableSingle, mParent)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceIterableSingle)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceIterableSingle)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceIterableSingle)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TestInterfaceIterableSingle::TestInterfaceIterableSingle(
+ nsPIDOMWindowInner* aParent)
+ : mParent(aParent) {
+ for (int i = 0; i < 3; ++i) {
+ mValues.AppendElement(i);
+ }
+}
+
+// static
+already_AddRefed<TestInterfaceIterableSingle>
+TestInterfaceIterableSingle::Constructor(const GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<TestInterfaceIterableSingle> r =
+ new TestInterfaceIterableSingle(window);
+ return r.forget();
+}
+
+JSObject* TestInterfaceIterableSingle::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return TestInterfaceIterableSingle_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner* TestInterfaceIterableSingle::GetParentObject() const {
+ return mParent;
+}
+
+uint32_t TestInterfaceIterableSingle::Length() const {
+ return mValues.Length();
+}
+
+int32_t TestInterfaceIterableSingle::IndexedGetter(uint32_t aIndex,
+ bool& aFound) const {
+ if (aIndex >= mValues.Length()) {
+ aFound = false;
+ return 0;
+ }
+
+ aFound = true;
+ return mValues[aIndex];
+}
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/test/TestInterfaceIterableSingle.h b/dom/bindings/test/TestInterfaceIterableSingle.h
new file mode 100644
index 0000000000..68104d1d15
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceIterableSingle.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_TestInterfaceIterableSingle_h
+#define mozilla_dom_TestInterfaceIterableSingle_h
+
+#include "nsCOMPtr.h"
+#include "nsWrapperCache.h"
+#include "nsTArray.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+
+// Implementation of test binding for webidl iterable interfaces, using
+// primitives for value type
+class TestInterfaceIterableSingle final : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(TestInterfaceIterableSingle)
+
+ explicit TestInterfaceIterableSingle(nsPIDOMWindowInner* aParent);
+ nsPIDOMWindowInner* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ static already_AddRefed<TestInterfaceIterableSingle> Constructor(
+ const GlobalObject& aGlobal, ErrorResult& rv);
+
+ uint32_t Length() const;
+ int32_t IndexedGetter(uint32_t aIndex, bool& aFound) const;
+
+ private:
+ virtual ~TestInterfaceIterableSingle() = default;
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+ nsTArray<int32_t> mValues;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TestInterfaceIterableSingle_h
diff --git a/dom/bindings/test/TestInterfaceJS.sys.mjs b/dom/bindings/test/TestInterfaceJS.sys.mjs
new file mode 100644
index 0000000000..53ce9d107d
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceJS.sys.mjs
@@ -0,0 +1,220 @@
+/* -*- 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/. */
+/* global noSuchMethodExistsYo1, noSuchMethodExistsYo2, noSuchMethodExistsYo3 */
+
+export function TestInterfaceJS() {}
+
+TestInterfaceJS.prototype = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIDOMGlobalPropertyInitializer",
+ "mozITestInterfaceJS",
+ ]),
+
+ init(win) {
+ this._win = win;
+ },
+
+ __init(anyArg, objectArg, dictionaryArg) {
+ this._anyAttr = undefined;
+ this._objectAttr = null;
+ this._anyArg = anyArg;
+ this._objectArg = objectArg;
+ this._dictionaryArg = dictionaryArg;
+ },
+
+ get anyArg() {
+ return this._anyArg;
+ },
+ get objectArg() {
+ return this._objectArg;
+ },
+ getDictionaryArg() {
+ return this._dictionaryArg;
+ },
+ get anyAttr() {
+ return this._anyAttr;
+ },
+ set anyAttr(val) {
+ this._anyAttr = val;
+ },
+ get objectAttr() {
+ return this._objectAttr;
+ },
+ set objectAttr(val) {
+ this._objectAttr = val;
+ },
+ getDictionaryAttr() {
+ return this._dictionaryAttr;
+ },
+ setDictionaryAttr(val) {
+ this._dictionaryAttr = val;
+ },
+ pingPongAny(any) {
+ return any;
+ },
+ pingPongObject(obj) {
+ return obj;
+ },
+ pingPongObjectOrString(objectOrString) {
+ return objectOrString;
+ },
+ pingPongDictionary(dict) {
+ return dict;
+ },
+ pingPongDictionaryOrLong(dictOrLong) {
+ return dictOrLong.anyMember || dictOrLong;
+ },
+ pingPongRecord(rec) {
+ return JSON.stringify(rec);
+ },
+ objectSequenceLength(seq) {
+ return seq.length;
+ },
+ anySequenceLength(seq) {
+ return seq.length;
+ },
+
+ getCallerPrincipal() {
+ return Cu.getWebIDLCallerPrincipal().origin;
+ },
+
+ convertSVS(svs) {
+ return svs;
+ },
+
+ pingPongUnion(x) {
+ return x;
+ },
+ pingPongUnionContainingNull(x) {
+ return x;
+ },
+ pingPongNullableUnion(x) {
+ return x;
+ },
+ returnBadUnion(x) {
+ return 3;
+ },
+
+ testSequenceOverload(arg) {},
+ testSequenceUnion(arg) {},
+
+ testThrowError() {
+ throw new this._win.Error("We are an Error");
+ },
+
+ testThrowDOMException() {
+ throw new this._win.DOMException(
+ "We are a DOMException",
+ "NotSupportedError"
+ );
+ },
+
+ testThrowTypeError() {
+ throw new this._win.TypeError("We are a TypeError");
+ },
+
+ testThrowNsresult() {
+ // This is explicitly testing preservation of raw thrown Crs in XPCJS
+ // eslint-disable-next-line mozilla/no-throw-cr-literal
+ throw Cr.NS_BINDING_ABORTED;
+ },
+
+ testThrowNsresultFromNative(x) {
+ // We want to throw an exception that we generate from an nsresult thrown
+ // by a C++ component.
+ Services.io.notImplemented();
+ },
+
+ testThrowCallbackError(callback) {
+ callback();
+ },
+
+ testThrowXraySelfHosted() {
+ this._win.Array.prototype.forEach();
+ },
+
+ testThrowSelfHosted() {
+ Array.prototype.forEach();
+ },
+
+ testPromiseWithThrowingChromePromiseInit() {
+ return new this._win.Promise(function () {
+ noSuchMethodExistsYo1();
+ });
+ },
+
+ testPromiseWithThrowingContentPromiseInit(func) {
+ return new this._win.Promise(func);
+ },
+
+ testPromiseWithDOMExceptionThrowingPromiseInit() {
+ return new this._win.Promise(() => {
+ throw new this._win.DOMException(
+ "We are a second DOMException",
+ "NotFoundError"
+ );
+ });
+ },
+
+ testPromiseWithThrowingChromeThenFunction() {
+ return this._win.Promise.resolve(5).then(function () {
+ noSuchMethodExistsYo2();
+ });
+ },
+
+ testPromiseWithThrowingContentThenFunction(func) {
+ return this._win.Promise.resolve(10).then(func);
+ },
+
+ testPromiseWithDOMExceptionThrowingThenFunction() {
+ return this._win.Promise.resolve(5).then(() => {
+ throw new this._win.DOMException(
+ "We are a third DOMException",
+ "NetworkError"
+ );
+ });
+ },
+
+ testPromiseWithThrowingChromeThenable() {
+ var thenable = {
+ then() {
+ noSuchMethodExistsYo3();
+ },
+ };
+ return new this._win.Promise(function (resolve) {
+ resolve(thenable);
+ });
+ },
+
+ testPromiseWithThrowingContentThenable(thenable) {
+ // Waive Xrays on the thenable, because we're calling resolve() in the
+ // chrome compartment, so that's the compartment the "then" property get
+ // will happen in, and if we leave the Xray in place the function-valued
+ // property won't return the function.
+ return this._win.Promise.resolve(Cu.waiveXrays(thenable));
+ },
+
+ testPromiseWithDOMExceptionThrowingThenable() {
+ var thenable = {
+ then: () => {
+ throw new this._win.DOMException(
+ "We are a fourth DOMException",
+ "TypeMismatchError"
+ );
+ },
+ };
+ return new this._win.Promise(function (resolve) {
+ resolve(thenable);
+ });
+ },
+
+ get onsomething() {
+ return this.__DOM_IMPL__.getEventHandler("onsomething");
+ },
+
+ set onsomething(val) {
+ this.__DOM_IMPL__.setEventHandler("onsomething", val);
+ },
+};
diff --git a/dom/bindings/test/TestInterfaceLength.cpp b/dom/bindings/test/TestInterfaceLength.cpp
new file mode 100644
index 0000000000..cbb2ffa5c6
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceLength.cpp
@@ -0,0 +1,24 @@
+/* -*- 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/. */
+
+#include "mozilla/dom/TestInterfaceLength.h"
+#include "mozilla/dom/TestFunctionsBinding.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(TestInterfaceLength)
+
+JSObject* TestInterfaceLength::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return TestInterfaceLength_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<TestInterfaceLength> TestInterfaceLength::Constructor(
+ const GlobalObject& aGlobalObject, const bool aArg) {
+ return MakeAndAddRef<TestInterfaceLength>();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/test/TestInterfaceLength.h b/dom/bindings/test/TestInterfaceLength.h
new file mode 100644
index 0000000000..989333bbf0
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceLength.h
@@ -0,0 +1,39 @@
+/* -*- 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_TestInterfaceLength_h
+#define mozilla_dom_TestInterfaceLength_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+class TestInterfaceLength final : public nsWrapperCache {
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(TestInterfaceLength)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(TestInterfaceLength)
+
+ public:
+ TestInterfaceLength() = default;
+
+ static already_AddRefed<TestInterfaceLength> Constructor(
+ const GlobalObject& aGlobalObject, const bool aArg);
+
+ protected:
+ ~TestInterfaceLength() = default;
+
+ public:
+ nsISupports* GetParentObject() const { return nullptr; }
+ JSObject* WrapObject(JSContext*, JS::Handle<JSObject*> aGivenProto) override;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_TestInterfaceLength_h
diff --git a/dom/bindings/test/TestInterfaceMaplike.cpp b/dom/bindings/test/TestInterfaceMaplike.cpp
new file mode 100644
index 0000000000..ba8ccf7279
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceMaplike.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/BindingUtils.h"
+
+namespace mozilla::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(nsPIDOMWindowInner* aParent)
+ : mParent(aParent) {}
+
+// static
+already_AddRefed<TestInterfaceMaplike> TestInterfaceMaplike::Constructor(
+ const GlobalObject& aGlobal, ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<TestInterfaceMaplike> r = new TestInterfaceMaplike(window);
+ return r.forget();
+}
+
+JSObject* TestInterfaceMaplike::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return TestInterfaceMaplike_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner* TestInterfaceMaplike::GetParentObject() const {
+ return mParent;
+}
+
+void TestInterfaceMaplike::SetInternal(const nsAString& aKey, int32_t aValue) {
+ ErrorResult rv;
+ TestInterfaceMaplike_Binding::MaplikeHelpers::Set(this, aKey, aValue, rv);
+}
+
+void TestInterfaceMaplike::ClearInternal() {
+ ErrorResult rv;
+ TestInterfaceMaplike_Binding::MaplikeHelpers::Clear(this, rv);
+}
+
+bool TestInterfaceMaplike::DeleteInternal(const nsAString& aKey) {
+ ErrorResult rv;
+ return TestInterfaceMaplike_Binding::MaplikeHelpers::Delete(this, aKey, rv);
+}
+
+bool TestInterfaceMaplike::HasInternal(const nsAString& aKey) {
+ ErrorResult rv;
+ return TestInterfaceMaplike_Binding::MaplikeHelpers::Has(this, aKey, rv);
+}
+
+int32_t TestInterfaceMaplike::GetInternal(const nsAString& aKey,
+ ErrorResult& aRv) {
+ return TestInterfaceMaplike_Binding::MaplikeHelpers::Get(this, aKey, aRv);
+}
+} // namespace mozilla::dom
diff --git a/dom/bindings/test/TestInterfaceMaplike.h b/dom/bindings/test/TestInterfaceMaplike.h
new file mode 100644
index 0000000000..e82f290d17
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceMaplike.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 nsPIDOMWindowInner;
+
+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_WRAPPERCACHE_CLASS(TestInterfaceMaplike)
+
+ explicit TestInterfaceMaplike(nsPIDOMWindowInner* aParent);
+ nsPIDOMWindowInner* 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);
+ int32_t GetInternal(const nsAString& aKey, ErrorResult& aRv);
+
+ private:
+ virtual ~TestInterfaceMaplike() = default;
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TestInterfaceMaplike_h
diff --git a/dom/bindings/test/TestInterfaceMaplikeJSObject.cpp b/dom/bindings/test/TestInterfaceMaplikeJSObject.cpp
new file mode 100644
index 0000000000..e9fb50e4b6
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceMaplikeJSObject.cpp
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/TestInterfaceMaplikeJSObject.h"
+#include "mozilla/dom/TestInterfaceMaplike.h"
+#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/BindingUtils.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceMaplikeJSObject, mParent)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceMaplikeJSObject)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceMaplikeJSObject)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceMaplikeJSObject)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TestInterfaceMaplikeJSObject::TestInterfaceMaplikeJSObject(
+ nsPIDOMWindowInner* aParent)
+ : mParent(aParent) {}
+
+// static
+already_AddRefed<TestInterfaceMaplikeJSObject>
+TestInterfaceMaplikeJSObject::Constructor(const GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<TestInterfaceMaplikeJSObject> r =
+ new TestInterfaceMaplikeJSObject(window);
+ return r.forget();
+}
+
+JSObject* TestInterfaceMaplikeJSObject::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return TestInterfaceMaplikeJSObject_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner* TestInterfaceMaplikeJSObject::GetParentObject() const {
+ return mParent;
+}
+
+void TestInterfaceMaplikeJSObject::SetInternal(JSContext* aCx,
+ const nsAString& aKey,
+ JS::Handle<JSObject*> aObject) {
+ ErrorResult rv;
+ TestInterfaceMaplikeJSObject_Binding::MaplikeHelpers::Set(this, aKey, aObject,
+ rv);
+}
+
+void TestInterfaceMaplikeJSObject::ClearInternal() {
+ ErrorResult rv;
+ TestInterfaceMaplikeJSObject_Binding::MaplikeHelpers::Clear(this, rv);
+}
+
+bool TestInterfaceMaplikeJSObject::DeleteInternal(const nsAString& aKey) {
+ ErrorResult rv;
+ return TestInterfaceMaplikeJSObject_Binding::MaplikeHelpers::Delete(this,
+ aKey, rv);
+}
+
+bool TestInterfaceMaplikeJSObject::HasInternal(const nsAString& aKey) {
+ ErrorResult rv;
+ return TestInterfaceMaplikeJSObject_Binding::MaplikeHelpers::Has(this, aKey,
+ rv);
+}
+
+void TestInterfaceMaplikeJSObject::GetInternal(
+ JSContext* aCx, const nsAString& aKey, JS::MutableHandle<JSObject*> aRetVal,
+ ErrorResult& aRv) {
+ TestInterfaceMaplikeJSObject_Binding::MaplikeHelpers::Get(this, aCx, aKey,
+ aRetVal, aRv);
+}
+} // namespace mozilla::dom
diff --git a/dom/bindings/test/TestInterfaceMaplikeJSObject.h b/dom/bindings/test/TestInterfaceMaplikeJSObject.h
new file mode 100644
index 0000000000..00feda7796
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceMaplikeJSObject.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_TestInterfaceMaplikeJSObject_h
+#define mozilla_dom_TestInterfaceMaplikeJSObject_h
+
+#include "nsWrapperCache.h"
+#include "nsCOMPtr.h"
+
+class nsPIDOMWindowInner;
+
+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 TestInterfaceMaplikeJSObject final : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(TestInterfaceMaplikeJSObject)
+
+ explicit TestInterfaceMaplikeJSObject(nsPIDOMWindowInner* aParent);
+ nsPIDOMWindowInner* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ static already_AddRefed<TestInterfaceMaplikeJSObject> Constructor(
+ const GlobalObject& aGlobal, ErrorResult& rv);
+
+ // External access for testing internal convenience functions.
+ void SetInternal(JSContext* aCx, const nsAString& aKey,
+ JS::Handle<JSObject*> aObject);
+ void ClearInternal();
+ bool DeleteInternal(const nsAString& aKey);
+ bool HasInternal(const nsAString& aKey);
+ void GetInternal(JSContext* aCx, const nsAString& aKey,
+ JS::MutableHandle<JSObject*> aRetVal, ErrorResult& aRv);
+
+ private:
+ virtual ~TestInterfaceMaplikeJSObject() = default;
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TestInterfaceMaplikeJSObject_h
diff --git a/dom/bindings/test/TestInterfaceMaplikeObject.cpp b/dom/bindings/test/TestInterfaceMaplikeObject.cpp
new file mode 100644
index 0000000000..b05e33df6b
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceMaplikeObject.cpp
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/BindingUtils.h"
+
+namespace mozilla::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(
+ nsPIDOMWindowInner* aParent)
+ : mParent(aParent) {}
+
+// static
+already_AddRefed<TestInterfaceMaplikeObject>
+TestInterfaceMaplikeObject::Constructor(const GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<TestInterfaceMaplikeObject> r = new TestInterfaceMaplikeObject(window);
+ return r.forget();
+}
+
+JSObject* TestInterfaceMaplikeObject::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return TestInterfaceMaplikeObject_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner* TestInterfaceMaplikeObject::GetParentObject() const {
+ return mParent;
+}
+
+void TestInterfaceMaplikeObject::SetInternal(const nsAString& aKey) {
+ RefPtr<TestInterfaceMaplike> p(new TestInterfaceMaplike(mParent));
+ ErrorResult rv;
+ TestInterfaceMaplikeObject_Binding::MaplikeHelpers::Set(this, aKey, *p, rv);
+}
+
+void TestInterfaceMaplikeObject::ClearInternal() {
+ ErrorResult rv;
+ TestInterfaceMaplikeObject_Binding::MaplikeHelpers::Clear(this, rv);
+}
+
+bool TestInterfaceMaplikeObject::DeleteInternal(const nsAString& aKey) {
+ ErrorResult rv;
+ return TestInterfaceMaplikeObject_Binding::MaplikeHelpers::Delete(this, aKey,
+ rv);
+}
+
+bool TestInterfaceMaplikeObject::HasInternal(const nsAString& aKey) {
+ ErrorResult rv;
+ return TestInterfaceMaplikeObject_Binding::MaplikeHelpers::Has(this, aKey,
+ rv);
+}
+
+already_AddRefed<TestInterfaceMaplike> TestInterfaceMaplikeObject::GetInternal(
+ const nsAString& aKey, ErrorResult& aRv) {
+ return TestInterfaceMaplikeObject_Binding::MaplikeHelpers::Get(this, aKey,
+ aRv);
+}
+} // namespace mozilla::dom
diff --git a/dom/bindings/test/TestInterfaceMaplikeObject.h b/dom/bindings/test/TestInterfaceMaplikeObject.h
new file mode 100644
index 0000000000..894447cc41
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceMaplikeObject.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 nsPIDOMWindowInner;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+class TestInterfaceMaplike;
+
+// 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_WRAPPERCACHE_CLASS(TestInterfaceMaplikeObject)
+
+ explicit TestInterfaceMaplikeObject(nsPIDOMWindowInner* aParent);
+ nsPIDOMWindowInner* 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);
+ already_AddRefed<TestInterfaceMaplike> GetInternal(const nsAString& aKey,
+ ErrorResult& aRv);
+
+ private:
+ virtual ~TestInterfaceMaplikeObject() = default;
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TestInterfaceMaplikeObject_h
diff --git a/dom/bindings/test/TestInterfaceObservableArray.cpp b/dom/bindings/test/TestInterfaceObservableArray.cpp
new file mode 100644
index 0000000000..cb8d532eb8
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceObservableArray.cpp
@@ -0,0 +1,225 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/TestInterfaceObservableArray.h"
+#include "mozilla/dom/TestInterfaceObservableArrayBinding.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/BindingUtils.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceObservableArray, mParent,
+ mSetBooleanCallback,
+ mDeleteBooleanCallback,
+ mSetObjectCallback, mDeleteObjectCallback,
+ mSetInterfaceCallback,
+ mDeleteInterfaceCallback)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceObservableArray)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceObservableArray)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceObservableArray)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TestInterfaceObservableArray::TestInterfaceObservableArray(
+ nsPIDOMWindowInner* aParent, const ObservableArrayCallbacks& aCallbacks)
+ : mParent(aParent) {
+ if (aCallbacks.mSetBooleanCallback.WasPassed()) {
+ mSetBooleanCallback = &aCallbacks.mSetBooleanCallback.Value();
+ }
+ if (aCallbacks.mDeleteBooleanCallback.WasPassed()) {
+ mDeleteBooleanCallback = &aCallbacks.mDeleteBooleanCallback.Value();
+ }
+ if (aCallbacks.mSetObjectCallback.WasPassed()) {
+ mSetObjectCallback = &aCallbacks.mSetObjectCallback.Value();
+ }
+ if (aCallbacks.mDeleteObjectCallback.WasPassed()) {
+ mDeleteObjectCallback = &aCallbacks.mDeleteObjectCallback.Value();
+ }
+ if (aCallbacks.mSetInterfaceCallback.WasPassed()) {
+ mSetInterfaceCallback = &aCallbacks.mSetInterfaceCallback.Value();
+ }
+ if (aCallbacks.mDeleteInterfaceCallback.WasPassed()) {
+ mDeleteInterfaceCallback = &aCallbacks.mDeleteInterfaceCallback.Value();
+ }
+}
+
+// static
+already_AddRefed<TestInterfaceObservableArray>
+TestInterfaceObservableArray::Constructor(
+ const GlobalObject& aGlobal, const ObservableArrayCallbacks& aCallbacks,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<TestInterfaceObservableArray> r =
+ new TestInterfaceObservableArray(window, aCallbacks);
+ return r.forget();
+}
+
+JSObject* TestInterfaceObservableArray::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return TestInterfaceObservableArray_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner* TestInterfaceObservableArray::GetParentObject() const {
+ return mParent;
+}
+
+void TestInterfaceObservableArray::OnSetObservableArrayObject(
+ JSContext* aCx, JS::Handle<JSObject*> aValue, uint32_t aIndex,
+ ErrorResult& aRv) {
+ if (mSetObjectCallback) {
+ MOZ_KnownLive(mSetObjectCallback)
+ ->Call(aValue, aIndex, aRv, "OnSetObservableArrayObject",
+ CallbackFunction::eRethrowExceptions);
+ }
+}
+
+void TestInterfaceObservableArray::OnDeleteObservableArrayObject(
+ JSContext* aCx, JS::Handle<JSObject*> aValue, uint32_t aIndex,
+ ErrorResult& aRv) {
+ if (mDeleteObjectCallback) {
+ MOZ_KnownLive(mDeleteObjectCallback)
+ ->Call(aValue, aIndex, aRv, "OnDeleteObservableArrayObject",
+ CallbackFunction::eRethrowExceptions);
+ }
+}
+
+void TestInterfaceObservableArray::OnSetObservableArrayBoolean(
+ bool aValue, uint32_t aIndex, ErrorResult& aRv) {
+ if (mSetBooleanCallback) {
+ MOZ_KnownLive(mSetBooleanCallback)
+ ->Call(aValue, aIndex, aRv, "OnSetObservableArrayBoolean",
+ CallbackFunction::eRethrowExceptions);
+ }
+}
+
+void TestInterfaceObservableArray::OnDeleteObservableArrayBoolean(
+ bool aValue, uint32_t aIndex, ErrorResult& aRv) {
+ if (mDeleteBooleanCallback) {
+ MOZ_KnownLive(mDeleteBooleanCallback)
+ ->Call(aValue, aIndex, aRv, "OnDeleteObservableArrayBoolean",
+ CallbackFunction::eRethrowExceptions);
+ }
+}
+
+void TestInterfaceObservableArray::OnSetObservableArrayInterface(
+ TestInterfaceObservableArray* aValue, uint32_t aIndex, ErrorResult& aRv) {
+ if (mSetInterfaceCallback && aValue) {
+ MOZ_KnownLive(mSetInterfaceCallback)
+ ->Call(*aValue, aIndex, aRv, "OnSetObservableArrayInterface",
+ CallbackFunction::eRethrowExceptions);
+ }
+}
+
+void TestInterfaceObservableArray::OnDeleteObservableArrayInterface(
+ TestInterfaceObservableArray* aValue, uint32_t aIndex, ErrorResult& aRv) {
+ if (mDeleteInterfaceCallback && aValue) {
+ MOZ_KnownLive(mDeleteInterfaceCallback)
+ ->Call(*aValue, aIndex, aRv, "OnDeleteObservableArrayInterface",
+ CallbackFunction::eRethrowExceptions);
+ }
+}
+
+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
diff --git a/dom/bindings/test/TestInterfaceObservableArray.h b/dom/bindings/test/TestInterfaceObservableArray.h
new file mode 100644
index 0000000000..3329663ac6
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceObservableArray.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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_TestInterfaceObservableArray_h
+#define mozilla_dom_TestInterfaceObservableArray_h
+
+#include "nsCOMPtr.h"
+#include "nsWrapperCache.h"
+#include "nsTArray.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+class SetDeleteBooleanCallback;
+class SetDeleteInterfaceCallback;
+class SetDeleteObjectCallback;
+struct ObservableArrayCallbacks;
+
+// Implementation of test binding for webidl ObservableArray type, using
+// primitives for value type
+class TestInterfaceObservableArray final : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(TestInterfaceObservableArray)
+
+ nsPIDOMWindowInner* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ static already_AddRefed<TestInterfaceObservableArray> Constructor(
+ const GlobalObject& aGlobal, const ObservableArrayCallbacks& aCallbacks,
+ ErrorResult& rv);
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void OnSetObservableArrayObject(JSContext* aCx, JS::Handle<JSObject*> aValue,
+ uint32_t aIndex, ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void OnDeleteObservableArrayObject(JSContext* aCx,
+ JS::Handle<JSObject*> aValue,
+ uint32_t aIndex, ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void OnSetObservableArrayBoolean(bool aValue, uint32_t aIndex,
+ ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void OnDeleteObservableArrayBoolean(bool aValue, uint32_t aIndex,
+ ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void OnSetObservableArrayInterface(TestInterfaceObservableArray* aValue,
+ uint32_t aIndex, ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ 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);
+ virtual ~TestInterfaceObservableArray() = default;
+
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+ RefPtr<SetDeleteBooleanCallback> mSetBooleanCallback;
+ RefPtr<SetDeleteBooleanCallback> mDeleteBooleanCallback;
+ RefPtr<SetDeleteObjectCallback> mSetObjectCallback;
+ RefPtr<SetDeleteObjectCallback> mDeleteObjectCallback;
+ RefPtr<SetDeleteInterfaceCallback> mSetInterfaceCallback;
+ RefPtr<SetDeleteInterfaceCallback> mDeleteInterfaceCallback;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TestInterfaceObservableArray_h
diff --git a/dom/bindings/test/TestInterfaceSetlike.cpp b/dom/bindings/test/TestInterfaceSetlike.cpp
new file mode 100644
index 0000000000..ac973d1c23
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceSetlike.cpp
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/BindingUtils.h"
+
+namespace mozilla::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,
+ nsPIDOMWindowInner* aParent)
+ : mParent(aParent) {}
+
+// static
+already_AddRefed<TestInterfaceSetlike> TestInterfaceSetlike::Constructor(
+ const GlobalObject& aGlobal, ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<TestInterfaceSetlike> r = new TestInterfaceSetlike(nullptr, window);
+ return r.forget();
+}
+
+JSObject* TestInterfaceSetlike::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return TestInterfaceSetlike_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner* TestInterfaceSetlike::GetParentObject() const {
+ return mParent;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/test/TestInterfaceSetlike.h b/dom/bindings/test/TestInterfaceSetlike.h
new file mode 100644
index 0000000000..c99a0240ea
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceSetlike.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 nsPIDOMWindowInner;
+
+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_WRAPPERCACHE_CLASS(TestInterfaceSetlike)
+ explicit TestInterfaceSetlike(JSContext* aCx, nsPIDOMWindowInner* aParent);
+ nsPIDOMWindowInner* 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() = default;
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TestInterfaceSetlike_h
diff --git a/dom/bindings/test/TestInterfaceSetlikeNode.cpp b/dom/bindings/test/TestInterfaceSetlikeNode.cpp
new file mode 100644
index 0000000000..5d01c50b42
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceSetlikeNode.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/BindingUtils.h"
+
+namespace mozilla::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,
+ nsPIDOMWindowInner* aParent)
+ : mParent(aParent) {}
+
+// static
+already_AddRefed<TestInterfaceSetlikeNode>
+TestInterfaceSetlikeNode::Constructor(const GlobalObject& aGlobal,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<TestInterfaceSetlikeNode> r =
+ new TestInterfaceSetlikeNode(nullptr, window);
+ return r.forget();
+}
+
+JSObject* TestInterfaceSetlikeNode::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return TestInterfaceSetlikeNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner* TestInterfaceSetlikeNode::GetParentObject() const {
+ return mParent;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/test/TestInterfaceSetlikeNode.h b/dom/bindings/test/TestInterfaceSetlikeNode.h
new file mode 100644
index 0000000000..c4efdeab19
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceSetlikeNode.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 nsPIDOMWindowInner;
+
+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_WRAPPERCACHE_CLASS(TestInterfaceSetlikeNode)
+ explicit TestInterfaceSetlikeNode(JSContext* aCx,
+ nsPIDOMWindowInner* aParent);
+ nsPIDOMWindowInner* 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() = default;
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TestInterfaceSetlikeNode_h
diff --git a/dom/bindings/test/TestJSImplGen.webidl b/dom/bindings/test/TestJSImplGen.webidl
new file mode 100644
index 0000000000..7a1028dc6f
--- /dev/null
+++ b/dom/bindings/test/TestJSImplGen.webidl
@@ -0,0 +1,884 @@
+/* -*- 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/.
+ */
+
+typedef TestJSImplInterface AnotherNameForTestJSImplInterface;
+typedef TestJSImplInterface YetAnotherNameForTestJSImplInterface;
+typedef TestJSImplInterface? NullableTestJSImplInterface;
+
+callback MyTestCallback = undefined();
+
+enum MyTestEnum {
+ "a",
+ "b"
+};
+
+[Exposed=Window, JSImplementation="@mozilla.org/test-js-impl-interface;1"]
+interface TestJSImplInterface {
+ // We don't support multiple constructors (bug 869268) or named constructors
+ // for JS-implemented WebIDL.
+ [Throws]
+ constructor(DOMString str, unsigned long num, boolean? boolArg,
+ TestInterface? iface, long arg1,
+ DictForConstructor dict, any any1,
+ object obj1,
+ object? obj2, sequence<Dict> seq, optional any any2,
+ optional object obj3,
+ optional object? obj4,
+ Uint8Array typedArr,
+ ArrayBuffer arrayBuf);
+
+ // Integer types
+ // XXXbz add tests for throwing versions of all the integer stuff
+ readonly attribute byte readonlyByte;
+ attribute byte writableByte;
+ undefined passByte(byte arg);
+ byte receiveByte();
+ undefined passOptionalByte(optional byte arg);
+ undefined passOptionalByteBeforeRequired(optional byte arg1, byte arg2);
+ undefined passOptionalByteWithDefault(optional byte arg = 0);
+ undefined passOptionalByteWithDefaultBeforeRequired(optional byte arg1 = 0, byte arg2);
+ undefined passNullableByte(byte? arg);
+ undefined passOptionalNullableByte(optional byte? arg);
+ undefined passVariadicByte(byte... arg);
+ // [Cached] is not supported in JS-implemented WebIDL.
+ //[Cached, Pure]
+ //readonly attribute byte cachedByte;
+ //[Cached, Constant]
+ //readonly attribute byte cachedConstantByte;
+ //[Cached, Pure]
+ //attribute byte cachedWritableByte;
+ [Affects=Nothing]
+ attribute byte sideEffectFreeByte;
+ [Affects=Nothing, DependsOn=DOMState]
+ attribute byte domDependentByte;
+ [Affects=Nothing, DependsOn=Nothing]
+ readonly attribute byte constantByte;
+ [DependsOn=DeviceState, Affects=Nothing]
+ readonly attribute byte deviceStateDependentByte;
+ [Affects=Nothing]
+ byte returnByteSideEffectFree();
+ [Affects=Nothing, DependsOn=DOMState]
+ byte returnDOMDependentByte();
+ [Affects=Nothing, DependsOn=Nothing]
+ byte returnConstantByte();
+ [DependsOn=DeviceState, Affects=Nothing]
+ byte returnDeviceStateDependentByte();
+
+ readonly attribute short readonlyShort;
+ attribute short writableShort;
+ undefined passShort(short arg);
+ short receiveShort();
+ undefined passOptionalShort(optional short arg);
+ undefined passOptionalShortWithDefault(optional short arg = 5);
+
+ readonly attribute long readonlyLong;
+ attribute long writableLong;
+ undefined passLong(long arg);
+ long receiveLong();
+ undefined passOptionalLong(optional long arg);
+ undefined passOptionalLongWithDefault(optional long arg = 7);
+
+ readonly attribute long long readonlyLongLong;
+ attribute long long writableLongLong;
+ undefined passLongLong(long long arg);
+ long long receiveLongLong();
+ undefined passOptionalLongLong(optional long long arg);
+ undefined passOptionalLongLongWithDefault(optional long long arg = -12);
+
+ readonly attribute octet readonlyOctet;
+ attribute octet writableOctet;
+ undefined passOctet(octet arg);
+ octet receiveOctet();
+ undefined passOptionalOctet(optional octet arg);
+ undefined passOptionalOctetWithDefault(optional octet arg = 19);
+
+ readonly attribute unsigned short readonlyUnsignedShort;
+ attribute unsigned short writableUnsignedShort;
+ undefined passUnsignedShort(unsigned short arg);
+ unsigned short receiveUnsignedShort();
+ undefined passOptionalUnsignedShort(optional unsigned short arg);
+ undefined passOptionalUnsignedShortWithDefault(optional unsigned short arg = 2);
+
+ readonly attribute unsigned long readonlyUnsignedLong;
+ attribute unsigned long writableUnsignedLong;
+ undefined passUnsignedLong(unsigned long arg);
+ unsigned long receiveUnsignedLong();
+ undefined passOptionalUnsignedLong(optional unsigned long arg);
+ undefined passOptionalUnsignedLongWithDefault(optional unsigned long arg = 6);
+
+ readonly attribute unsigned long long readonlyUnsignedLongLong;
+ attribute unsigned long long writableUnsignedLongLong;
+ undefined passUnsignedLongLong(unsigned long long arg);
+ unsigned long long receiveUnsignedLongLong();
+ undefined passOptionalUnsignedLongLong(optional unsigned long long arg);
+ undefined passOptionalUnsignedLongLongWithDefault(optional unsigned long long arg = 17);
+
+ attribute float writableFloat;
+ attribute unrestricted float writableUnrestrictedFloat;
+ attribute float? writableNullableFloat;
+ attribute unrestricted float? writableNullableUnrestrictedFloat;
+ attribute double writableDouble;
+ attribute unrestricted double writableUnrestrictedDouble;
+ attribute double? writableNullableDouble;
+ attribute unrestricted double? writableNullableUnrestrictedDouble;
+ undefined passFloat(float arg1, unrestricted float arg2,
+ float? arg3, unrestricted float? arg4,
+ double arg5, unrestricted double arg6,
+ double? arg7, unrestricted double? arg8,
+ sequence<float> arg9, sequence<unrestricted float> arg10,
+ sequence<float?> arg11, sequence<unrestricted float?> arg12,
+ sequence<double> arg13, sequence<unrestricted double> arg14,
+ sequence<double?> arg15, sequence<unrestricted double?> arg16);
+ [LenientFloat]
+ undefined passLenientFloat(float arg1, unrestricted float arg2,
+ float? arg3, unrestricted float? arg4,
+ double arg5, unrestricted double arg6,
+ double? arg7, unrestricted double? arg8,
+ sequence<float> arg9,
+ sequence<unrestricted float> arg10,
+ sequence<float?> arg11,
+ sequence<unrestricted float?> arg12,
+ sequence<double> arg13,
+ sequence<unrestricted double> arg14,
+ sequence<double?> arg15,
+ sequence<unrestricted double?> arg16);
+ [LenientFloat]
+ attribute float lenientFloatAttr;
+ [LenientFloat]
+ attribute double lenientDoubleAttr;
+
+ // Castable interface types
+ // XXXbz add tests for throwing versions of all the castable interface stuff
+ TestJSImplInterface receiveSelf();
+ TestJSImplInterface? receiveNullableSelf();
+
+ TestJSImplInterface receiveWeakSelf();
+ TestJSImplInterface? receiveWeakNullableSelf();
+
+ // A version to test for casting to TestJSImplInterface&
+ undefined passSelf(TestJSImplInterface arg);
+ undefined passNullableSelf(TestJSImplInterface? arg);
+ attribute TestJSImplInterface nonNullSelf;
+ attribute TestJSImplInterface? nullableSelf;
+ // [Cached] is not supported in JS-implemented WebIDL.
+ //[Cached, Pure]
+ //readonly attribute TestJSImplInterface cachedSelf;
+ // Optional arguments
+ undefined passOptionalSelf(optional TestJSImplInterface? arg);
+ undefined passOptionalNonNullSelf(optional TestJSImplInterface arg);
+ undefined passOptionalSelfWithDefault(optional TestJSImplInterface? arg = null);
+
+ // Non-wrapper-cache interface types
+ [NewObject]
+ TestNonWrapperCacheInterface receiveNonWrapperCacheInterface();
+ [NewObject]
+ TestNonWrapperCacheInterface? receiveNullableNonWrapperCacheInterface();
+
+ [NewObject]
+ sequence<TestNonWrapperCacheInterface> receiveNonWrapperCacheInterfaceSequence();
+ [NewObject]
+ sequence<TestNonWrapperCacheInterface?> receiveNullableNonWrapperCacheInterfaceSequence();
+ [NewObject]
+ sequence<TestNonWrapperCacheInterface>? receiveNonWrapperCacheInterfaceNullableSequence();
+ [NewObject]
+ sequence<TestNonWrapperCacheInterface?>? receiveNullableNonWrapperCacheInterfaceNullableSequence();
+
+ // External interface types
+ TestExternalInterface receiveExternal();
+ TestExternalInterface? receiveNullableExternal();
+ TestExternalInterface receiveWeakExternal();
+ TestExternalInterface? receiveWeakNullableExternal();
+ undefined passExternal(TestExternalInterface arg);
+ undefined passNullableExternal(TestExternalInterface? arg);
+ attribute TestExternalInterface nonNullExternal;
+ attribute TestExternalInterface? nullableExternal;
+ // Optional arguments
+ undefined passOptionalExternal(optional TestExternalInterface? arg);
+ undefined passOptionalNonNullExternal(optional TestExternalInterface arg);
+ undefined passOptionalExternalWithDefault(optional TestExternalInterface? arg = null);
+
+ // Callback interface types
+ TestCallbackInterface receiveCallbackInterface();
+ TestCallbackInterface? receiveNullableCallbackInterface();
+ TestCallbackInterface receiveWeakCallbackInterface();
+ TestCallbackInterface? receiveWeakNullableCallbackInterface();
+ undefined passCallbackInterface(TestCallbackInterface arg);
+ undefined passNullableCallbackInterface(TestCallbackInterface? arg);
+ attribute TestCallbackInterface nonNullCallbackInterface;
+ attribute TestCallbackInterface? nullableCallbackInterface;
+ // Optional arguments
+ undefined passOptionalCallbackInterface(optional TestCallbackInterface? arg);
+ undefined passOptionalNonNullCallbackInterface(optional TestCallbackInterface arg);
+ undefined passOptionalCallbackInterfaceWithDefault(optional TestCallbackInterface? arg = null);
+
+ // Sequence types
+ // [Cached] is not supported in JS-implemented WebIDL.
+ //[Cached, Pure]
+ //readonly attribute sequence<long> readonlySequence;
+ //[Cached, Pure]
+ //readonly attribute sequence<Dict> readonlySequenceOfDictionaries;
+ //[Cached, Pure]
+ //readonly attribute sequence<Dict>? readonlyNullableSequenceOfDictionaries;
+ //[Cached, Pure, Frozen]
+ //readonly attribute sequence<long> readonlyFrozenSequence;
+ //[Cached, Pure, Frozen]
+ //readonly attribute sequence<long>? readonlyFrozenNullableSequence;
+ sequence<long> receiveSequence();
+ sequence<long>? receiveNullableSequence();
+ sequence<long?> receiveSequenceOfNullableInts();
+ sequence<long?>? receiveNullableSequenceOfNullableInts();
+ undefined passSequence(sequence<long> arg);
+ undefined passNullableSequence(sequence<long>? arg);
+ undefined passSequenceOfNullableInts(sequence<long?> arg);
+ undefined passOptionalSequenceOfNullableInts(optional sequence<long?> arg);
+ undefined passOptionalNullableSequenceOfNullableInts(optional sequence<long?>? arg);
+ sequence<TestJSImplInterface> receiveCastableObjectSequence();
+ sequence<TestCallbackInterface> receiveCallbackObjectSequence();
+ sequence<TestJSImplInterface?> receiveNullableCastableObjectSequence();
+ sequence<TestCallbackInterface?> receiveNullableCallbackObjectSequence();
+ sequence<TestJSImplInterface>? receiveCastableObjectNullableSequence();
+ sequence<TestJSImplInterface?>? receiveNullableCastableObjectNullableSequence();
+ sequence<TestJSImplInterface> receiveWeakCastableObjectSequence();
+ sequence<TestJSImplInterface?> receiveWeakNullableCastableObjectSequence();
+ sequence<TestJSImplInterface>? receiveWeakCastableObjectNullableSequence();
+ sequence<TestJSImplInterface?>? receiveWeakNullableCastableObjectNullableSequence();
+ undefined passCastableObjectSequence(sequence<TestJSImplInterface> arg);
+ undefined passNullableCastableObjectSequence(sequence<TestJSImplInterface?> arg);
+ undefined passCastableObjectNullableSequence(sequence<TestJSImplInterface>? arg);
+ undefined passNullableCastableObjectNullableSequence(sequence<TestJSImplInterface?>? arg);
+ undefined passOptionalSequence(optional sequence<long> arg);
+ undefined passOptionalSequenceWithDefaultValue(optional sequence<long> arg = []);
+ undefined passOptionalNullableSequence(optional sequence<long>? arg);
+ undefined passOptionalNullableSequenceWithDefaultValue(optional sequence<long>? arg = null);
+ undefined passOptionalNullableSequenceWithDefaultValue2(optional sequence<long>? arg = []);
+ undefined passOptionalObjectSequence(optional sequence<TestJSImplInterface> arg);
+ undefined passExternalInterfaceSequence(sequence<TestExternalInterface> arg);
+ undefined passNullableExternalInterfaceSequence(sequence<TestExternalInterface?> arg);
+
+ sequence<DOMString> receiveStringSequence();
+ sequence<ByteString> receiveByteStringSequence();
+ sequence<UTF8String> receiveUTF8StringSequence();
+ // Callback interface problem. See bug 843261.
+ //undefined passStringSequence(sequence<DOMString> arg);
+ sequence<any> receiveAnySequence();
+ sequence<any>? receiveNullableAnySequence();
+ //XXXbz No support for sequence of sequence return values yet.
+ //sequence<sequence<any>> receiveAnySequenceSequence();
+
+ sequence<object> receiveObjectSequence();
+ sequence<object?> receiveNullableObjectSequence();
+
+ undefined passSequenceOfSequences(sequence<sequence<long>> arg);
+ undefined passSequenceOfSequencesOfSequences(sequence<sequence<sequence<long>>> arg);
+ //XXXbz No support for sequence of sequence return values yet.
+ //sequence<sequence<long>> receiveSequenceOfSequences();
+
+ // record types
+ undefined passRecord(record<DOMString, long> arg);
+ undefined passNullableRecord(record<DOMString, long>? arg);
+ undefined passRecordOfNullableInts(record<DOMString, long?> arg);
+ undefined passOptionalRecordOfNullableInts(optional record<DOMString, long?> arg);
+ undefined passOptionalNullableRecordOfNullableInts(optional record<DOMString, long?>? arg);
+ undefined passCastableObjectRecord(record<DOMString, TestInterface> arg);
+ undefined passNullableCastableObjectRecord(record<DOMString, TestInterface?> arg);
+ undefined passCastableObjectNullableRecord(record<DOMString, TestInterface>? arg);
+ undefined passNullableCastableObjectNullableRecord(record<DOMString, TestInterface?>? arg);
+ undefined passOptionalRecord(optional record<DOMString, long> arg);
+ undefined passOptionalNullableRecord(optional record<DOMString, long>? arg);
+ undefined passOptionalNullableRecordWithDefaultValue(optional record<DOMString, long>? arg = null);
+ undefined passOptionalObjectRecord(optional record<DOMString, TestInterface> arg);
+ undefined passExternalInterfaceRecord(record<DOMString, TestExternalInterface> arg);
+ undefined passNullableExternalInterfaceRecord(record<DOMString, TestExternalInterface?> arg);
+ undefined passStringRecord(record<DOMString, DOMString> arg);
+ undefined passByteStringRecord(record<DOMString, ByteString> arg);
+ undefined passUTF8StringRecord(record<DOMString, UTF8String> arg);
+ undefined passRecordOfRecords(record<DOMString, record<DOMString, long>> arg);
+ record<DOMString, long> receiveRecord();
+ record<DOMString, long>? receiveNullableRecord();
+ record<DOMString, long?> receiveRecordOfNullableInts();
+ record<DOMString, long?>? receiveNullableRecordOfNullableInts();
+ //XXXbz No support for record of records return values yet.
+ //record<DOMString, record<DOMString, long>> receiveRecordOfRecords();
+ record<DOMString, any> receiveAnyRecord();
+
+ // Typed array types
+ undefined passArrayBuffer(ArrayBuffer arg);
+ undefined passNullableArrayBuffer(ArrayBuffer? arg);
+ undefined passOptionalArrayBuffer(optional ArrayBuffer arg);
+ undefined passOptionalNullableArrayBuffer(optional ArrayBuffer? arg);
+ undefined passOptionalNullableArrayBufferWithDefaultValue(optional ArrayBuffer? arg= null);
+ undefined passArrayBufferView(ArrayBufferView arg);
+ undefined passInt8Array(Int8Array arg);
+ undefined passInt16Array(Int16Array arg);
+ undefined passInt32Array(Int32Array arg);
+ undefined passUint8Array(Uint8Array arg);
+ undefined passUint16Array(Uint16Array arg);
+ undefined passUint32Array(Uint32Array arg);
+ undefined passUint8ClampedArray(Uint8ClampedArray arg);
+ undefined passFloat32Array(Float32Array arg);
+ undefined passFloat64Array(Float64Array arg);
+ undefined passSequenceOfArrayBuffers(sequence<ArrayBuffer> arg);
+ undefined passSequenceOfNullableArrayBuffers(sequence<ArrayBuffer?> arg);
+ undefined passRecordOfArrayBuffers(record<DOMString, ArrayBuffer> arg);
+ undefined passRecordOfNullableArrayBuffers(record<DOMString, ArrayBuffer?> arg);
+ undefined passVariadicTypedArray(Float32Array... arg);
+ undefined passVariadicNullableTypedArray(Float32Array?... arg);
+ Uint8Array receiveUint8Array();
+ attribute Uint8Array uint8ArrayAttr;
+
+ // DOMString types
+ undefined passString(DOMString arg);
+ undefined passNullableString(DOMString? arg);
+ undefined passOptionalString(optional DOMString arg);
+ undefined passOptionalStringWithDefaultValue(optional DOMString arg = "abc");
+ undefined passOptionalNullableString(optional DOMString? arg);
+ undefined passOptionalNullableStringWithDefaultValue(optional DOMString? arg = null);
+ undefined passVariadicString(DOMString... arg);
+
+ // ByteString types
+ undefined passByteString(ByteString arg);
+ undefined passNullableByteString(ByteString? arg);
+ undefined passOptionalByteString(optional ByteString arg);
+ undefined passOptionalByteStringWithDefaultValue(optional ByteString arg = "abc");
+ undefined passOptionalNullableByteString(optional ByteString? arg);
+ undefined passOptionalNullableByteStringWithDefaultValue(optional ByteString? arg = null);
+ undefined passVariadicByteString(ByteString... arg);
+ undefined passUnionByteString((ByteString or long) arg);
+ undefined passOptionalUnionByteString(optional (ByteString or long) arg);
+ undefined passOptionalUnionByteStringWithDefaultValue(optional (ByteString or long) arg = "abc");
+
+ // UTF8String types
+ undefined passUTF8String(UTF8String arg);
+ undefined passNullableUTF8String(UTF8String? arg);
+ undefined passOptionalUTF8String(optional UTF8String arg);
+ undefined passOptionalUTF8StringWithDefaultValue(optional UTF8String arg = "abc");
+ undefined passOptionalNullableUTF8String(optional UTF8String? arg);
+ undefined passOptionalNullableUTF8StringWithDefaultValue(optional UTF8String? arg = null);
+ undefined passVariadicUTF8String(UTF8String... arg);
+ undefined passUnionUTF8String((UTF8String or long) arg);
+ undefined passOptionalUnionUTF8String(optional (UTF8String or long) arg);
+ undefined passOptionalUnionUTF8StringWithDefaultValue(optional (UTF8String or long) arg = "abc");
+
+ // USVString types
+ undefined passSVS(USVString arg);
+ undefined passNullableSVS(USVString? arg);
+ undefined passOptionalSVS(optional USVString arg);
+ undefined passOptionalSVSWithDefaultValue(optional USVString arg = "abc");
+ undefined passOptionalNullableSVS(optional USVString? arg);
+ undefined passOptionalNullableSVSWithDefaultValue(optional USVString? arg = null);
+ undefined passVariadicSVS(USVString... arg);
+ USVString receiveSVS();
+
+ // JSString types
+ undefined passJSString(JSString arg);
+ // undefined passNullableJSString(JSString? arg); // NOT SUPPORTED YET
+ // undefined passOptionalJSString(optional JSString arg); // NOT SUPPORTED YET
+ undefined passOptionalJSStringWithDefaultValue(optional JSString arg = "abc");
+ // undefined passOptionalNullableJSString(optional JSString? arg); // NOT SUPPORTED YET
+ // undefined passOptionalNullableJSStringWithDefaultValue(optional JSString? arg = null); // NOT SUPPORTED YET
+ // undefined passVariadicJSString(JSString... arg); // NOT SUPPORTED YET
+ // undefined passRecordOfJSString(record<DOMString, JSString> arg); // NOT SUPPORTED YET
+ // undefined passSequenceOfJSString(sequence<JSString> arg); // NOT SUPPORTED YET
+ // undefined passUnionJSString((JSString or long) arg); // NOT SUPPORTED YET
+ JSString receiveJSString();
+ // sequence<JSString> receiveJSStringSequence(); // NOT SUPPORTED YET
+ // (JSString or long) receiveJSStringUnion(); // NOT SUPPORTED YET
+ // record<DOMString, JSString> receiveJSStringRecord(); // NOT SUPPORTED YET
+ readonly attribute JSString readonlyJSStringAttr;
+ attribute JSString jsStringAttr;
+
+ // Enumerated types
+ undefined passEnum(MyTestEnum arg);
+ undefined passNullableEnum(MyTestEnum? arg);
+ undefined passOptionalEnum(optional MyTestEnum arg);
+ undefined passEnumWithDefault(optional MyTestEnum arg = "a");
+ undefined passOptionalNullableEnum(optional MyTestEnum? arg);
+ undefined passOptionalNullableEnumWithDefaultValue(optional MyTestEnum? arg = null);
+ undefined passOptionalNullableEnumWithDefaultValue2(optional MyTestEnum? arg = "a");
+ MyTestEnum receiveEnum();
+ MyTestEnum? receiveNullableEnum();
+ attribute MyTestEnum enumAttribute;
+ readonly attribute MyTestEnum readonlyEnumAttribute;
+
+ // Callback types
+ undefined passCallback(MyTestCallback arg);
+ undefined passNullableCallback(MyTestCallback? arg);
+ undefined passOptionalCallback(optional MyTestCallback arg);
+ undefined passOptionalNullableCallback(optional MyTestCallback? arg);
+ undefined passOptionalNullableCallbackWithDefaultValue(optional MyTestCallback? arg = null);
+ MyTestCallback receiveCallback();
+ MyTestCallback? receiveNullableCallback();
+ // Hmm. These two don't work, I think because I need a locally modified version of TestTreatAsNullCallback.
+ //undefined passNullableTreatAsNullCallback(TestTreatAsNullCallback? arg);
+ //undefined passOptionalNullableTreatAsNullCallback(optional TestTreatAsNullCallback? arg);
+ undefined passOptionalNullableTreatAsNullCallbackWithDefaultValue(optional TestTreatAsNullCallback? arg = null);
+
+ // Any types
+ undefined passAny(any arg);
+ undefined passVariadicAny(any... arg);
+ undefined passOptionalAny(optional any arg);
+ undefined passAnyDefaultNull(optional any arg = null);
+ undefined passSequenceOfAny(sequence<any> arg);
+ undefined passNullableSequenceOfAny(sequence<any>? arg);
+ undefined passOptionalSequenceOfAny(optional sequence<any> arg);
+ undefined passOptionalNullableSequenceOfAny(optional sequence<any>? arg);
+ undefined passOptionalSequenceOfAnyWithDefaultValue(optional sequence<any>? arg = null);
+ undefined passSequenceOfSequenceOfAny(sequence<sequence<any>> arg);
+ undefined passSequenceOfNullableSequenceOfAny(sequence<sequence<any>?> arg);
+ undefined passNullableSequenceOfNullableSequenceOfAny(sequence<sequence<any>?>? arg);
+ undefined passOptionalNullableSequenceOfNullableSequenceOfAny(optional sequence<sequence<any>?>? arg);
+ undefined passRecordOfAny(record<DOMString, any> arg);
+ undefined passNullableRecordOfAny(record<DOMString, any>? arg);
+ undefined passOptionalRecordOfAny(optional record<DOMString, any> arg);
+ undefined passOptionalNullableRecordOfAny(optional record<DOMString, any>? arg);
+ undefined passOptionalRecordOfAnyWithDefaultValue(optional record<DOMString, any>? arg = null);
+ undefined passRecordOfRecordOfAny(record<DOMString, record<DOMString, any>> arg);
+ undefined passRecordOfNullableRecordOfAny(record<DOMString, record<DOMString, any>?> arg);
+ undefined passNullableRecordOfNullableRecordOfAny(record<DOMString, record<DOMString, any>?>? arg);
+ undefined passOptionalNullableRecordOfNullableRecordOfAny(optional record<DOMString, record<DOMString, any>?>? arg);
+ undefined passOptionalNullableRecordOfNullableSequenceOfAny(optional record<DOMString, sequence<any>?>? arg);
+ undefined passOptionalNullableSequenceOfNullableRecordOfAny(optional sequence<record<DOMString, any>?>? arg);
+ any receiveAny();
+
+ // object types
+ undefined passObject(object arg);
+ undefined passVariadicObject(object... arg);
+ undefined passNullableObject(object? arg);
+ undefined passVariadicNullableObject(object... arg);
+ undefined passOptionalObject(optional object arg);
+ undefined passOptionalNullableObject(optional object? arg);
+ undefined passOptionalNullableObjectWithDefaultValue(optional object? arg = null);
+ undefined passSequenceOfObject(sequence<object> arg);
+ undefined passSequenceOfNullableObject(sequence<object?> arg);
+ undefined passNullableSequenceOfObject(sequence<object>? arg);
+ undefined passOptionalNullableSequenceOfNullableSequenceOfObject(optional sequence<sequence<object>?>? arg);
+ undefined passOptionalNullableSequenceOfNullableSequenceOfNullableObject(optional sequence<sequence<object?>?>? arg);
+ undefined passRecordOfObject(record<DOMString, object> arg);
+ object receiveObject();
+ object? receiveNullableObject();
+
+ // Union types
+ undefined passUnion((object or long) arg);
+ // Some union tests are debug-only to aundefined creating all those
+ // unused union types in opt builds.
+#ifdef DEBUG
+ undefined passUnion2((long or boolean) arg);
+ undefined passUnion3((object or long or boolean) arg);
+ undefined passUnion4((Node or long or boolean) arg);
+ undefined passUnion5((object or boolean) arg);
+ undefined passUnion6((object or DOMString) arg);
+ undefined passUnion7((object or DOMString or long) arg);
+ undefined passUnion8((object or DOMString or boolean) arg);
+ undefined passUnion9((object or DOMString or long or boolean) arg);
+ undefined passUnion10(optional (EventInit or long) arg = {});
+ undefined passUnion11(optional (CustomEventInit or long) arg = {});
+ undefined passUnion12(optional (EventInit or long) arg = 5);
+ undefined passUnion13(optional (object or long?) arg = null);
+ undefined passUnion14(optional (object or long?) arg = 5);
+ undefined passUnion15((sequence<long> or long) arg);
+ undefined passUnion16(optional (sequence<long> or long) arg);
+ undefined passUnion17(optional (sequence<long>? or long) arg = 5);
+ undefined passUnion18((sequence<object> or long) arg);
+ undefined passUnion19(optional (sequence<object> or long) arg);
+ undefined passUnion20(optional (sequence<object> or long) arg = []);
+ undefined passUnion21((record<DOMString, long> or long) arg);
+ undefined passUnion22((record<DOMString, object> or long) arg);
+ undefined passUnion23((sequence<ImageData> or long) arg);
+ undefined passUnion24((sequence<ImageData?> or long) arg);
+ undefined passUnion25((sequence<sequence<ImageData>> or long) arg);
+ undefined passUnion26((sequence<sequence<ImageData?>> or long) arg);
+ undefined passUnion27(optional (sequence<DOMString> or EventInit) arg = {});
+ undefined passUnion28(optional (EventInit or sequence<DOMString>) arg = {});
+ undefined passUnionWithCallback((EventHandler or long) arg);
+ undefined passUnionWithByteString((ByteString or long) arg);
+ undefined passUnionWithUTF8String((UTF8String or long) arg);
+ undefined passUnionWithRecord((record<DOMString, DOMString> or DOMString) arg);
+ undefined passUnionWithRecordAndSequence((record<DOMString, DOMString> or sequence<DOMString>) arg);
+ undefined passUnionWithSequenceAndRecord((sequence<DOMString> or record<DOMString, DOMString>) arg);
+ undefined passUnionWithSVS((USVString or long) arg);
+#endif
+ undefined passUnionWithNullable((object? or long) arg);
+ undefined passNullableUnion((object or long)? arg);
+ undefined passOptionalUnion(optional (object or long) arg);
+ undefined passOptionalNullableUnion(optional (object or long)? arg);
+ undefined passOptionalNullableUnionWithDefaultValue(optional (object or long)? arg = null);
+ //undefined passUnionWithInterfaces((TestJSImplInterface or TestExternalInterface) arg);
+ //undefined passUnionWithInterfacesAndNullable((TestJSImplInterface? or TestExternalInterface) arg);
+ //undefined passUnionWithSequence((sequence<object> or long) arg);
+ undefined passUnionWithArrayBuffer((ArrayBuffer or long) arg);
+ undefined passUnionWithString((DOMString or object) arg);
+ // Using an enum in a union. Note that we use some enum not declared in our
+ // binding file, because UnionTypes.h will need to include the binding header
+ // for this enum. Pick an enum from an interface that won't drag in too much
+ // stuff.
+ undefined passUnionWithEnum((SupportedType or object) arg);
+
+ // Trying to use a callback in a union won't include the test
+ // headers, unfortunately, so won't compile.
+ // undefined passUnionWithCallback((MyTestCallback or long) arg);
+ undefined passUnionWithObject((object or long) arg);
+ //undefined passUnionWithDict((Dict or long) arg);
+
+ undefined passUnionWithDefaultValue1(optional (double or DOMString) arg = "");
+ undefined passUnionWithDefaultValue2(optional (double or DOMString) arg = 1);
+ undefined passUnionWithDefaultValue3(optional (double or DOMString) arg = 1.5);
+ undefined passUnionWithDefaultValue4(optional (float or DOMString) arg = "");
+ undefined passUnionWithDefaultValue5(optional (float or DOMString) arg = 1);
+ undefined passUnionWithDefaultValue6(optional (float or DOMString) arg = 1.5);
+ undefined passUnionWithDefaultValue7(optional (unrestricted double or DOMString) arg = "");
+ undefined passUnionWithDefaultValue8(optional (unrestricted double or DOMString) arg = 1);
+ undefined passUnionWithDefaultValue9(optional (unrestricted double or DOMString) arg = 1.5);
+ undefined passUnionWithDefaultValue10(optional (unrestricted double or DOMString) arg = Infinity);
+ undefined passUnionWithDefaultValue11(optional (unrestricted float or DOMString) arg = "");
+ undefined passUnionWithDefaultValue12(optional (unrestricted float or DOMString) arg = 1);
+ undefined passUnionWithDefaultValue13(optional (unrestricted float or DOMString) arg = Infinity);
+ undefined passUnionWithDefaultValue14(optional (double or ByteString) arg = "");
+ undefined passUnionWithDefaultValue15(optional (double or ByteString) arg = 1);
+ undefined passUnionWithDefaultValue16(optional (double or ByteString) arg = 1.5);
+ undefined passUnionWithDefaultValue17(optional (double or SupportedType) arg = "text/html");
+ undefined passUnionWithDefaultValue18(optional (double or SupportedType) arg = 1);
+ undefined passUnionWithDefaultValue19(optional (double or SupportedType) arg = 1.5);
+ undefined passUnionWithDefaultValue20(optional (double or USVString) arg = "abc");
+ undefined passUnionWithDefaultValue21(optional (double or USVString) arg = 1);
+ undefined passUnionWithDefaultValue22(optional (double or USVString) arg = 1.5);
+ undefined passUnionWithDefaultValue23(optional (double or UTF8String) arg = "");
+ undefined passUnionWithDefaultValue24(optional (double or UTF8String) arg = 1);
+ undefined passUnionWithDefaultValue25(optional (double or UTF8String) arg = 1.5);
+
+ undefined passNullableUnionWithDefaultValue1(optional (double or DOMString)? arg = "");
+ undefined passNullableUnionWithDefaultValue2(optional (double or DOMString)? arg = 1);
+ undefined passNullableUnionWithDefaultValue3(optional (double or DOMString)? arg = null);
+ undefined passNullableUnionWithDefaultValue4(optional (float or DOMString)? arg = "");
+ undefined passNullableUnionWithDefaultValue5(optional (float or DOMString)? arg = 1);
+ undefined passNullableUnionWithDefaultValue6(optional (float or DOMString)? arg = null);
+ undefined passNullableUnionWithDefaultValue7(optional (unrestricted double or DOMString)? arg = "");
+ undefined passNullableUnionWithDefaultValue8(optional (unrestricted double or DOMString)? arg = 1);
+ undefined passNullableUnionWithDefaultValue9(optional (unrestricted double or DOMString)? arg = null);
+ undefined passNullableUnionWithDefaultValue10(optional (unrestricted float or DOMString)? arg = "");
+ undefined passNullableUnionWithDefaultValue11(optional (unrestricted float or DOMString)? arg = 1);
+ undefined passNullableUnionWithDefaultValue12(optional (unrestricted float or DOMString)? arg = null);
+ undefined passNullableUnionWithDefaultValue13(optional (double or ByteString)? arg = "");
+ undefined passNullableUnionWithDefaultValue14(optional (double or ByteString)? arg = 1);
+ undefined passNullableUnionWithDefaultValue15(optional (double or ByteString)? arg = 1.5);
+ undefined passNullableUnionWithDefaultValue16(optional (double or ByteString)? arg = null);
+ undefined passNullableUnionWithDefaultValue17(optional (double or SupportedType)? arg = "text/html");
+ undefined passNullableUnionWithDefaultValue18(optional (double or SupportedType)? arg = 1);
+ undefined passNullableUnionWithDefaultValue19(optional (double or SupportedType)? arg = 1.5);
+ undefined passNullableUnionWithDefaultValue20(optional (double or SupportedType)? arg = null);
+ undefined passNullableUnionWithDefaultValue21(optional (double or USVString)? arg = "abc");
+ undefined passNullableUnionWithDefaultValue22(optional (double or USVString)? arg = 1);
+ undefined passNullableUnionWithDefaultValue23(optional (double or USVString)? arg = 1.5);
+ undefined passNullableUnionWithDefaultValue24(optional (double or USVString)? arg = null);
+ undefined passNullableUnionWithDefaultValue25(optional (double or UTF8String)? arg = "");
+ undefined passNullableUnionWithDefaultValue26(optional (double or UTF8String)? arg = 1);
+ undefined passNullableUnionWithDefaultValue27(optional (double or UTF8String)? arg = 1.5);
+ undefined passNullableUnionWithDefaultValue28(optional (double or UTF8String)? arg = null);
+
+ undefined passSequenceOfUnions(sequence<(CanvasPattern or CanvasGradient)> arg);
+ undefined passSequenceOfUnions2(sequence<(object or long)> arg);
+ undefined passVariadicUnion((CanvasPattern or CanvasGradient)... arg);
+
+ undefined passSequenceOfNullableUnions(sequence<(CanvasPattern or CanvasGradient)?> arg);
+ undefined passVariadicNullableUnion((CanvasPattern or CanvasGradient)?... arg);
+ undefined passRecordOfUnions(record<DOMString, (CanvasPattern or CanvasGradient)> arg);
+ // XXXbz no move constructor on some unions
+ // undefined passRecordOfUnions2(record<DOMString, (object or long)> arg);
+
+ (CanvasPattern or CanvasGradient) receiveUnion();
+ (object or long) receiveUnion2();
+ (CanvasPattern? or CanvasGradient) receiveUnionContainingNull();
+ (CanvasPattern or CanvasGradient)? receiveNullableUnion();
+ (object or long)? receiveNullableUnion2();
+
+ attribute (CanvasPattern or CanvasGradient) writableUnion;
+ attribute (CanvasPattern? or CanvasGradient) writableUnionContainingNull;
+ attribute (CanvasPattern or CanvasGradient)? writableNullableUnion;
+
+ // Promise types
+ undefined passPromise(Promise<any> arg);
+ undefined passOptionalPromise(optional Promise<any> arg);
+ undefined passPromiseSequence(sequence<Promise<any>> arg);
+ Promise<any> receivePromise();
+ Promise<any> receiveAddrefedPromise();
+
+ // binaryNames tests
+ [BinaryName="methodRenamedTo"]
+ undefined methodRenamedFrom();
+ [BinaryName="methodRenamedTo"]
+ undefined methodRenamedFrom(byte argument);
+ [BinaryName="attributeGetterRenamedTo"]
+ readonly attribute byte attributeGetterRenamedFrom;
+ [BinaryName="attributeRenamedTo"]
+ attribute byte attributeRenamedFrom;
+
+ undefined passDictionary(optional Dict x = {});
+ undefined passDictionary2(Dict x);
+ // [Cached] is not supported in JS-implemented WebIDL.
+ //[Cached, Pure]
+ //readonly attribute Dict readonlyDictionary;
+ //[Cached, Pure]
+ //readonly attribute Dict? readonlyNullableDictionary;
+ //[Cached, Pure]
+ //attribute Dict writableDictionary;
+ //[Cached, Pure, Frozen]
+ //readonly attribute Dict readonlyFrozenDictionary;
+ //[Cached, Pure, Frozen]
+ //readonly attribute Dict? readonlyFrozenNullableDictionary;
+ //[Cached, Pure, Frozen]
+ //attribute Dict writableFrozenDictionary;
+ Dict receiveDictionary();
+ Dict? receiveNullableDictionary();
+ undefined passOtherDictionary(optional GrandparentDict x = {});
+ undefined passSequenceOfDictionaries(sequence<Dict> x);
+ undefined passRecordOfDictionaries(record<DOMString, GrandparentDict> x);
+ // No support for nullable dictionaries inside a sequence (nor should there be)
+ // undefined passSequenceOfNullableDictionaries(sequence<Dict?> x);
+ undefined passDictionaryOrLong(optional Dict x = {});
+ undefined passDictionaryOrLong(long x);
+
+ undefined passDictContainingDict(optional DictContainingDict arg = {});
+ undefined passDictContainingSequence(optional DictContainingSequence arg = {});
+ DictContainingSequence receiveDictContainingSequence();
+ undefined passVariadicDictionary(Dict... arg);
+
+ // EnforceRange/Clamp tests
+ undefined dontEnforceRangeOrClamp(byte arg);
+ undefined doEnforceRange([EnforceRange] byte arg);
+ undefined doEnforceRangeNullable([EnforceRange] byte? arg);
+ undefined doClamp([Clamp] byte arg);
+ undefined doClampNullable([Clamp] byte? arg);
+ attribute [EnforceRange] byte enforcedByte;
+ attribute [EnforceRange] byte? enforcedByteNullable;
+ attribute [Clamp] byte clampedByte;
+ attribute [Clamp] byte? clampedByteNullable;
+
+ // Typedefs
+ const myLong myLongConstant = 5;
+ undefined exerciseTypedefInterfaces1(AnotherNameForTestJSImplInterface arg);
+ AnotherNameForTestJSImplInterface exerciseTypedefInterfaces2(NullableTestJSImplInterface arg);
+ undefined exerciseTypedefInterfaces3(YetAnotherNameForTestJSImplInterface arg);
+
+ // Deprecated methods and attributes
+ [Deprecated="Components"]
+ attribute byte deprecatedAttribute;
+ [Deprecated="Components"]
+ byte deprecatedMethod();
+ [Deprecated="Components"]
+ undefined deprecatedMethodWithContext(any arg);
+
+ // Static methods and attributes
+ // FIXME: Bug 863952 Static things are not supported yet
+ /*
+ static attribute boolean staticAttribute;
+ static undefined staticMethod(boolean arg);
+ static undefined staticMethodWithContext(any arg);
+
+ // Deprecated static methods and attributes
+ [Deprecated="Components"]
+ static attribute byte staticDeprecatedAttribute;
+ [Deprecated="Components"]
+ static byte staticDeprecatedMethod();
+ [Deprecated="Components"]
+ static byte staticDeprecatedMethodWithContext();
+ */
+
+ // Overload resolution tests
+ //undefined overload1(DOMString... strs);
+ boolean overload1(TestJSImplInterface arg);
+ TestJSImplInterface overload1(DOMString strs, TestJSImplInterface arg);
+ undefined overload2(TestJSImplInterface arg);
+ undefined overload2(optional Dict arg = {});
+ undefined overload2(boolean arg);
+ undefined overload2(DOMString arg);
+ undefined overload3(TestJSImplInterface arg);
+ undefined overload3(MyTestCallback arg);
+ undefined overload3(boolean arg);
+ undefined overload4(TestJSImplInterface arg);
+ undefined overload4(TestCallbackInterface arg);
+ undefined overload4(DOMString arg);
+ undefined overload5(long arg);
+ undefined overload5(MyTestEnum arg);
+ undefined overload6(long arg);
+ undefined overload6(boolean arg);
+ undefined overload7(long arg);
+ undefined overload7(boolean arg);
+ undefined overload7(ByteString arg);
+ undefined overload8(long arg);
+ undefined overload8(TestJSImplInterface arg);
+ undefined overload9(long? arg);
+ undefined overload9(DOMString arg);
+ undefined overload10(long? arg);
+ undefined overload10(object arg);
+ undefined overload11(long arg);
+ undefined overload11(DOMString? arg);
+ undefined overload12(long arg);
+ undefined overload12(boolean? arg);
+ undefined overload13(long? arg);
+ undefined overload13(boolean arg);
+ undefined overload14(optional long arg);
+ undefined overload14(TestInterface arg);
+ undefined overload15(long arg);
+ undefined overload15(optional TestInterface arg);
+ undefined overload16(long arg);
+ undefined overload16(optional TestInterface? arg);
+ undefined overload17(sequence<long> arg);
+ undefined overload17(record<DOMString, long> arg);
+ undefined overload18(record<DOMString, DOMString> arg);
+ undefined overload18(sequence<DOMString> arg);
+ undefined overload19(sequence<long> arg);
+ undefined overload19(optional Dict arg = {});
+ undefined overload20(optional Dict arg = {});
+ undefined overload20(sequence<long> arg);
+
+ // Variadic handling
+ undefined passVariadicThirdArg(DOMString arg1, long arg2, TestJSImplInterface... arg3);
+
+ // Conditionally exposed methods/attributes
+ [Pref="dom.webidl.test1"]
+ readonly attribute boolean prefable1;
+ [Pref="dom.webidl.test1"]
+ readonly attribute boolean prefable2;
+ [Pref="dom.webidl.test2"]
+ readonly attribute boolean prefable3;
+ [Pref="dom.webidl.test2"]
+ readonly attribute boolean prefable4;
+ [Pref="dom.webidl.test1"]
+ readonly attribute boolean prefable5;
+ [Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ readonly attribute boolean prefable6;
+ [Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ readonly attribute boolean prefable7;
+ [Pref="dom.webidl.test2", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ readonly attribute boolean prefable8;
+ [Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ readonly attribute boolean prefable9;
+ [Pref="dom.webidl.test1"]
+ undefined prefable10();
+ [Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ undefined prefable11();
+ [Pref="dom.webidl.test1", Func="TestFuncControlledMember"]
+ readonly attribute boolean prefable12;
+ [Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ undefined prefable13();
+ [Pref="dom.webidl.test1", Func="TestFuncControlledMember"]
+ readonly attribute boolean prefable14;
+ [Func="TestFuncControlledMember"]
+ readonly attribute boolean prefable15;
+ [Func="TestFuncControlledMember"]
+ readonly attribute boolean prefable16;
+ [Pref="dom.webidl.test1", Func="TestFuncControlledMember"]
+ undefined prefable17();
+ [Func="TestFuncControlledMember"]
+ undefined prefable18();
+ [Func="TestFuncControlledMember"]
+ undefined prefable19();
+ [Pref="dom.webidl.test1", Func="TestFuncControlledMember", ChromeOnly]
+ undefined prefable20();
+
+ // Conditionally exposed methods/attributes involving [SecureContext]
+ [SecureContext]
+ readonly attribute boolean conditionalOnSecureContext1;
+ [SecureContext, Pref="dom.webidl.test1"]
+ readonly attribute boolean conditionalOnSecureContext2;
+ [SecureContext, Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ readonly attribute boolean conditionalOnSecureContext3;
+ [SecureContext, Pref="dom.webidl.test1", Func="TestFuncControlledMember"]
+ readonly attribute boolean conditionalOnSecureContext4;
+ [SecureContext]
+ undefined conditionalOnSecureContext5();
+ [SecureContext, Pref="dom.webidl.test1"]
+ undefined conditionalOnSecureContext6();
+ [SecureContext, Pref="dom.webidl.test1", Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
+ undefined conditionalOnSecureContext7();
+ [SecureContext, Pref="dom.webidl.test1", Func="TestFuncControlledMember"]
+ undefined conditionalOnSecureContext8();
+
+ // Miscellania
+ [LegacyLenientThis] attribute long attrWithLenientThis;
+ // FIXME: Bug 863954 Unforgeable things get all confused when
+ // non-JS-implemented interfaces inherit from JS-implemented ones or vice
+ // versa.
+ // [Unforgeable] readonly attribute long unforgeableAttr;
+ // [Unforgeable, ChromeOnly] readonly attribute long unforgeableAttr2;
+ // [Unforgeable] long unforgeableMethod();
+ // [Unforgeable, ChromeOnly] long unforgeableMethod2();
+ // FIXME: Bug 863955 No stringifiers yet
+ // stringifier;
+ undefined passRenamedInterface(TestRenamedInterface arg);
+ [PutForwards=writableByte] readonly attribute TestJSImplInterface putForwardsAttr;
+ [PutForwards=writableByte, LegacyLenientThis] readonly attribute TestJSImplInterface putForwardsAttr2;
+ [PutForwards=writableByte, ChromeOnly] readonly attribute TestJSImplInterface putForwardsAttr3;
+ [Throws] undefined throwingMethod();
+ [Throws] attribute boolean throwingAttr;
+ [GetterThrows] attribute boolean throwingGetterAttr;
+ [SetterThrows] attribute boolean throwingSetterAttr;
+ [CanOOM] undefined canOOMMethod();
+ [CanOOM] attribute boolean canOOMAttr;
+ [GetterCanOOM] attribute boolean canOOMGetterAttr;
+ [SetterCanOOM] attribute boolean canOOMSetterAttr;
+ [CEReactions] undefined ceReactionsMethod();
+ [CEReactions] undefined ceReactionsMethodOverload();
+ [CEReactions] undefined ceReactionsMethodOverload(DOMString bar);
+ [CEReactions] attribute boolean ceReactionsAttr;
+ // NeedsSubjectPrincipal not supported on JS-implemented things for
+ // now, because we always pass in the caller principal anyway.
+ // [NeedsSubjectPrincipal] undefined needsSubjectPrincipalMethod();
+ // [NeedsSubjectPrincipal] attribute boolean needsSubjectPrincipalAttr;
+ // legacycaller short(unsigned long arg1, TestInterface arg2);
+ undefined passArgsWithDefaults(optional long arg1,
+ optional TestInterface? arg2 = null,
+ optional Dict arg3 = {}, optional double arg4 = 5.0,
+ optional float arg5);
+ attribute any toJSONShouldSkipThis;
+ attribute TestParentInterface toJSONShouldSkipThis2;
+ attribute TestCallbackInterface toJSONShouldSkipThis3;
+ [Default] object toJSON();
+
+ attribute byte dashed-attribute;
+ undefined dashed-method();
+
+ // [NonEnumerable] tests
+ [NonEnumerable]
+ attribute boolean nonEnumerableAttr;
+ [NonEnumerable]
+ const boolean nonEnumerableConst = true;
+ [NonEnumerable]
+ undefined nonEnumerableMethod();
+
+ // [AllowShared] tests
+ attribute [AllowShared] ArrayBufferViewTypedef allowSharedArrayBufferViewTypedef;
+ attribute [AllowShared] ArrayBufferView allowSharedArrayBufferView;
+ attribute [AllowShared] ArrayBufferView? allowSharedNullableArrayBufferView;
+ attribute [AllowShared] ArrayBuffer allowSharedArrayBuffer;
+ attribute [AllowShared] ArrayBuffer? allowSharedNullableArrayBuffer;
+
+ undefined passAllowSharedArrayBufferViewTypedef(AllowSharedArrayBufferViewTypedef foo);
+ undefined passAllowSharedArrayBufferView([AllowShared] ArrayBufferView foo);
+ undefined passAllowSharedNullableArrayBufferView([AllowShared] ArrayBufferView? foo);
+ undefined passAllowSharedArrayBuffer([AllowShared] ArrayBuffer foo);
+ undefined passAllowSharedNullableArrayBuffer([AllowShared] ArrayBuffer? foo);
+ undefined passUnionArrayBuffer((DOMString or ArrayBuffer) foo);
+ undefined passUnionAllowSharedArrayBuffer((DOMString or [AllowShared] ArrayBuffer) foo);
+
+ // If you add things here, add them to TestCodeGen as well
+};
+
+[Exposed=Window]
+interface TestCImplementedInterface : TestJSImplInterface {
+};
+
+[Exposed=Window]
+interface TestCImplementedInterface2 {
+};
+
+[LegacyNoInterfaceObject,
+ JSImplementation="@mozilla.org/test-js-impl-interface;2",
+ Exposed=Window]
+interface TestJSImplNoInterfaceObject {
+ // [Cached] is not supported in JS-implemented WebIDL.
+ //[Cached, Pure]
+ //readonly attribute byte cachedByte;
+};
diff --git a/dom/bindings/test/TestJSImplInheritanceGen.webidl b/dom/bindings/test/TestJSImplInheritanceGen.webidl
new file mode 100644
index 0000000000..b4b66d20a0
--- /dev/null
+++ b/dom/bindings/test/TestJSImplInheritanceGen.webidl
@@ -0,0 +1,39 @@
+/* -*- 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/.
+ */
+
+[Exposed=Window, JSImplementation="@mozilla.org/test-js-impl-interface2;1"]
+interface TestJSImplInterface2 : TestCImplementedInterface {
+ [Throws]
+ constructor();
+};
+
+[Exposed=Window, JSImplementation="@mozilla.org/test-js-impl-interface3;1"]
+interface TestJSImplInterface3 : TestCImplementedInterface2 {
+ [Throws]
+ constructor();
+};
+
+// Important: TestJSImplInterface5 needs to come before TestJSImplInterface6 in
+// this file to test what it's trying to test.
+[Exposed=Window, JSImplementation="@mozilla.org/test-js-impl-interface5;1"]
+interface TestJSImplInterface5 : TestJSImplInterface6 {
+ [Throws]
+ constructor();
+};
+
+// Important: TestJSImplInterface6 needs to come after TestJSImplInterface3 in
+// this file to test what it's trying to test.
+[Exposed=Window, JSImplementation="@mozilla.org/test-js-impl-interface6;1"]
+interface TestJSImplInterface6 : TestJSImplInterface3 {
+ [Throws]
+ constructor();
+};
+
+[Exposed=Window, JSImplementation="@mozilla.org/test-js-impl-interface4;1"]
+interface TestJSImplInterface4 : EventTarget {
+ [Throws]
+ constructor();
+};
diff --git a/dom/bindings/test/TestTrialInterface.cpp b/dom/bindings/test/TestTrialInterface.cpp
new file mode 100644
index 0000000000..e0b9bb44f9
--- /dev/null
+++ b/dom/bindings/test/TestTrialInterface.cpp
@@ -0,0 +1,24 @@
+/* -*- 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/. */
+
+#include "mozilla/dom/TestTrialInterface.h"
+#include "mozilla/dom/TestFunctionsBinding.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(TestTrialInterface)
+
+JSObject* TestTrialInterface::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return TestTrialInterface_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<TestTrialInterface> TestTrialInterface::Constructor(
+ const GlobalObject& aGlobalObject) {
+ return MakeAndAddRef<TestTrialInterface>();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/test/TestTrialInterface.h b/dom/bindings/test/TestTrialInterface.h
new file mode 100644
index 0000000000..1fa746b09a
--- /dev/null
+++ b/dom/bindings/test/TestTrialInterface.h
@@ -0,0 +1,39 @@
+/* -*- 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_TestTrialInterface_h
+#define mozilla_dom_TestTrialInterface_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+class TestTrialInterface final : public nsWrapperCache {
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(TestTrialInterface)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(TestTrialInterface)
+
+ public:
+ TestTrialInterface() = default;
+
+ static already_AddRefed<TestTrialInterface> Constructor(
+ const GlobalObject& aGlobalObject);
+
+ protected:
+ ~TestTrialInterface() = default;
+
+ public:
+ nsISupports* GetParentObject() const { return nullptr; }
+ JSObject* WrapObject(JSContext*, JS::Handle<JSObject*> aGivenProto) override;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_TestTrialInterface_h
diff --git a/dom/bindings/test/TestTypedef.webidl b/dom/bindings/test/TestTypedef.webidl
new file mode 100644
index 0000000000..7f758c79e8
--- /dev/null
+++ b/dom/bindings/test/TestTypedef.webidl
@@ -0,0 +1,7 @@
+/* -*- 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/.
+ */
+
+typedef TestInterface YetAnotherNameForTestInterface;
diff --git a/dom/bindings/test/WrapperCachedNonISupportsTestInterface.cpp b/dom/bindings/test/WrapperCachedNonISupportsTestInterface.cpp
new file mode 100644
index 0000000000..aa69488a09
--- /dev/null
+++ b/dom/bindings/test/WrapperCachedNonISupportsTestInterface.cpp
@@ -0,0 +1,28 @@
+/* -*- 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/. */
+
+#include "mozilla/dom/WrapperCachedNonISupportsTestInterface.h"
+#include "mozilla/dom/TestFunctionsBinding.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WrapperCachedNonISupportsTestInterface)
+
+JSObject* WrapperCachedNonISupportsTestInterface::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return WrapperCachedNonISupportsTestInterface_Binding::Wrap(aCx, this,
+ aGivenProto);
+}
+
+already_AddRefed<WrapperCachedNonISupportsTestInterface>
+WrapperCachedNonISupportsTestInterface::Constructor(
+ const GlobalObject& aGlobalObject) {
+ RefPtr<WrapperCachedNonISupportsTestInterface> result =
+ new WrapperCachedNonISupportsTestInterface();
+ return result.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/bindings/test/WrapperCachedNonISupportsTestInterface.h b/dom/bindings/test/WrapperCachedNonISupportsTestInterface.h
new file mode 100644
index 0000000000..c226d03456
--- /dev/null
+++ b/dom/bindings/test/WrapperCachedNonISupportsTestInterface.h
@@ -0,0 +1,45 @@
+/* -*- 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_WrapperCachedNonISupportsTestInterface_h
+#define mozilla_dom_WrapperCachedNonISupportsTestInterface_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class WrapperCachedNonISupportsTestInterface final : public nsWrapperCache {
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(
+ WrapperCachedNonISupportsTestInterface)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(
+ WrapperCachedNonISupportsTestInterface)
+
+ public:
+ WrapperCachedNonISupportsTestInterface() = default;
+
+ static already_AddRefed<WrapperCachedNonISupportsTestInterface> Constructor(
+ const GlobalObject& aGlobalObject);
+
+ protected:
+ ~WrapperCachedNonISupportsTestInterface() = default;
+
+ public:
+ nsISupports* GetParentObject() const { return nullptr; }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_WrapperCachedNonISupportsTestInterface_h
diff --git a/dom/bindings/test/chrome.ini b/dom/bindings/test/chrome.ini
new file mode 100644
index 0000000000..fc633d9ceb
--- /dev/null
+++ b/dom/bindings/test/chrome.ini
@@ -0,0 +1,19 @@
+[DEFAULT]
+support-files =
+ !/dom/bindings/test/file_bug775543.html
+ !/dom/bindings/test/file_document_location_set_via_xray.html
+ !/dom/bindings/test/file_dom_xrays.html
+ !/dom/bindings/test/file_proxies_via_xray.html
+
+[test_bug775543.html]
+[test_document_location_set_via_xray.html]
+[test_dom_xrays.html]
+skip-if = debug == false # TestFunctions is only available in debug builds
+[test_proxies_via_xray.html]
+[test_document_location_via_xray_cached.html]
+[test_bug1123516_maplikesetlikechrome.xhtml]
+skip-if = debug == false # TestFunctions is only available in debug builds
+[test_bug1287912.html]
+[test_bug1457051.html]
+[test_interfaceLength_chrome.html]
+skip-if = debug == false # TestFunctions is only available in debug builds
diff --git a/dom/bindings/test/file_InstanceOf.html b/dom/bindings/test/file_InstanceOf.html
new file mode 100644
index 0000000000..61e2bc295e
--- /dev/null
+++ b/dom/bindings/test/file_InstanceOf.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<script type="application/javascript">
+function runTest() {
+ return [ parent.HTMLElement.prototype instanceof Element,
+ parent.HTMLElement.prototype instanceof parent.Element ];
+}
+</script>
+</body>
+</html>
diff --git a/dom/bindings/test/file_barewordGetsWindow_frame1.html b/dom/bindings/test/file_barewordGetsWindow_frame1.html
new file mode 100644
index 0000000000..f20ba8a257
--- /dev/null
+++ b/dom/bindings/test/file_barewordGetsWindow_frame1.html
@@ -0,0 +1 @@
+OLD \ No newline at end of file
diff --git a/dom/bindings/test/file_barewordGetsWindow_frame2.html b/dom/bindings/test/file_barewordGetsWindow_frame2.html
new file mode 100644
index 0000000000..5f08364e3b
--- /dev/null
+++ b/dom/bindings/test/file_barewordGetsWindow_frame2.html
@@ -0,0 +1 @@
+NEW \ No newline at end of file
diff --git a/dom/bindings/test/file_bug1808352_frame.html b/dom/bindings/test/file_bug1808352_frame.html
new file mode 100644
index 0000000000..aaff3b561f
--- /dev/null
+++ b/dom/bindings/test/file_bug1808352_frame.html
@@ -0,0 +1,4 @@
+<script>
+document.domain = "mochi.test";
+window.parent.loadedFrame();
+</script>
diff --git a/dom/bindings/test/file_bug1808352b_frame.html b/dom/bindings/test/file_bug1808352b_frame.html
new file mode 100644
index 0000000000..513f32e4da
--- /dev/null
+++ b/dom/bindings/test/file_bug1808352b_frame.html
@@ -0,0 +1,3 @@
+<script>
+window.opener.loadedFrame();
+</script>
diff --git a/dom/bindings/test/file_bug775543.html b/dom/bindings/test/file_bug775543.html
new file mode 100644
index 0000000000..856d14ab0e
--- /dev/null
+++ b/dom/bindings/test/file_bug775543.html
@@ -0,0 +1,5 @@
+<body>
+<script>
+var worker = new Worker("a");
+</script>
+</body>
diff --git a/dom/bindings/test/file_document_location_set_via_xray.html b/dom/bindings/test/file_document_location_set_via_xray.html
new file mode 100644
index 0000000000..323acba666
--- /dev/null
+++ b/dom/bindings/test/file_document_location_set_via_xray.html
@@ -0,0 +1,5 @@
+<body>
+<script>
+document.x = 5;
+</script>
+</body>
diff --git a/dom/bindings/test/file_dom_xrays.html b/dom/bindings/test/file_dom_xrays.html
new file mode 100644
index 0000000000..3a71a3d836
--- /dev/null
+++ b/dom/bindings/test/file_dom_xrays.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+ <script>
+ document.expando = 42;
+ document.shadowedIframe = 42;
+ Object.setPrototypeOf(document, Object.create(HTMLDocument.prototype,
+ {
+ shadowedIframe: { value: 42 },
+ iframe: { value: 42 },
+ defaultView: { value: 42 },
+ addEventListener: { value: 42 },
+ toString: { value: 42 },
+ }));
+ document.documentElement.expando = 42;
+ Object.defineProperty(document.documentElement, "version", { value: 42 });
+ </script>
+ <iframe name="shadowedIframe" id="shadowedIframe"></iframe>
+ <iframe name="iframe" id="iframe"></iframe>
+ <iframe name="document" id="document"></iframe>
+ <iframe name="self" id="self"></iframe>
+ <iframe name="addEventListener" id="addEventListener"></iframe>
+ <iframe name="toString" id="toString"></iframe>
+ <iframe name="item" id="item"></iframe>
+</html>
diff --git a/dom/bindings/test/file_proxies_via_xray.html b/dom/bindings/test/file_proxies_via_xray.html
new file mode 100644
index 0000000000..135c1f5301
--- /dev/null
+++ b/dom/bindings/test/file_proxies_via_xray.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+ <script>
+ document.x = 5;
+ </script>
+ <img id="y" name="y"></div>
+ <img id="z" name="z"></div>
+</html>
diff --git a/dom/bindings/test/forOf_iframe.html b/dom/bindings/test/forOf_iframe.html
new file mode 100644
index 0000000000..91417aba0e
--- /dev/null
+++ b/dom/bindings/test/forOf_iframe.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>iframe content for test_forOf_iframe.html</title>
+</head>
+<body>
+ <div id="basket">
+ <span id="egg0"></span>
+ <span id="egg1"><span id="duckling1"></span></span>
+ <span id="egg2"></span>
+ </div>
+</body>
+</html>
diff --git a/dom/bindings/test/mochitest.ini b/dom/bindings/test/mochitest.ini
new file mode 100644
index 0000000000..552e915898
--- /dev/null
+++ b/dom/bindings/test/mochitest.ini
@@ -0,0 +1,117 @@
+[DEFAULT]
+support-files =
+ file_InstanceOf.html
+ file_bug775543.html
+ file_document_location_set_via_xray.html
+ file_dom_xrays.html
+ file_proxies_via_xray.html
+ forOf_iframe.html
+ !/js/xpconnect/tests/mochitest/file_empty.html
+prefs =
+ javascript.options.large_arraybuffers=true
+
+[test_async_stacks.html]
+[test_ByteString.html]
+[test_InstanceOf.html]
+[test_bug560072.html]
+[test_bug742191.html]
+[test_bug759621.html]
+[test_bug773326.html]
+[test_bug788369.html]
+[test_bug852846.html]
+[test_bug862092.html]
+[test_bug1036214.html]
+skip-if =
+ !debug
+ http3
+[test_bug1041646.html]
+[test_bug1123875.html]
+[test_bug1808352.html]
+skip-if = http3 # Bug 1827526
+support-files =
+ file_bug1808352_frame.html
+[test_bug1808352b.html]
+support-files =
+ file_bug1808352b_frame.html
+[test_barewordGetsWindow.html]
+support-files =
+ file_barewordGetsWindow_frame1.html
+ file_barewordGetsWindow_frame2.html
+[test_callback_across_document_open.html]
+[test_callback_default_thisval.html]
+[test_cloneAndImportNode.html]
+[test_defineProperty.html]
+[test_enums.html]
+[test_exceptionThrowing.html]
+[test_exception_messages.html]
+[test_forOf.html]
+[test_integers.html]
+[test_interfaceLength.html]
+skip-if = debug == false
+[test_interfaceName.html]
+[test_interfaceToString.html]
+[test_prefOnConstructor.html]
+skip-if = debug == false
+[test_exceptions_from_jsimplemented.html]
+tags = webrtc
+[test_lenientThis.html]
+[test_lookupGetter.html]
+[test_namedNoIndexed.html]
+[test_named_getter_enumerability.html]
+[test_Object.prototype_props.html]
+[test_proxy_expandos.html]
+[test_proxy_accessors.html]
+[test_returnUnion.html]
+skip-if = debug == false
+[test_usvstring.html]
+skip-if = debug == false
+[test_sequence_wrapping.html]
+subsuite = gpu
+[test_setWithNamedGetterNoNamedSetter.html]
+[test_throwing_method_noDCE.html]
+[test_treat_non_object_as_null.html]
+[test_traceProtos.html]
+[test_sequence_detection.html]
+skip-if = debug == false
+[test_exception_options_from_jsimplemented.html]
+skip-if = debug == false
+[test_promise_rejections_from_jsimplemented.html]
+skip-if = debug == false
+[test_worker_UnwrapArg.html]
+[test_unforgeablesonexpando.html]
+[test_crossOriginWindowSymbolAccess.html]
+skip-if =
+ http3
+[test_primitive_this.html]
+[test_callback_exceptions.html]
+[test_bug1123516_maplikesetlike.html]
+skip-if = debug == false
+[test_jsimplemented_eventhandler.html]
+skip-if = debug == false
+[test_jsimplemented_cross_realm_this.html]
+skip-if = debug == false
+[test_iterable.html]
+skip-if = debug == false
+[test_async_iterable.html]
+skip-if = debug == false
+[test_oom_reporting.html]
+[test_domProxyArrayLengthGetter.html]
+[test_exceptionSanitization.html]
+skip-if = debug == false
+[test_stringBindings.html]
+skip-if = debug == false
+[test_jsimplemented_subclassing.html]
+[test_toJSON.html]
+skip-if = debug == false
+[test_attributes_on_types.html]
+skip-if = debug == false
+[test_large_arraybuffers.html]
+skip-if = (debug == false || bits == 32) # Large ArrayBuffers are only supported on 64-bit platforms.
+[test_observablearray.html]
+skip-if = debug == false
+[test_observablearray_proxyhandler.html]
+skip-if = debug == false
+[test_observablearray_helper.html]
+skip-if = debug == false
+[test_large_imageData.html]
+[test_remoteProxyAsPrototype.html]
diff --git a/dom/bindings/test/moz.build b/dom/bindings/test/moz.build
new file mode 100644
index 0000000000..4cc34d59dc
--- /dev/null
+++ b/dom/bindings/test/moz.build
@@ -0,0 +1,69 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DEFINES["IMPL_LIBXUL"] = True
+DEFINES["MOZILLA_INTERNAL_API"] = True
+
+# Do NOT export this library. We don't actually want our test code
+# being added to libxul or anything.
+
+Library("dombindings_test_s")
+
+MOCHITEST_MANIFESTS += ["mochitest.ini"]
+
+MOCHITEST_CHROME_MANIFESTS += ["chrome.ini"]
+
+TEST_WEBIDL_FILES += [
+ "TestCallback.webidl",
+ "TestDictionary.webidl",
+ "TestJSImplInheritanceGen.webidl",
+ "TestTypedef.webidl",
+]
+
+TESTING_JS_MODULES += [
+ "TestInterfaceJS.sys.mjs",
+]
+
+PREPROCESSED_TEST_WEBIDL_FILES += [
+ "TestCodeGen.webidl",
+ "TestExampleGen.webidl",
+ "TestJSImplGen.webidl",
+]
+
+WEBIDL_EXAMPLE_INTERFACES += [
+ "TestExampleInterface",
+ "TestExampleProxyInterface",
+ "TestExampleThrowingConstructorInterface",
+ "TestExampleWorkerInterface",
+]
+
+# Bug 932082 tracks having bindings use namespaced includes.
+LOCAL_INCLUDES += [
+ "!/dist/include/mozilla/dom",
+]
+
+LOCAL_INCLUDES += [
+ "!..",
+ "/dom/bindings",
+ "/js/xpconnect/src",
+ "/js/xpconnect/wrappers",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+if CONFIG["MOZ_DEBUG"]:
+ XPIDL_SOURCES += [
+ "mozITestInterfaceJS.idl",
+ ]
+
+ XPIDL_MODULE = "dom_bindings_test"
+
+# Because we don't actually link this code anywhere, we don't care about
+# their optimization level, so don't waste time on optimization.
+if CONFIG["CC_TYPE"] == "clang-cl":
+ CXXFLAGS += ["-Od"]
+else:
+ CXXFLAGS += ["-O0"]
diff --git a/dom/bindings/test/mozITestInterfaceJS.idl b/dom/bindings/test/mozITestInterfaceJS.idl
new file mode 100644
index 0000000000..7bfbc9b25f
--- /dev/null
+++ b/dom/bindings/test/mozITestInterfaceJS.idl
@@ -0,0 +1,21 @@
+/* -*- Mode: IDL; tab-width: 4; 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/. */
+
+#include "nsISupports.idl"
+
+/**
+ * An interface to allow testing of binding interactions with JS-implemented
+ * XPCOM components. The actual implementation is TestInterfaceJS, just like
+ * for TestInteraceJS.webidl.
+ */
+
+[scriptable, uuid(9eeb2c12-ddd9-4734-8cfb-c0cdfb136e07)]
+interface mozITestInterfaceJS : nsISupports {
+ // Test throwing Components.results.NS_BINDING_ABORTED.
+ void testThrowNsresult();
+ // Test calling a C++ component which throws an nsresult exception.
+ void testThrowNsresultFromNative();
+};
diff --git a/dom/bindings/test/test_ByteString.html b/dom/bindings/test/test_ByteString.html
new file mode 100644
index 0000000000..c4285228e9
--- /dev/null
+++ b/dom/bindings/test/test_ByteString.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=796850
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for ByteString support</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=796850">Mozilla Bug 796850</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+ /** Test for Bug 796850 **/
+ var xhr = new XMLHttpRequest();
+ var caught = false;
+ try {
+ xhr.open("\u5427", "about:mozilla", true);
+ } catch (TypeError) {
+ caught = true;
+ }
+ ok(caught, "Character values > 255 not rejected for ByteString");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_InstanceOf.html b/dom/bindings/test/test_InstanceOf.html
new file mode 100644
index 0000000000..d04e4e4771
--- /dev/null
+++ b/dom/bindings/test/test_InstanceOf.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=748983
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 748983</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=748983">Mozilla Bug 748983</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 748983 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+ ok(document instanceof EventTarget, "document is an event target");
+ ok(new XMLHttpRequest() instanceof XMLHttpRequest, "instanceof should work on XHR");
+ ok(HTMLElement.prototype instanceof Node, "instanceof needs to walk the prototype chain");
+
+ var otherWin = document.getElementById("testFrame").contentWindow;
+
+ ok(otherWin.HTMLElement.prototype instanceof otherWin.Node, "Same-origin instanceof of a interface prototype object should work, even if called cross-origin");
+ ok(!(otherWin.HTMLElement.prototype instanceof Node), "Cross-origin instanceof of a interface prototype object shouldn't work");
+
+ // We need to reset HTMLElement.prototype.__proto__ to the original value
+ // before using anything from the harness, otherwise the harness code breaks
+ // in weird ways.
+ HTMLElement.prototype.__proto__ = otherWin.Element.prototype;
+ var [ shouldSucceed, shouldFail ] = otherWin.runTest();
+ shouldSucceed = shouldSucceed && HTMLElement.prototype instanceof otherWin.Element;
+ shouldFail = shouldFail && HTMLElement.prototype instanceof Element;
+ HTMLElement.prototype.__proto__ = Element.prototype;
+
+ ok(shouldSucceed, "If an interface prototype object is on the protochain then instanceof with the interface object should succeed");
+ ok(!shouldFail, "If an interface prototype object is not on the protochain then instanceof with the interface object should succeed");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+<iframe id="testFrame" src="file_InstanceOf.html" onload="runTest()"></iframe>
+</body>
+</html>
diff --git a/dom/bindings/test/test_Object.prototype_props.html b/dom/bindings/test/test_Object.prototype_props.html
new file mode 100644
index 0000000000..b0e42dbc05
--- /dev/null
+++ b/dom/bindings/test/test_Object.prototype_props.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for bug 987110</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+/* global test, assert_array_equals */
+test(function() {
+ var props = Object.getOwnPropertyNames(Object.prototype);
+ // If you change this list, make sure it continues to match the list in
+ // Codegen.py's CGDictionary.getMemberDefinition method.
+ var expected = [
+ "constructor", "toString", "toLocaleString", "valueOf",
+ "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable",
+ "__defineGetter__", "__defineSetter__", "__lookupGetter__",
+ "__lookupSetter__", "__proto__",
+ ];
+ assert_array_equals(props.sort(), expected.sort());
+}, "Own properties of Object.prototype");
+</script>
diff --git a/dom/bindings/test/test_async_iterable.html b/dom/bindings/test/test_async_iterable.html
new file mode 100644
index 0000000000..8f1f04aea7
--- /dev/null
+++ b/dom/bindings/test/test_async_iterable.html
@@ -0,0 +1,300 @@
+<!-- Any copyright is dedicated to the Public Domain.
+- http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test Async Iterable Interface</title>
+ <script 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">
+
+add_task(async function init() {
+ await SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]});
+});
+
+const singleValues = Array(10).fill(0).map((_, i) => i * 9 % 7);
+
+async function check_single_result_values(values, multiplier = 1) {
+ is(values.length, 10, `AsyncIterableSingle: should return 10 elements`);
+ for (let i = 0; i < 10; i++) {
+ let expected = singleValues[i] * multiplier;
+ is(values[i], expected,
+ `AsyncIterableSingle: should be ${expected}, get ${values[i]}`);
+ }
+}
+
+async function check_single_result(itr, multiplier = 1) {
+ let values = [];
+ for await (let v of itr) {
+ values.push(v);
+ }
+ check_single_result_values(values, multiplier);
+}
+
+async function test_data_single() {
+ info(`AsyncIterableSingle: Testing simple iterable creation and functionality`);
+
+ // eslint-disable-next-line no-undef
+ let itr = new TestInterfaceAsyncIterableSingle({ failToInit: true });
+ let initFailed = false;
+ try {
+ itr.values();
+ } catch (e) {
+ initFailed = true;
+ }
+ ok(initFailed,
+ "AsyncIterableSingle: A failure in asynchronous iterator initialization " +
+ "steps should propagate to the caller of the asynchronous iterator's " +
+ "constructor.");
+
+ // eslint-disable-next-line no-undef
+ itr = new TestInterfaceAsyncIterableSingle();
+ is(itr.values, itr[Symbol.asyncIterator],
+ `AsyncIterableSingle: Should be using @@asyncIterator for 'values'`);
+
+ await check_single_result(itr);
+ await check_single_result(itr.values());
+
+ // eslint-disable-next-line no-undef
+ itr = new TestInterfaceAsyncIterableSingleWithArgs();
+ is(itr.values, itr[Symbol.asyncIterator],
+ `AsyncIterableSingleWithArgs: Should be using @@asyncIterator for 'values'`);
+
+ await check_single_result(itr, 1);
+ await check_single_result(itr.values({ multiplier: 2 }), 2);
+
+ // eslint-disable-next-line no-undef
+ itr = new TestInterfaceAsyncIterableSingle();
+ let itrValues = itr.values();
+ let values = [];
+ for (let i = 0; i < 10; ++i) {
+ values.push(itrValues.next());
+ }
+ check_single_result_values(await Promise.all(values).then(v => v.map(w => w.value)));
+
+ // Test that there is only one ongoing promise at a time.
+ // Async iterables return a promise that is then resolved with the iterator
+ // value. We create an array of unresolved promises here, one promise for
+ // every result that we expect from the iterator. We pass that array of
+ // promises to the .value() method of the
+ // TestInterfaceAsyncIterableSingleWithArgs, and it will chain the resolving
+ // of each resulting iterator value on the corresponding promise from this
+ // array. We then resolve the promises in the array one by one in reverse
+ // order. This tries to make sure that the iterator always resolves the
+ // promises in the order of iteration.
+ let unblockers = [];
+ let blockingPromises = [];
+ for (let i = 0; i < 10; ++i) {
+ let unblocker;
+ let promise = new Promise((resolve, reject) => {
+ unblocker = resolve;
+ });
+ unblockers.push(unblocker);
+ blockingPromises.push(promise);
+ }
+
+ // eslint-disable-next-line no-undef
+ itr = new TestInterfaceAsyncIterableSingleWithArgs();
+ itrValues = itr.values({ blockingPromises });
+ values = [];
+ for (let i = 0; i < 10; ++i) {
+ values.push(itrValues.next());
+ }
+ unblockers.reverse();
+ for (let unblocker of unblockers) {
+ unblocker();
+ }
+
+ check_single_result_values(await Promise.all(values).then(v => v.map(w => w.value)));
+
+ // eslint-disable-next-line no-undef
+ itr = new TestInterfaceAsyncIterableSingleWithArgs();
+
+ let callCount = itr.returnCallCount;
+
+ let i = 0;
+ for await (let v of itr) {
+ if (++i > 1) {
+ break;
+ }
+ values.push(v);
+ }
+
+ is(itr.returnCallCount, callCount + 1,
+ `AsyncIterableSingle: breaking out of for-await-of loop should call "return"`);
+ is(itr.returnLastCalledWith, undefined,
+ `AsyncIterableSingleWithArgs: the asynchronous iterator return algorithm should be called with the argument that was passed in.`);
+
+ // eslint-disable-next-line no-undef
+ itr = new TestInterfaceAsyncIterableSingleWithArgs();
+
+ async function * yieldFromIterator () {
+ yield * itr
+ }
+
+ let yieldingIterator = yieldFromIterator();
+
+ let result = await yieldingIterator.next();
+ is(result.value, singleValues[0],
+ `AsyncIterableSingle: should be ${singleValues[0]}, get ${result.value}`);
+ result = await yieldingIterator.next();
+ is(result.value, singleValues[1],
+ `AsyncIterableSingle: should be ${singleValues[1]}, get ${result.value}`);
+
+ result = await yieldingIterator.return("abcd");
+ is(typeof result, "object",
+ `AsyncIterableSingleWithArgs: "return("abcd")" should return { done: true, value: "abcd" }`);
+ is(result.done, true,
+ `AsyncIterableSingleWithArgs: "return("abcd")" should return { done: true, value: "abcd" }`);
+ is(result.value, "abcd",
+ `AsyncIterableSingleWithArgs: "return("abcd")" should return { done: true, value: "abcd" }`);
+ is(itr.returnLastCalledWith, "abcd",
+ `AsyncIterableSingleWithArgs: the asynchronous iterator return algorithm should be called with the argument that was passed in.`);
+
+ result = await yieldingIterator.return("efgh");
+ is(typeof result, "object",
+ `AsyncIterableSingleWithArgs: "return("efgh")" should return { done: true, value: "efgh" }`);
+ is(result.done, true,
+ `AsyncIterableSingleWithArgs: "return("efgh")" should return { done: true, value: "efgh" }`);
+ is(result.value, "efgh",
+ `AsyncIterableSingleWithArgs: "return("efgh")" should return { done: true, value: "efgh" }`);
+ is(itr.returnLastCalledWith, "abcd",
+ `AsyncIterableSingleWithArgs: the asynchronous iterator return algorithm shouldn't be called if the iterator's 'is finished' flag is true already.`);
+
+ // eslint-disable-next-line no-undef
+ itr = new TestInterfaceAsyncIterableSingleWithArgs();
+ itrValues = itr.values({ failNextAfter: 1 });
+ await itrValues.next().then(({ value, done }) => {
+ is(value, singleValues[0], "First value is correct");
+ ok(!done, "Expecting more values");
+ return itrValues.next();
+ }).then(() => {
+ ok(false, "Second call to next() should convert failure to a rejected promise.");
+ return itrValues.next();
+ }).catch(() => {
+ ok(true, "Second call to next() should convert failure to a rejected promise.");
+ return itrValues.next();
+ }).then(({ done }) => {
+ ok(done, "An earlier failure in next() should set the async iterator's 'is finished' flag to true.");
+ }).catch(() => {
+ ok(false, "An earlier failure in next() shouldn't cause subsequent calls to return a rejected promise.");
+ });
+
+ // eslint-disable-next-line no-undef
+ itr = new TestInterfaceAsyncIterableSingleWithArgs();
+ itrValues = itr.values({ throwFromNext: true });
+ await itrValues.next().then(() => {
+ ok(false, "Should have rejected from the exception");
+ }).catch(() => {
+ ok(true, "Should have rejected from the exception");
+ });
+
+ // eslint-disable-next-line no-undef
+ itr = new TestInterfaceAsyncIterableSingleWithArgs();
+ itrValues = itr.values({ throwFromReturn: () => { throw new DOMException("Throw from return", "InvalidStateError"); } });
+ await itrValues.return().then(() => {
+ ok(false, "Should have rejected from the exception");
+ }).catch(() => {
+ ok(true, "Should have rejected from the exception");
+ });
+}
+
+async function test_data_double() {
+ info(`AsyncIterableDouble: Testing simple iterable creation and functionality`);
+
+ // eslint-disable-next-line no-undef
+ let itr = new TestInterfaceAsyncIterableDouble();
+ is(itr.entries, itr[Symbol.asyncIterator],
+ `AsyncIterableDouble: Should be using @@asyncIterator for 'entries'`);
+
+ let elements = [["a", "b"], ["c", "d"], ["e", "f"]];
+ let key_itr = itr.keys();
+ let value_itr = itr.values();
+ let entries_itr = itr.entries();
+ let key = await key_itr.next();
+ let value = await value_itr.next();
+ let entry = await entries_itr.next();
+ for (let i = 0; i < 3; ++i) {
+ is(key.value, elements[i][0], `AsyncIterableDouble: Key.value should be ${elements[i][0]}, got ${key.value}`);
+ is(key.done, false, `AsyncIterableDouble: Key.done should be false, got ${key.done}`);
+ is(value.value, elements[i][1], `AsyncIterableDouble: Value.value should be ${elements[i][1]}, got ${value.value}`);
+ is(value.done, false, `AsyncIterableDouble: Value.done should be false, got ${value.done}`);
+ is(entry.value[0], elements[i][0], `AsyncIterableDouble: Entry.value[0] should be ${elements[i][0]}, got ${entry.value[0]}`);
+ is(entry.value[1], elements[i][1], `AsyncIterableDouble: Entry.value[1] should be ${elements[i][1]}, got ${entry.value[1]}`);
+ is(entry.done, false, `AsyncIterableDouble: Entry.done should be false, got ${entry.done}`);
+
+ key = await key_itr.next();
+ value = await value_itr.next();
+ entry = await entries_itr.next();
+ }
+ is(key.value, undefined, `AsyncIterableDouble: Key.value should be ${undefined}, got ${key.value}`);
+ is(key.done, true, `AsyncIterableDouble: Key.done should be true, got ${key.done}`);
+ is(value.value, undefined, `AsyncIterableDouble: Value.value should be ${undefined}, got ${value.value}`);
+ is(value.done, true, `AsyncIterableDouble: Value.done should be true, got ${value.done}`);
+ is(entry.value, undefined, `AsyncIterableDouble: Entry.value should be ${undefined}, got ${entry.value}`);
+ is(entry.done, true, `AsyncIterableDouble: Entry.done should be true, got ${entry.done}`);
+
+ let idx = 0;
+ for await (let [itrkey, itrvalue] of itr) {
+ is(itrkey, elements[idx][0], `AsyncIterableDouble: Looping at ${idx} should have key ${elements[idx][0]}, got ${key}`);
+ is(itrvalue, elements[idx][1], `AsyncIterableDouble: Looping at ${idx} should have value ${elements[idx][1]}, got ${value}`);
+ ++idx;
+ }
+ is(idx, 3, `AsyncIterableDouble: Should have 3 loops of for-await-of, got ${idx}`);
+}
+
+async function test_data_double_union() {
+ info(`AsyncIterableDoubleUnion: Testing simple iterable creation and functionality`);
+
+ // eslint-disable-next-line no-undef
+ let itr = new TestInterfaceAsyncIterableDoubleUnion();
+ is(itr.entries, itr[Symbol.asyncIterator],
+ `AsyncIterableDoubleUnion: Should be using @@asyncIterator for 'entries'`);
+
+ let elements = [["long", 1], ["string", "a"]];
+ let key_itr = itr.keys();
+ let value_itr = itr.values();
+ let entries_itr = itr.entries();
+ let key = await key_itr.next();
+ let value = await value_itr.next();
+ let entry = await entries_itr.next();
+ for (let i = 0; i < 2; ++i) {
+ is(key.value, elements[i][0], `AsyncIterableDoubleUnion: Key.value should be ${elements[i][0]}, got ${key.value}`);
+ is(key.done, false, `AsyncIterableDoubleUnion: Key.done should be false, got ${key.done}`);
+ is(value.value, elements[i][1], `AsyncIterableDoubleUnion: Value.value should be ${elements[i][1]}, got ${value.value}`);
+ is(value.done, false, `AsyncIterableDoubleUnion: Value.done should be false, got ${value.done}`);
+ is(entry.value[0], elements[i][0], `AsyncIterableDoubleUnion: Entry.value[0] should be ${elements[i][0]}, got ${entry.value[0]}`);
+ is(entry.value[1], elements[i][1], `AsyncIterableDoubleUnion: Entry.value[1] should be ${elements[i][1]}, got ${entry.value[1]}`);
+ is(entry.done, false, `AsyncIterableDoubleUnion: Entry.done should be false, got ${entry.done}`);
+
+ key = await key_itr.next();
+ value = await value_itr.next();
+ entry = await entries_itr.next();
+ }
+ is(key.value, undefined, `AsyncIterableDoubleUnion: Key.value should be ${undefined}, got ${key.value}`);
+ is(key.done, true, `AsyncIterableDoubleUnion: Key.done should be true, got ${key.done}`);
+ is(value.value, undefined, `AsyncIterableDoubleUnion: Value.value should be ${undefined}, got ${value.value}`);
+ is(value.done, true, `AsyncIterableDoubleUnion: Value.done should be true, got ${value.done}`);
+ is(entry.value, undefined, `AsyncIterableDoubleUnion: Entry.value should be ${undefined}, got ${entry.value}`);
+ is(entry.done, true, `AsyncIterableDoubleUnion: Entry.done should be true, got ${entry.done}`);
+
+ let idx = 0;
+ for await (let [itrkey, itrvalue] of itr) {
+ is(itrkey, elements[idx][0], `AsyncIterableDoubleUnion: Looping at ${idx} should have key ${elements[idx][0]}, got ${key}`);
+ is(itrvalue, elements[idx][1], `AsyncIterableDoubleUnion: Looping at ${idx} should have value ${elements[idx][1]}, got ${value}`);
+ ++idx;
+ }
+ is(idx, 2, `AsyncIterableDoubleUnion: Should have 2 loops of for-await-of, got ${idx}`);
+}
+
+add_task(async function do_tests() {
+ await test_data_single();
+ await test_data_double();
+ await test_data_double_union();
+});
+
+ </script>
+ </body>
+</html>
diff --git a/dom/bindings/test/test_async_stacks.html b/dom/bindings/test/test_async_stacks.html
new file mode 100644
index 0000000000..fe761e783b
--- /dev/null
+++ b/dom/bindings/test/test_async_stacks.html
@@ -0,0 +1,109 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1148593
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1148593</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ /* global noSuchFunction */
+
+ /** Test for Bug 1148593 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ var TESTS;
+
+ function nextTest() {
+ var t = TESTS.pop();
+ if (t) {
+ t();
+ } else {
+ SimpleTest.finish();
+ }
+ }
+
+ function checkStack(functionName) {
+ try {
+ noSuchFunction();
+ } catch (e) {
+ ok(e.stack.includes(functionName), "stack includes " + functionName);
+ }
+ nextTest();
+ }
+
+ function eventListener() {
+ checkStack("registerEventListener");
+ }
+ function registerEventListener(link) {
+ link.onload = eventListener;
+ }
+ function eventTest() {
+ var link = document.createElement("link");
+ link.rel = "stylesheet";
+ link.href = "data:text/css,";
+ registerEventListener(link);
+ document.body.appendChild(link);
+ }
+
+ function xhrListener() {
+ checkStack("xhrTest");
+ }
+ function xhrTest() {
+ var ourFile = location.href;
+ var x = new XMLHttpRequest();
+ x.onload = xhrListener;
+ x.open("get", ourFile, true);
+ x.send();
+ }
+
+ function rafListener() {
+ checkStack("rafTest");
+ }
+ function rafTest() {
+ requestAnimationFrame(rafListener);
+ }
+
+ var intervalId;
+ function intervalHandler() {
+ clearInterval(intervalId);
+ checkStack("intervalTest");
+ }
+ function intervalTest() {
+ intervalId = setInterval(intervalHandler, 5);
+ }
+
+ function postMessageHandler(ev) {
+ ev.stopPropagation();
+ checkStack("postMessageTest");
+ }
+ function postMessageTest() {
+ window.addEventListener("message", postMessageHandler, true);
+ window.postMessage("whatever", "*");
+ }
+
+ function runTests() {
+ TESTS = [postMessageTest, intervalTest, rafTest, xhrTest, eventTest];
+ nextTest();
+ }
+
+ addLoadEvent(function() {
+ SpecialPowers.pushPrefEnv(
+ {"set": [["javascript.options.asyncstack_capture_debuggee_only", false]]},
+ runTests);
+ });
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1148593">Mozilla Bug 1148593</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_attributes_on_types.html b/dom/bindings/test/test_attributes_on_types.html
new file mode 100644
index 0000000000..ce606d2014
--- /dev/null
+++ b/dom/bindings/test/test_attributes_on_types.html
@@ -0,0 +1,246 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1295322
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for WebIDL attributes on types</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1295322">Mozilla Bug 1295322</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+ /* global TestFunctions */
+
+ add_task(async function push_permission() {
+ await SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]});
+ });
+
+ add_task(function testClampedNullableOctet() {
+ let test = new TestFunctions();
+ test.clampedNullableOctet = null;
+ is(test.clampedNullableOctet, null, "clampedNullableOctet should be null");
+ test.clampedNullableOctet = -1;
+ is(test.clampedNullableOctet, 0, "clampedNullableOctet should be clamped to 0");
+ test.clampedNullableOctet = 256;
+ is(test.clampedNullableOctet, 255, "clampedNullableOctet should be clamped 255");
+ test.clampedNullableOctet = 200;
+ is(test.clampedNullableOctet, 200, "clampedNullableOctet should be 200");
+ test.clampedNullableOctet = null;
+ is(test.clampedNullableOctet, null, "clampedNullableOctet should be null");
+ });
+
+ add_task(function testEnforcedNullableOctet() {
+ let test = new TestFunctions();
+ test.enforcedNullableOctet = null;
+ is(test.enforcedNullableOctet, null, "enforcedNullableOctet should be null");
+ try {
+ test.enforcedNullableOctet = -1;
+ ok(false, "Setting -1 to enforcedNullableOctet should throw exception");
+ } catch(e) {}
+ is(test.enforcedNullableOctet, null, "enforcedNullableOctet should still be null");
+ try {
+ test.enforcedNullableOctet = 256;
+ ok(false, "Setting 256 to enforcedNullableOctet should throw exception");
+ } catch(e) {}
+ is(test.enforcedNullableOctet, null, "enforcedNullableOctet should still be null");
+ test.enforcedNullableOctet = 200;
+ is(test.enforcedNullableOctet, 200, "enforcedNullableOctet should be 200");
+ test.enforcedNullableOctet = null;
+ is(test.enforcedNullableOctet, null, "enforcedNullableOctet should be null");
+ });
+
+ add_task(function testAllowShared() {
+ let test = new TestFunctions();
+ [{type: "ArrayBuffer", isShared: false},
+ {type: "SharedArrayBuffer", isShared: true}].forEach(arrayBuffer => {
+ if (self[arrayBuffer.type] === undefined) {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1606624
+ // Once we enable SharedArrayBuffer on all channel, we could remove
+ // this.
+ todo(false, `${arrayBuffer.type} is unavailable.`);
+ return;
+ }
+
+ let buffer = new self[arrayBuffer.type](32);
+ let threw = false;
+ // Test Not Allow Shared
+ try {
+ test.testNotAllowShared(buffer);
+ threw = false;
+ } catch(e) {
+ threw = true;
+ }
+ is(threw, arrayBuffer.isShared, `Call testNotAllowShared with ${arrayBuffer.type}`);
+
+ try {
+ test.testDictWithAllowShared({arrayBuffer: buffer});
+ threw = false;
+ } catch(e) {
+ threw = true;
+ }
+ is(threw, arrayBuffer.isShared, `Call testDictWithAllowShared with {arrayBuffer: ${arrayBuffer.type}}`);
+
+ try {
+ test.testUnionOfBuffferSource(buffer);
+ threw = false;
+ } catch(e) {
+ threw = true;
+ }
+ is(threw, arrayBuffer.isShared, `Call testUnionOfBuffferSource with ${arrayBuffer.type}`);
+
+ try {
+ test.arrayBuffer = buffer;
+ threw = false;
+ } catch(e) {
+ threw = true;
+ }
+ is(threw, arrayBuffer.isShared, `Set arrayBuffer to ${arrayBuffer.type}`);
+
+ try {
+ test.sequenceOfArrayBuffer = [buffer];
+ threw = false;
+ } catch(e) {
+ threw = true;
+ }
+ is(threw, arrayBuffer.isShared, `Set sequenceOfArrayBuffer to [${arrayBuffer.type}]`);
+
+ // Test Allow Shared
+ try {
+ test.testAllowShared(buffer);
+ threw = false;
+ } catch(e) {
+ threw = true;
+ }
+ ok(!threw, `Call testAllowShared with ${arrayBuffer.type}`);
+
+ try {
+ test.testDictWithAllowShared({allowSharedArrayBuffer: buffer});
+ threw = false;
+ } catch(e) {
+ threw = true;
+ }
+ ok(!threw, `Call testDictWithAllowShared with {allowSharedArrayBuffer: ${arrayBuffer.type}}`);
+
+ try {
+ test.testUnionOfAllowSharedBuffferSource(buffer);
+ threw = false;
+ } catch(e) {
+ threw = true;
+ }
+ ok(!threw, `Call testUnionOfAllowSharedBuffferSource with ${arrayBuffer.type}`);
+
+ try {
+ test.allowSharedArrayBuffer = buffer;
+ threw = false;
+ } catch(e) {
+ threw = true;
+ }
+ ok(!threw, `Set allowSharedArrayBuffer to ${arrayBuffer.type}`);
+
+ try {
+ test.sequenceOfAllowSharedArrayBuffer = [buffer];
+ threw = false;
+ } catch(e) {
+ threw = true;
+ }
+ ok(!threw, `Set sequenceOfAllowSharedArrayBuffer to [${arrayBuffer.type}]`);
+
+ ["Int8Array", "Uint8Array", "Uint8ClampedArray", "Int16Array", "Uint16Array",
+ "Int32Array", "Uint32Array", "Float32Array", "Float64Array", "DataView"].forEach(arrayType => {
+ let array = new self[arrayType](buffer);
+ // Test Not Allow Shared
+ try {
+ test.testNotAllowShared(array);
+ threw = false;
+ } catch(e) {
+ threw = true;
+ }
+ is(threw, arrayBuffer.isShared, `Call testNotAllowShared with ${arrayType} (${arrayBuffer.type})`);
+
+ try {
+ test.testDictWithAllowShared({arrayBufferView: array});
+ threw = false;
+ } catch(e) {
+ threw = true;
+ }
+ is(threw, arrayBuffer.isShared, `Call testDictWithAllowShared with {arrayBufferView: ${arrayType} (${arrayBuffer.type})}`);
+
+ try {
+ test.testUnionOfBuffferSource(array);
+ threw = false;
+ } catch(e) {
+ threw = true;
+ }
+ is(threw, arrayBuffer.isShared, `Call testUnionOfBuffferSource with ${arrayType} (${arrayBuffer.type})`);
+
+ try {
+ test.arrayBufferView = array;
+ threw = false;
+ } catch(e) {
+ threw = true;
+ }
+ is(threw, arrayBuffer.isShared, `Set arrayBufferView to ${arrayType} (${arrayBuffer.type})`);
+
+ try {
+ test.sequenceOfArrayBufferView = [array];
+ threw = false;
+ } catch(e) {
+ threw = true;
+ }
+ is(threw, arrayBuffer.isShared, `Set sequenceOfArrayBufferView to [${arrayType} (${arrayBuffer.type})]`);
+
+ // Test Allow Shared
+ try {
+ test.testAllowShared(array);
+ threw = false;
+ } catch(e) {
+ threw = true;
+ }
+ ok(!threw, `Call testAllowShared with ${arrayType} (${arrayBuffer.type})`);
+
+ try {
+ test.testDictWithAllowShared({allowSharedArrayBufferView: array});
+ threw = false;
+ } catch(e) {
+ threw = true;
+ }
+ ok(!threw, `Call testDictWithAllowShared with {allowSharedArrayBufferView: ${arrayType} (${arrayBuffer.type})}`);
+
+ try {
+ test.testUnionOfAllowSharedBuffferSource(array);
+ threw = false;
+ } catch(e) {
+ threw = true;
+ }
+ ok(!threw, `Call testUnionOfAllowSharedBuffferSource with ${arrayType} (${arrayBuffer.type})`);
+
+ try {
+ test.allowSharedArrayBufferView = array;
+ threw = false;
+ } catch(e) {
+ threw = true;
+ }
+ ok(!threw, `Set allowSharedArrayBufferView to ${arrayType} (${arrayBuffer.type})`);
+
+ try {
+ test.sequenceOfAllowSharedArrayBufferView = [array];
+ threw = false;
+ } catch(e) {
+ threw = true;
+ }
+ ok(!threw, `Set sequenceOfAllowSharedArrayBufferView to [${arrayType} (${arrayBuffer.type})]`);
+ });
+ });
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/bindings/test/test_barewordGetsWindow.html b/dom/bindings/test/test_barewordGetsWindow.html
new file mode 100644
index 0000000000..ddd62fc520
--- /dev/null
+++ b/dom/bindings/test/test_barewordGetsWindow.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=936056
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 936056</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 936056 **/
+ SimpleTest.waitForExplicitFinish();
+ window.onload = function() {
+ var desc = Object.getOwnPropertyDescriptor(frames[0], "document");
+ if (!desc || !desc.get) {
+ todo(false, "This test does nothing so far, but will once Window is on WebIDL bindings");
+ SimpleTest.finish();
+ return;
+ }
+ var get = desc.get;
+ ok(get, "Couldn't find document getter");
+ Object.defineProperty(frames[0], "foo", { get, configurable: true });
+
+ var barewordFunc = frames[0].eval("(function (count) { var doc; for (var i = 0; i < count; ++i) doc = foo; return doc.documentElement; })");
+ var qualifiedFunc = frames[0].eval("(function (count) { var doc; for (var i = 0; i < count; ++i) doc = window.document; return doc.documentElement; })");
+ document.querySelector("iframe").onload = function() {
+ // interp
+ is(barewordFunc(1).innerText, "OLD", "Bareword should see own inner 1");
+ is(qualifiedFunc(1).innerText, "NEW",
+ "Qualified should see current inner 1");
+ // baseline
+ is(barewordFunc(100).innerText, "OLD", "Bareword should see own inner 2");
+ is(qualifiedFunc(100).innerText, "NEW",
+ "Qualified should see current inner 2");
+ // ion
+ is(barewordFunc(10000).innerText, "OLD", "Bareword should see own inner 3");
+ is(qualifiedFunc(10000).innerText, "NEW",
+ "Qualified should see current inner 2");
+ SimpleTest.finish();
+ };
+ frames[0].location = "file_barewordGetsWindow_frame2.html";
+ };
+
+
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=936056">Mozilla Bug 936056</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe src="file_barewordGetsWindow_frame1.html"></iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_bug1036214.html b/dom/bindings/test/test_bug1036214.html
new file mode 100644
index 0000000000..8fbe373b65
--- /dev/null
+++ b/dom/bindings/test/test_bug1036214.html
@@ -0,0 +1,141 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1036214
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1036214</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ /* global TestInterfaceJS */
+
+ /** Test for subsumes-checking |any| and |object| for js-implemented WebIDL. **/
+ SimpleTest.waitForExplicitFinish();
+ var xoObjects = [];
+ function setup() {
+ // window[0] is same-process and cross-origin, even with Fission enabled.
+ xoObjects.push(window[0]);
+ xoObjects.push(window[0].location);
+ xoObjects.push(SpecialPowers.unwrap(SpecialPowers.wrap(window[0]).document));
+ xoObjects.push(SpecialPowers.unwrap(SpecialPowers));
+ xoObjects.push(SpecialPowers.unwrap(SpecialPowers.wrap));
+ SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]}, go);
+ }
+
+ function setup2() {
+ if (SpecialPowers.useRemoteSubframes) {
+ // window[1] is cross-origin and out of process, with Fission enabled.
+ xoObjects.push(window[1]);
+ xoObjects.push(window[1].location);
+ }
+ }
+
+ function checkThrows(f, msg) {
+ try {
+ f();
+ ok(false, "Should have thrown: " + msg);
+ } catch (e) {
+ ok(true, "Threw correctly: " + msg);
+ ok(/denied|insecure/.test(e), "Threw security exception: " + e);
+ }
+ }
+
+ function go() {
+ //
+ // Test the basics of the test interface.
+ //
+
+ var any = { a: 11 };
+ var obj = { b: 22, c: "str" };
+ var obj2 = { foo: "baz" };
+ var myDict = { anyMember: 42, objectMember: { answer: 42 }, objectOrStringMember: { answer: "anobject" },
+ anySequenceMember: [{}, 1, "thirdinsequence"],
+ objectRecordMember: { key: { answer: "fortytwo" } },
+ innerDictionary: { innerObject: { answer: "rabbithole" } } };
+ var t = new TestInterfaceJS(any, obj, myDict);
+ is(Object.getPrototypeOf(t), TestInterfaceJS.prototype, "Prototype setup works correctly");
+ is(t.anyArg, any, "anyArg is correct");
+ is(t.objectArg, obj, "objectArg is correct");
+ is(t.getDictionaryArg().anyMember, 42, "dictionaryArg looks correct");
+ is(t.getDictionaryArg().objectMember.answer, 42, "dictionaryArg looks correct");
+ is(t.getDictionaryArg().objectRecordMember.key.answer, "fortytwo", "dictionaryArg looks correct");
+ is(t.getDictionaryArg().objectRecordMember.key.answer, "fortytwo", "dictionaryArg looks correct");
+ t.anyAttr = 2;
+ is(t.anyAttr, 2, "ping-pong any attribute works");
+ t.objAttr = obj2;
+ is(t.objAttr, obj2, "ping-pong object attribute works");
+ t.setDictionaryAttr(myDict);
+ is(t.getDictionaryAttr().anyMember, 42, "ping-pong dictionary works");
+ is(t.getDictionaryAttr().objectMember.answer, 42, "ping-pong dictionary works");
+ is(t.getDictionaryAttr().objectRecordMember.key.answer, "fortytwo", "ping-pong dictionary works");
+ is(t.getDictionaryAttr().objectRecordMember.key.answer, "fortytwo", "ping-pong dictionary works");
+
+ is(any, t.pingPongAny(any), "ping-pong works with any");
+ is(obj, t.pingPongObject(obj), "ping-pong works with obj");
+ is(obj, t.pingPongObjectOrString(obj), "ping-pong works with obj or string");
+ is("foo", t.pingPongObjectOrString("foo"), "ping-pong works with obj or string");
+ is(t.pingPongDictionary(myDict).anyMember, 42, "ping pong works with dict");
+ is(t.pingPongDictionary(myDict).objectMember.answer, 42, "ping pong works with dict");
+ is(t.pingPongDictionary(myDict).objectOrStringMember.answer, "anobject", "ping pong works with dict");
+ is(t.pingPongDictionary(myDict).anySequenceMember[2], "thirdinsequence", "ping pong works with dict");
+ is(t.pingPongDictionary(myDict).objectRecordMember.key.answer, "fortytwo", "ping pong works with dict");
+ is(t.pingPongDictionary(myDict).objectRecordMember.key.answer, "fortytwo", "ping pong works with dict");
+ is(t.pingPongDictionary(myDict).innerDictionary.innerObject.answer, "rabbithole", "ping pong works with layered dicts");
+ is(t.pingPongDictionaryOrLong({anyMember: 42}), 42, "ping pong (dict or long) works with dict");
+ is(t.pingPongDictionaryOrLong(42), 42, "ping pong (dict or long) works with long");
+ ok(/canary/.test(t.pingPongRecord({ someVal: 42, someOtherVal: "canary" })), "ping pong works with record");
+ is(t.objectSequenceLength([{}, {}, {}]), 3, "ping pong works with object sequence");
+ is(t.anySequenceLength([42, "string", {}, undefined]), 4, "ping pong works with any sequence");
+
+ //
+ // Test that we throw in the cross-origin cases.
+ //
+
+ xoObjects.forEach(function(xoObj) {
+ new TestInterfaceJS();
+ checkThrows(() => new TestInterfaceJS(xoObj, undefined), "any param for constructor");
+ checkThrows(() => new TestInterfaceJS(undefined, xoObj), "obj param for constructor");
+ checkThrows(() => new TestInterfaceJS(undefined, undefined, { anyMember: xoObj }), "any dict param for constructor");
+ checkThrows(() => new TestInterfaceJS(undefined, undefined, { objectMember: xoObj }), "object dict param for constructor");
+ checkThrows(() => new TestInterfaceJS(undefined, undefined, { objectOrStringMember: xoObj }), "union dict param for constructor");
+ checkThrows(() => new TestInterfaceJS(undefined, undefined, { anySequenceMember: [0, xoObj, "hi" ] }), "sequence dict param for constructor");
+ checkThrows(() => new TestInterfaceJS(undefined, undefined, { innerDictionary: { innerObject: xoObj } }), "inner dict param for constructor");
+ checkThrows(() => t.anyAttr = xoObj, "anyAttr");
+ checkThrows(() => t.objectAttr = xoObj, "objAttr");
+ checkThrows(() => t.setDictionaryAttr({ anyMember: xoObj }), "dictionaryAttr any");
+ checkThrows(() => t.setDictionaryAttr({ objectMember: xoObj }), "dictionaryAttr object");
+ checkThrows(() => t.pingPongAny(xoObj), "pingpong any");
+ checkThrows(() => t.pingPongObject(xoObj), "pingpong obj");
+ checkThrows(() => t.pingPongObjectOrString(xoObj), "pingpong union");
+ checkThrows(() => t.pingPongDictionary({ anyMember: xoObj }), "dictionary pingpong any");
+ checkThrows(() => t.pingPongDictionary({ objectMember: xoObj }), "dictionary pingpong object");
+ checkThrows(() => t.pingPongDictionary({ anyMember: xoObj, objectMember: xoObj }), "dictionary pingpong both");
+ checkThrows(() => t.pingPongDictionary({ objectOrStringMember: xoObj }), "dictionary pingpong objectorstring");
+ checkThrows(() => t.pingPongDictionary({ objectRecordMember: { key: xoObj } }), "dictionary pingpong record of object");
+ checkThrows(() => t.pingPongDictionaryOrLong({ objectMember: xoObj }), "unionable dictionary");
+ checkThrows(() => t.pingPongDictionaryOrLong({ anyMember: xoObj }), "unionable dictionary");
+ checkThrows(() => t.pingPongRecord({ someMember: 42, someOtherMember: {}, crossOriginMember: xoObj }), "record");
+ checkThrows(() => t.objectSequenceLength([{}, {}, xoObj, {}]), "object sequence");
+ checkThrows(() => t.anySequenceLength([42, "someString", xoObj, {}]), "any sequence");
+ });
+
+
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1036214">Mozilla Bug 1036214</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<iframe id="ifr" onload="setup();" src="http://test1.mochi.test:8888/tests/js/xpconnect/tests/mochitest/file_empty.html"></iframe>
+<iframe id="ifr2" onload="setup2();" src="http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html"></iframe>
+</body>
+</html>
diff --git a/dom/bindings/test/test_bug1041646.html b/dom/bindings/test/test_bug1041646.html
new file mode 100644
index 0000000000..95550a98e2
--- /dev/null
+++ b/dom/bindings/test/test_bug1041646.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1041646
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1041646</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1041646 **/
+ // We need to reject the promise with a DOMException, so make sure we have
+ // something that produces one.
+ function throwException() {
+ document.createTextNode("").appendChild(document);
+ }
+ try {
+ throwException();
+ } catch (e) {
+ ok(e instanceof DOMException, "This test won't test what it should be testing");
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ // We want a new DOMException each time here.
+ for (var i = 0; i < 100; ++i) {
+ new Promise(throwException);
+ }
+
+ // Now make sure we wait for all those promises above to reject themselves
+ Promise.resolve(1).then(function() {
+ SpecialPowers.gc(); // This should not assert or crash
+ SimpleTest.finish();
+ });
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1041646">Mozilla Bug 1041646</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_bug1123516_maplikesetlike.html b/dom/bindings/test/test_bug1123516_maplikesetlike.html
new file mode 100644
index 0000000000..bc5048eb05
--- /dev/null
+++ b/dom/bindings/test/test_bug1123516_maplikesetlike.html
@@ -0,0 +1,278 @@
+<!-- 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 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">
+ /* global TestInterfaceMaplike, TestInterfaceSetlike, TestInterfaceMaplikeObject, TestInterfaceMaplikeJSObject*/
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]}, function() {
+ var base_properties = [["has", "function", 1],
+ ["entries", "function", 0],
+ ["keys", "function", 0],
+ ["values", "function", 0],
+ ["forEach", "function", 1],
+ ["size", "number"]];
+ var maplike_properties = base_properties.concat([["set", "function", 2]]);
+ var rw_properties = [["clear", "function", 0],
+ ["delete", "function", 1]];
+ var setlike_rw_properties = base_properties.concat(rw_properties).concat([["add", "function", 1]]);
+ var 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} should be enumerable`);
+ break;
+ }
+ owner = Object.getPrototypeOf(owner);
+ }
+ }
+ };
+
+ var m;
+ var testSet;
+ var testIndex;
+
+ // 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");
+ var 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");
+ 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");
+ var iterable = false;
+ for (let 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, "entries", "SimpleMap: @@iterator symbol has correct name");
+ is(m[Symbol.iterator], m.entries, 'SimpleMap: @@iterator is an alias for "entries"');
+ ok(iterable, "SimpleMap: @@iterator symbol resolved correctly");
+ for (let k of m.keys()) {
+ is(k, "test", "SimpleMap: first keys element should be 'test'");
+ }
+ for (let v of m.values()) {
+ is(v, 1, "SimpleMap: first values elements should be 1");
+ }
+ for (let 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");
+ 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 (let 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, "values", "SimpleSet: @@iterator symbol has correct name");
+ is(m[Symbol.iterator], m.values, 'SimpleSet: @@iterator is an alias for "values"');
+ ok(iterable, "SimpleSet: @@iterator symbol resolved correctly");
+ for (let k of m.keys()) {
+ is(k, "test", "SimpleSet: first keys element should be 'test'");
+ }
+ for (let v of m.values()) {
+ is(v, "test", "SimpleSet: first values elements should be 'test'");
+ }
+ for (let 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");
+ // It's fine to let getInternal to return 0 if the key doesn't exist
+ // because this API can only be used internally in C++ and we'd throw
+ // an error if the key doesn't exist.
+ SimpleTest.doesThrow(() => m.getInternal("test"), 0, "MapConvenience: maplike getInternal should throw if the key doesn't exist");
+ 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");
+ is(m.getInternal("test"), 1, "MapConvenience: maplike getInternal should return value entered");
+ 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.getInternal("test2"), 2, "MapConvenience: maplike getInternal 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");
+ SimpleTest.doesThrow(() => m.getInternal("test"), "ReadOnlyMapConvenience: maplike getInternal should throw when the key doesn't exist");
+ m.setInternal("test");
+ is(m.size, 1, "size should be 1");
+ ok(m.hasInternal("test"), "ReadOnlyMapConvenience: maplike hasInternal should return true");
+ ok(m.getInternal("test") instanceof TestInterfaceMaplike, "ReadOnlyMapConvenience: maplike getInternal should return the object");
+ m.setInternal("test2");
+ is(m.size, 2, "size should be 2");
+ ok(m.hasInternal("test2"), "ReadOnlyMapConvenience: maplike hasInternal should return true");
+ ok(m.getInternal("test2") instanceof TestInterfaceMaplike, "ReadOnlyMapConvenience: maplike getInternal should return the object");
+ 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");
+
+ // Map convenience function test using JavaScript objects
+ info("Testing Map convenience function test using javascript objects");
+ m = new TestInterfaceMaplikeJSObject();
+ ok(m, "JSObjectMapConvenience: got a TestInterfaceMaplikeJSObject object");
+ is(m.size, 0, "JSObjectMapConvenience: size should be zero");
+ is(m.set, undefined, "JSObjectMapConvenience: readonly map, should be no set function");
+ is(m.clear, undefined, "JSObjectMapConvenience: readonly map, should be no clear function");
+ is(m.delete, undefined, "JSObjectMapConvenience: readonly map, should be no delete function");
+ ok(!m.hasInternal("test"), "JSObjectMapConvenience: maplike hasInternal should return false");
+ SimpleTest.doesThrow(() => m.getInternal("test"), "JSObjectMapConvenience: maplike getInternal should throw when the key doesn't exist");
+ let testObject = {"Hey1": 1};
+ m.setInternal("test", testObject);
+ is(m.size, 1, "size should be 1");
+ ok(m.hasInternal("test"), "JSObjectMapConvenience: maplike hasInternal should return true");
+ let addedObject = m.getInternal("test");
+ is(addedObject, testObject, "JSObjectMapConvenience: maplike getInternal should return the object");
+ testObject = {"Hey2": 2};
+ m.setInternal("test2", testObject);
+ is(m.size, 2, "size should be 2");
+ ok(m.hasInternal("test2"), "JSObjectMapConvenience: maplike hasInternal should return true");
+ addedObject = m.getInternal("test2");
+ is(addedObject, testObject, "JSObjectMapConvenience: maplike getInternal should return the object");
+ is(m.deleteInternal("test2"), true, "JSObjectMapConvenience: maplike deleteInternal should return true");
+ is(m.size, 1, "JSObjectMapConvenience: size should be 1");
+ m.clearInternal();
+ is(m.size, 0, "JSObjectMapConvenience: size should be 0 after clearInternal");
+ // JS implemented map creation convenience function test
+
+ // 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>
diff --git a/dom/bindings/test/test_bug1123516_maplikesetlikechrome.xhtml b/dom/bindings/test/test_bug1123516_maplikesetlikechrome.xhtml
new file mode 100644
index 0000000000..724c40cbd3
--- /dev/null
+++ b/dom/bindings/test/test_bug1123516_maplikesetlikechrome.xhtml
@@ -0,0 +1,70 @@
+<?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 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[
+ /* global TestInterfaceSetlikeNode */
+
+ /** Test for Bug 1123516 **/
+ function doTest() {
+ var win = $("t").contentWindow;
+ var sandbox = Cu.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 = Cu.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 = Cu.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 = Cu.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 {
+ TestInterfaceSetlikeNode.prototype.keys.call(setlike);
+ ok(false, "Calling iterators via xrays should fail");
+ /* eslint-disable-next-line no-shadow */
+ } 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");
+ /* eslint-disable-next-line no-shadow */
+ } catch(e) {
+ ok(false, "Shouldn't throw when working with cross-compartment maplike/setlike interfaces " + e)
+ };
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(doTest);
+ ]]>
+ </script>
+</window>
diff --git a/dom/bindings/test/test_bug1123875.html b/dom/bindings/test/test_bug1123875.html
new file mode 100644
index 0000000000..383d14fe9f
--- /dev/null
+++ b/dom/bindings/test/test_bug1123875.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test for Bug 1123875</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+/* global test, assert_throws */
+ test(() => {
+ assert_throws(new TypeError, () => {
+ "use strict";
+ document.childNodes.length = 0;
+ });
+ }, "setting a readonly attribute on a proxy in strict mode should throw a TypeError");
+</script>
diff --git a/dom/bindings/test/test_bug1287912.html b/dom/bindings/test/test_bug1287912.html
new file mode 100644
index 0000000000..310a53afc6
--- /dev/null
+++ b/dom/bindings/test/test_bug1287912.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1287912
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1287912</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1287912">Mozilla Bug 1287912</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id="t" src="http://example.org/tests/dom/bindings/test/"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+function test() {
+ var win = document.getElementById("t").contentWindow;
+ is(Object.getPrototypeOf(win.Image), win.Function.prototype, "The __proto__ of a named constructor is Function.prototype");
+ is(win.Image.prototype, win.HTMLImageElement.prototype, "The prototype property of a named constructor is the interface prototype object");
+ is(win.HTMLImageElement.foo, undefined, "Should not have a property named foo on the HTMLImageElement interface object");
+ is(win.Image.foo, undefined, "Should not have a property named foo on the Image named constructor");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_bug1457051.html b/dom/bindings/test/test_bug1457051.html
new file mode 100644
index 0000000000..2ed1bd16ec
--- /dev/null
+++ b/dom/bindings/test/test_bug1457051.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1457051
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1457051</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1457051 **/
+ ok(Element.isInstance(document.documentElement), "Basic isInstance works");
+ ok(!Element.isInstance(null),
+ "Passing null should return false without throwing");
+ ok(!Element.isInstance(5), "Passing 5 should return false without throwing");
+ var obj = Object.create(Element.prototype);
+ ok(obj instanceof Element, "instanceof should walk the proto chain");
+ ok(!Element.isInstance(obj), "isInstance should be a pure brand check");
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1457051">Mozilla Bug 1457051</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_bug1808352.html b/dom/bindings/test/test_bug1808352.html
new file mode 100644
index 0000000000..aa1dc133b8
--- /dev/null
+++ b/dom/bindings/test/test_bug1808352.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test for Bug 1808352</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<div id=log></div>
+<div id="testnode"></div>
+<script>
+function loadedFrame() {
+ var node = document.getElementById("testnode");
+ var frame = document.getElementById("frame");
+ frame.contentWindow.document.adoptNode(node);
+ document.adoptNode(node);
+ var res;
+ // Use a loop to ensure the JITs optimize the `id` getter access.
+ for (var i = 0; i < 10_000; i++) {
+ res = node.id;
+ }
+ is(res, "testnode", "expected node id");
+ SimpleTest.finish();
+}
+SimpleTest.waitForExplicitFinish();
+document.domain = "mochi.test"; // We want a cross-compartment adoptNode.
+</script>
+<iframe id="frame" src="http://test1.mochi.test:8888/tests/dom/bindings/test/file_bug1808352_frame.html"></iframe>
diff --git a/dom/bindings/test/test_bug1808352b.html b/dom/bindings/test/test_bug1808352b.html
new file mode 100644
index 0000000000..c3b95f62f9
--- /dev/null
+++ b/dom/bindings/test/test_bug1808352b.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test for Bug 1808352</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<div id=log></div>
+<div id="testnode"></div>
+<script>
+var w;
+function loadedFrame() {
+ var node = document.getElementById("testnode");
+ w.document.adoptNode(node);
+ document.adoptNode(node);
+ var res;
+ // Use a loop to ensure the JITs optimize the `id` getter access.
+ for (var i = 0; i < 10_000; i++) {
+ res = node.id;
+ }
+ is(res, "testnode", "expected node id");
+ w.close();
+ SimpleTest.finish();
+}
+SimpleTest.waitForExplicitFinish();
+// Open a popup window, so it'll be same origin, but cross compartment.
+w = window.open("file_bug1808352b_frame.html", "_blank");
+</script>
diff --git a/dom/bindings/test/test_bug560072.html b/dom/bindings/test/test_bug560072.html
new file mode 100644
index 0000000000..de2f8baec9
--- /dev/null
+++ b/dom/bindings/test/test_bug560072.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=560072
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 560072</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=560072">Mozilla Bug 560072</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 560072 **/
+is(document.body,
+ Object.getOwnPropertyDescriptor(Document.prototype, "body").get.call(document),
+ "Should get body out of property descriptor");
+
+is(document.body,
+ Object.getOwnPropertyDescriptor(
+ Object.getPrototypeOf(Object.getPrototypeOf(document)), "body").get.call(document),
+ "Should get body out of property descriptor this way too");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_bug742191.html b/dom/bindings/test/test_bug742191.html
new file mode 100644
index 0000000000..f991bf6f91
--- /dev/null
+++ b/dom/bindings/test/test_bug742191.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=742191
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for invalid argument object</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=742191">Mozilla Bug 742191</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 742191 **/
+function doTest() {
+ var gotTypeError = false;
+ var ctx = document.createElement("canvas").getContext("2d");
+ try {
+ ctx.drawImage({}, 0, 0);
+ } catch (e) {
+ if (e instanceof TypeError) {
+ gotTypeError = true;
+ }
+ }
+
+ ok(gotTypeError, "passing an invalid argument should cause a type error!");
+}
+doTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_bug759621.html b/dom/bindings/test/test_bug759621.html
new file mode 100644
index 0000000000..7c47887d35
--- /dev/null
+++ b/dom/bindings/test/test_bug759621.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=759621
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 759621</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=759621">Mozilla Bug 759621</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 759621 **/
+var l = document.getElementsByTagName("*");
+l.namedItem = "pass";
+is(l.namedItem, "pass", "Should be able to set expando shadowing a proto prop");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_bug773326.html b/dom/bindings/test/test_bug773326.html
new file mode 100644
index 0000000000..cb21ae6916
--- /dev/null
+++ b/dom/bindings/test/test_bug773326.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test for Bug 773326</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+/* global test */
+
+test(function() {
+ new Worker("data:text/javascript,new XMLHttpRequest(42)");
+}, "Should not crash");
+</script>
diff --git a/dom/bindings/test/test_bug775543.html b/dom/bindings/test/test_bug775543.html
new file mode 100644
index 0000000000..d8fe8aaf2b
--- /dev/null
+++ b/dom/bindings/test/test_bug775543.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=775543
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 775543</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=775543">Mozilla Bug 775543</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id="t" src="http://example.org/tests/dom/bindings/test/file_bug775543.html" onload="test();"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+/* global XPCNativeWrapper */
+/** Test for Bug 775543 **/
+
+function test() {
+ var a = XPCNativeWrapper(document.getElementById("t").contentWindow.wrappedJSObject.worker);
+ isnot(XPCNativeWrapper.unwrap(a), a, "XPCNativeWrapper(Worker) should be an Xray wrapper");
+ a.toString();
+ ok(true, "Shouldn't crash when calling a method on an Xray wrapper around a worker");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_bug788369.html b/dom/bindings/test/test_bug788369.html
new file mode 100644
index 0000000000..02d03ac199
--- /dev/null
+++ b/dom/bindings/test/test_bug788369.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=788369
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 788369</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=788369">Mozilla Bug 788369</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 788369 **/
+try {
+ var xhr = new (window.ActiveXObject || XMLHttpRequest)("Microsoft.XMLHTTP");
+ ok(xhr instanceof XMLHttpRequest, "Should have an XHR object");
+} catch (e) {
+ ok(false, "Should not throw exception when constructing: " + e);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_bug852846.html b/dom/bindings/test/test_bug852846.html
new file mode 100644
index 0000000000..19ec39dae5
--- /dev/null
+++ b/dom/bindings/test/test_bug852846.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=852846
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 852846</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 852846 **/
+ var elem = document.createElement("div");
+ is(elem.style.color, "", "Shouldn't have color set on HTML element");
+ elem.style = "color: green";
+ is(elem.style.color, "green", "Should have color set on HTML element");
+
+ elem = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ is(elem.style.color, "", "Shouldn't have color set on SVG element");
+ elem.style = "color: green";
+ is(elem.style.color, "green", "Should have color set on SVG element");
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=852846">Mozilla Bug 852846</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_bug862092.html b/dom/bindings/test/test_bug862092.html
new file mode 100644
index 0000000000..871252660a
--- /dev/null
+++ b/dom/bindings/test/test_bug862092.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=862092
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 862092</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 862092 **/
+
+ SimpleTest.waitForExplicitFinish();
+ function runTest() {
+ var frameDoc = document.getElementById("f").contentDocument;
+ var a = document.createElement("select");
+ a.expando = "test";
+ a = frameDoc.adoptNode(a);
+ is(a.expando, "test", "adoptNode needs to preserve expandos");
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body onload="runTest();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=862092">Mozilla Bug 862092</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id="f"></iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_callback_across_document_open.html b/dom/bindings/test/test_callback_across_document_open.html
new file mode 100644
index 0000000000..dee4445904
--- /dev/null
+++ b/dom/bindings/test/test_callback_across_document_open.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for callback invocation for a callback that comes from a
+ no-longer-current window that still has an active document.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<iframe srcdoc='<script>function f() { parent.callCount++; }</script>'></iframe>
+<script>
+/* global async_test, assert_equals */
+ var callCount = 0;
+ var t = async_test("A test of callback invocation in a no-longer-current window with a still-active document");
+ window.addEventListener("load", t.step_func_done(function() {
+ var d = document.createElement("div");
+ d.addEventListener("xyz", frames[0].f);
+ frames[0].document.open();
+ frames[0].document.write("All gone");
+ frames[0].document.close();
+ d.dispatchEvent(new Event("xyz"));
+ assert_equals(callCount, 1, "Callback should have been called");
+ }));
+</script>
diff --git a/dom/bindings/test/test_callback_default_thisval.html b/dom/bindings/test/test_callback_default_thisval.html
new file mode 100644
index 0000000000..337657b21f
--- /dev/null
+++ b/dom/bindings/test/test_callback_default_thisval.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=957929
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 957929</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 957929 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function f() {
+ "use strict";
+ is(this, undefined, "Should have undefined this value");
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(function() {
+ requestAnimationFrame(f);
+ });
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=957929">Mozilla Bug 957929</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_callback_exceptions.html b/dom/bindings/test/test_callback_exceptions.html
new file mode 100644
index 0000000000..93d4787774
--- /dev/null
+++ b/dom/bindings/test/test_callback_exceptions.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for ...</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+/* global promise_test, promise_rejects */
+
+promise_test(function(t) {
+ var iterator = document.createNodeIterator(document, NodeFilter.SHOW_ALL, JSON.parse);
+ return promise_rejects(t, new SyntaxError,
+ Promise.resolve().then(iterator.nextNode.bind(iterator)));
+}, "Trying to use JSON.parse as filter should throw a catchable SyntaxError exception even when the filter is invoked async");
+
+promise_test(function(t) {
+ return promise_rejects(t, new SyntaxError, Promise.resolve("{").then(JSON.parse));
+}, "Trying to use JSON.parse as a promise callback should allow the next promise to handle the resulting exception.");
+</script>
diff --git a/dom/bindings/test/test_cloneAndImportNode.html b/dom/bindings/test/test_cloneAndImportNode.html
new file mode 100644
index 0000000000..4acfec82cd
--- /dev/null
+++ b/dom/bindings/test/test_cloneAndImportNode.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=882541
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 882541</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 882541 **/
+ var div = document.createElement("div");
+ div.appendChild(document.createElement("span"));
+
+ var div2;
+
+ div2 = div.cloneNode();
+ is(div2.childNodes.length, 0, "cloneNode() should do a shallow clone");
+
+ div2 = div.cloneNode(undefined);
+ is(div2.childNodes.length, 0, "cloneNode(undefined) should do a shallow clone");
+
+ div2 = div.cloneNode(true);
+ is(div2.childNodes.length, 1, "cloneNode(true) should do a deep clone");
+
+ div2 = document.importNode(div);
+ is(div2.childNodes.length, 0, "importNode(node) should do a deep import");
+
+ div2 = document.importNode(div, undefined);
+ is(div2.childNodes.length, 0, "importNode(undefined) should do a shallow import");
+
+ div2 = document.importNode(div, true);
+ is(div2.childNodes.length, 1, "importNode(true) should do a deep import");
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=882541">Mozilla Bug 882541</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_crossOriginWindowSymbolAccess.html b/dom/bindings/test/test_crossOriginWindowSymbolAccess.html
new file mode 100644
index 0000000000..0ece6c8f9d
--- /dev/null
+++ b/dom/bindings/test/test_crossOriginWindowSymbolAccess.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for accessing symbols on a cross-origin window</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<iframe id="sameProc" src="http://test1.mochi.test:8888/tests/js/xpconnect/tests/mochitest/file_empty.html"></iframe>
+<iframe id="crossProc" src="http://www1.w3c-test.org/common/blank.html"></iframe>
+<script>
+/* global async_test, assert_equals, assert_throws */
+
+async_test(function(t) {
+ window.addEventListener("load", t.step_func(
+ function() {
+ assert_equals(document.getElementById("sameProc").contentDocument, null, "Should have a crossorigin frame");
+ assert_equals(document.getElementById("crossProc").contentDocument, null, "Should have a crossorigin frame");
+ for (let f of [0, 1]) {
+ assert_throws("SecurityError", function() {
+ frames[f][Symbol.iterator];
+ }, "Should throw exception on cross-origin Window symbol-named get");
+ assert_throws("SecurityError", function() {
+ frames[f].location[Symbol.iterator];
+ }, "Should throw exception on cross-origin Location symbol-named get");
+ }
+ t.done();
+ }
+ ));
+}, "Check Symbol access on load");
+</script>
diff --git a/dom/bindings/test/test_defineProperty.html b/dom/bindings/test/test_defineProperty.html
new file mode 100644
index 0000000000..391c6fcac8
--- /dev/null
+++ b/dom/bindings/test/test_defineProperty.html
@@ -0,0 +1,157 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=910220
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 910220</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=910220">Mozilla Bug 910220</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<form name="x"></form>
+</div>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+
+/** Test for Bug 910220 **/
+
+function getX() {
+ return "x";
+}
+
+function namedSetStrict(obj) {
+ "use strict";
+ var threw;
+ try {
+ obj.x = 5;
+ threw = false;
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw,
+ "Should throw in strict mode when setting named property on " + obj);
+
+ try {
+ obj[getX()] = 5;
+ threw = false;
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw,
+ "Should throw in strict mode when setting named property via SETELEM on " + obj);
+
+ try {
+ Object.defineProperty(obj, "x", { value: 17 });
+ threw = false;
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw,
+ "Should throw in strict mode when defining named property on " + obj);
+}
+function namedSetNonStrict(obj) {
+ var threw;
+ try {
+ obj.x = 5;
+ threw = false;
+ } catch (e) {
+ threw = true;
+ }
+ ok(!threw,
+ "Should not throw in non-strict mode when setting named property on " + obj);
+
+ try {
+ obj[getX()] = 5;
+ threw = false;
+ } catch (e) {
+ threw = true;
+ }
+ ok(!threw,
+ "Should not throw in non-strict mode when setting named property via SETELEM on" + obj);
+
+ try {
+ Object.defineProperty(obj, "x", { value: 17 });
+ threw = false;
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw,
+ "Should throw in non-strict mode when defining named property on " + obj);
+}
+for (let obj of [ document, document.forms ]) {
+ namedSetStrict(obj);
+ namedSetNonStrict(obj);
+}
+
+function indexedSetStrict(obj) {
+ "use strict";
+ var threw;
+ try {
+ obj[0] = 5;
+ threw = false;
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw,
+ "Should throw in strict mode when setting indexed property on " + obj);
+
+ try {
+ obj[1000] = 5;
+ threw = false;
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw,
+ "Should throw in strict mode when setting out of bounds indexed property on " + obj);
+
+ try {
+ Object.defineProperty(obj, "0", { value: 17 });
+ threw = false;
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw,
+ "Should throw in strict mode when defining indexed property on " + obj);
+}
+function indexedSetNonStrict(obj) {
+ var threw;
+ try {
+ obj[0] = 5;
+ threw = false;
+ } catch (e) {
+ threw = true;
+ }
+ ok(!threw,
+ "Should not throw in non-strict mode when setting indexed property on " + obj);
+
+ try {
+ obj[1000] = 5;
+ threw = false;
+ } catch (e) {
+ threw = true;
+ }
+ ok(!threw,
+ "Should not throw in non-strict mode when setting out of bounds indexed property on " + obj);
+
+ try {
+ Object.defineProperty(obj, "0", { value: 17 });
+ threw = false;
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw,
+ "Should throw in non-strict mode when defining indexed property on " + obj);
+}
+for (let obj of [ document.forms, document.childNodes ]) {
+ indexedSetStrict(obj);
+ indexedSetNonStrict(obj);
+}
+</script>
+</body>
+</html>
diff --git a/dom/bindings/test/test_document_location_set_via_xray.html b/dom/bindings/test/test_document_location_set_via_xray.html
new file mode 100644
index 0000000000..2c8d01aeda
--- /dev/null
+++ b/dom/bindings/test/test_document_location_set_via_xray.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=905493
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 905493</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=905493">Mozilla Bug 905493</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id="t" src="http://example.org/tests/dom/bindings/test/file_document_location_set_via_xray.html"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 905493 **/
+
+function test() {
+ var doc = document.getElementById("t").contentWindow.document;
+ ok(!("x" in doc), "Should have an Xray here");
+ is(doc.x, undefined, "Really should have an Xray here");
+ is(doc.wrappedJSObject.x, 5, "And wrapping the right thing");
+ document.getElementById("t").onload = function() {
+ ok(true, "Load happened");
+ SimpleTest.finish();
+ };
+ try {
+ // Test the forwarding location setter
+ doc.location = "chrome://mochikit/content/tests/SimpleTest/test.css";
+ } catch (e) {
+ // Load failed
+ ok(false, "Load failed");
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_document_location_via_xray_cached.html b/dom/bindings/test/test_document_location_via_xray_cached.html
new file mode 100644
index 0000000000..f47f78cd55
--- /dev/null
+++ b/dom/bindings/test/test_document_location_via_xray_cached.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1041731
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1041731</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1041731">Mozilla Bug 1041731</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id="t" src="http://example.org/tests/dom/bindings/test/file_document_location_set_via_xray.html"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1041731 **/
+
+function test() {
+ var loc = document.getElementById("t").contentWindow.document.location;
+ is(loc.toString, loc.toString, "Unforgeable method on the Xray should be cached");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_domProxyArrayLengthGetter.html b/dom/bindings/test/test_domProxyArrayLengthGetter.html
new file mode 100644
index 0000000000..e2ce1fb870
--- /dev/null
+++ b/dom/bindings/test/test_domProxyArrayLengthGetter.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1221421
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1221421</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript">
+
+ var x = document.documentElement.style;
+ x.__proto__ = [1, 2, 3];
+
+ var res = 0;
+ for (var h = 0; h < 5000; ++h) {
+ res += x.length;
+ }
+ is(res, 15000, "length getter should return array length");
+
+ </script>
+</head>
+
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1221421">Mozilla Bug 1221421</a>
+</body>
+</html>
diff --git a/dom/bindings/test/test_dom_xrays.html b/dom/bindings/test/test_dom_xrays.html
new file mode 100644
index 0000000000..e89f705c33
--- /dev/null
+++ b/dom/bindings/test/test_dom_xrays.html
@@ -0,0 +1,383 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=787070
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 787070</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=787070">Mozilla Bug 787070</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id="t" src="http://example.org/tests/dom/bindings/test/file_dom_xrays.html"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1021066 **/
+
+// values should contain the values that the property should have on each of
+// the objects on the prototype chain of obj. A value of undefined signals
+// that the value should not be present on that prototype.
+function checkXrayProperty(obj, name, values) {
+ var instance = obj;
+ do {
+ var value = values.shift();
+ if (typeof value == "undefined") {
+ ok(!obj.hasOwnProperty(name), "hasOwnProperty shouldn't see \"" + String(name) + "\" through Xrays");
+ is(Object.getOwnPropertyDescriptor(obj, name), undefined, "getOwnPropertyDescriptor shouldn't see \"" + String(name) + "\" through Xrays");
+ ok(!Object.keys(obj).includes(name), "Enumerating the Xray should not return \"" + String(name) + "\"");
+ ok(!Object.getOwnPropertyNames(obj).includes(name),
+ `The Xray's property names should not include ${String(name)}`);
+ ok(!Object.getOwnPropertySymbols(obj).includes(name),
+ `The Xray's property symbols should not include ${String(name)}`);
+ } else {
+ ok(obj.hasOwnProperty(name), "hasOwnProperty should see \"" + String(name) + "\" through Xrays");
+ var pd = Object.getOwnPropertyDescriptor(obj, name);
+ ok(pd, "getOwnPropertyDescriptor should see \"" + String(name) + "\" through Xrays");
+ if (pd && pd.get) {
+ is(pd.get.call(instance), value, "Should get the right value for \"" + String(name) + "\" through Xrays");
+ } else {
+ is(obj[name], value, "Should get the right value for \"" + String(name) + "\" through Xrays");
+ }
+ if (pd) {
+ if (pd.enumerable) {
+ ok(Object.keys(obj).indexOf("" + name) > -1, "Enumerating the Xray should return \"" + String(name) + "\"");
+ }
+ if (typeof name == "symbol") {
+ ok(Object.getOwnPropertySymbols(obj).includes(name),
+ `The Xray's property symbols should include ${String(name)}`);
+ } else {
+ ok(Object.getOwnPropertyNames(obj).includes("" + name),
+ `The Xray's property names should include ${name}`);
+ }
+ }
+ }
+ } while ((obj = Object.getPrototypeOf(obj)));
+}
+
+function checkWindowXrayProperty(win, name, { windowValue, windowPrototypeValue, namedPropertiesValue, eventTargetPrototypeValue }) {
+ checkXrayProperty(win, name, [ windowValue, windowPrototypeValue, namedPropertiesValue, eventTargetPrototypeValue ]);
+}
+function checkDocumentXrayProperty(doc, name, { documentValue, htmlDocumentPrototypeValue, documentPrototypeValue, nodePrototypeValue, eventTargetPrototypeValue }) {
+ checkXrayProperty(doc, name, [ documentValue, htmlDocumentPrototypeValue, documentPrototypeValue, nodePrototypeValue, eventTargetPrototypeValue ]);
+}
+
+function test() {
+ // Window
+ var win = document.getElementById("t").contentWindow;
+ var doc = document.getElementById("t").contentDocument;
+
+ var winProto = Object.getPrototypeOf(win);
+ is(winProto, win.Window.prototype, "The proto chain of the Xray should mirror the prototype chain of the Xrayed object");
+
+ var namedPropertiesObject = Object.getPrototypeOf(winProto);
+ is(Cu.getClassName(namedPropertiesObject, /* unwrap = */ true), "WindowProperties", "The proto chain of the Xray should mirror the prototype chain of the Xrayed object");
+
+ var eventTargetProto = Object.getPrototypeOf(namedPropertiesObject);
+ is(eventTargetProto, win.EventTarget.prototype, "The proto chain of the Xray should mirror the prototype chain of the Xrayed object");
+
+ let docProto = Object.getPrototypeOf(doc);
+ is(docProto, win.HTMLDocument.prototype, "The proto chain of the Xray should mirror the prototype chain of the Xrayed object");
+
+ // Xrays need to filter expandos.
+ checkDocumentXrayProperty(doc, "expando", {});
+ ok(!("expando" in doc), "Xrays should filter expandos");
+
+ checkDocumentXrayProperty(doc, "shadowedIframe", {});
+ ok(!("shadowedIframe" in doc), "Named properties should not be exposed through Xrays");
+
+ // Named properties live on the named properties object for global objects,
+ // but are not exposed via Xrays.
+ checkWindowXrayProperty(win, "iframe", {});
+ ok(!("iframe" in win), "Named properties should not be exposed through Xrays");
+
+ // Window properties live on the instance, shadowing the properties of the named property object.
+ checkWindowXrayProperty(win, "document", { windowValue: doc });
+ ok("document" in win, "WebIDL properties should be exposed through Xrays");
+
+ // Unforgeable properties live on the instance, shadowing the properties of the named property object.
+ checkWindowXrayProperty(win, "self", { windowValue: win });
+ ok("self" in win, "WebIDL properties should be exposed through Xrays");
+
+ // Named properties live on the instance for non-global objects, but are not
+ // exposed via Xrays.
+ checkDocumentXrayProperty(doc, "iframe", {});
+ ok(!("iframe" in doc), "Named properties should not be exposed through Xrays");
+
+ // Object.prototype is at the end of the prototype chain.
+ var obj = win;
+ var proto;
+ while ((proto = Object.getPrototypeOf(obj))) {
+ obj = proto;
+ }
+ is(obj, win.Object.prototype, "Object.prototype should be at the end of the prototype chain");
+
+ // Named properties shouldn't shadow WebIDL- or ECMAScript-defined properties.
+ checkWindowXrayProperty(win, "addEventListener", { eventTargetPrototypeValue: eventTargetProto.addEventListener });
+ is(win.addEventListener, eventTargetProto.addEventListener, "Named properties shouldn't shadow WebIDL-defined properties");
+
+ is(win.toString, win.Object.prototype.toString, "Named properties shouldn't shadow ECMAScript-defined properties");
+
+ // WebIDL interface names should be exposed.
+ var waivedWin = Cu.waiveXrays(win);
+ checkWindowXrayProperty(win, "Element", { windowValue: Cu.unwaiveXrays(waivedWin.Element) });
+
+ // JS standard classes should be exposed.
+ checkWindowXrayProperty(win, "Array", { windowValue: Cu.unwaiveXrays(waivedWin.Array) });
+
+ // HTMLDocument
+ // Unforgeable properties live on the instance.
+ checkXrayProperty(doc, "location", [ win.location ]);
+ is(String(win.location), document.getElementById("t").src,
+ "Should have the right stringification");
+
+ // HTMLHtmlElement
+ var elem = doc.documentElement;
+
+ var elemProto = Object.getPrototypeOf(elem);
+ is(elemProto, win.HTMLHtmlElement.prototype, "The proto chain of the Xray should mirror the prototype chain of the Xrayed object");
+
+ elemProto = Object.getPrototypeOf(elemProto);
+ is(elemProto, win.HTMLElement.prototype, "The proto chain of the Xray should mirror the prototype chain of the Xrayed object");
+
+ elemProto = Object.getPrototypeOf(elemProto);
+ is(elemProto, win.Element.prototype, "The proto chain of the Xray should mirror the prototype chain of the Xrayed object");
+
+ elemProto = Object.getPrototypeOf(elemProto);
+ is(elemProto, win.Node.prototype, "The proto chain of the Xray should mirror the prototype chain of the Xrayed object");
+
+ elemProto = Object.getPrototypeOf(elemProto);
+ is(elemProto, win.EventTarget.prototype, "The proto chain of the Xray should mirror the prototype chain of the Xrayed object");
+
+ // Xrays need to filter expandos.
+ ok(!("expando" in elem), "Xrays should filter expandos");
+
+ // WebIDL-defined properties live on the prototype.
+ checkXrayProperty(elem, "version", [ undefined, "" ]);
+ is(elem.version, "", "WebIDL properties should be exposed through Xrays");
+
+ // HTMLCollection
+ var coll = doc.getElementsByTagName("iframe");
+
+ // Named properties live on the instance for non-global objects.
+ checkXrayProperty(coll, "iframe", [ doc.getElementById("iframe") ]);
+
+ // Indexed properties live on the instance.
+ checkXrayProperty(coll, 0, [ doc.getElementById("shadowedIframe") ]);
+
+ // WebIDL-defined properties live on the prototype, overriding any named properties.
+ checkXrayProperty(coll, "item", [ undefined, win.HTMLCollection.prototype.item ]);
+
+ // ECMAScript-defined properties live on the prototype, overriding any named properties.
+ checkXrayProperty(coll, "toString", [ undefined, undefined, win.Object.prototype.toString ]);
+
+ // Frozen arrays should come from our compartment, not the target one.
+ var languages1 = win.navigator.languages;
+ isnot(languages1, undefined, "Must have .languages");
+ ok(Array.isArray(languages1), ".languages should be an array");
+ ok(Object.isFrozen(languages1), ".languages should be a frozen array");
+ ok(!Cu.isXrayWrapper(languages1), "Should have our own version of array");
+ is(Cu.getGlobalForObject(languages1), window,
+ "languages1 should come from our window");
+ // We want to get .languages in the content compartment, but without waiving
+ // Xrays altogether.
+ var languages2 = win.eval("navigator.languages");
+ isnot(languages2, undefined, "Must still have .languages");
+ ok(Array.isArray(languages2), ".languages should still be an array");
+ ok(Cu.isXrayWrapper(languages2), "Should have xray for content version of array");
+ is(Cu.getGlobalForObject(languages2), win,
+ "languages2 come from the underlying window");
+ ok(Object.isFrozen(languages2.wrappedJSObject),
+ ".languages should still be a frozen array underneath");
+ isnot(languages1, languages2, "Must have distinct arrays");
+ isnot(languages1, languages2.wrappedJSObject,
+ "Must have distinct arrays no matter how we slice it");
+
+ // Check that DataTransfer's .types has the hack to alias contains()
+ // to includes().
+ var dataTransfer = new win.DataTransfer();
+ is(dataTransfer.types.contains, dataTransfer.types.includes,
+ "Should have contains() set up as an alias to includes()");
+ // Waive Xrays on the dataTransfer itself, since the .types we get is
+ // different over Xrays vs not.
+ is(dataTransfer.wrappedJSObject.types.contains, undefined,
+ "Underlying object should not have contains() set up as an alias to " +
+ "includes()");
+
+ // Check that deleters work correctly in the [OverrideBuiltins] case.
+ elem = win.document.documentElement;
+ var dataset = elem.dataset;
+ is(dataset.foo, undefined, "Should not have a 'foo' property");
+ ok(!("foo" in dataset), "Really should not have a 'foo' property");
+ is(elem.getAttribute("data-foo"), null,
+ "Should not have a 'data-foo' attribute");
+ ok(!elem.hasAttribute("data-foo"),
+ "Really should not have a 'data-foo' attribute");
+ dataset.foo = "bar";
+ is(dataset.foo, "bar", "Should now have a 'foo' property");
+ ok("foo" in dataset, "Really should have a 'foo' property");
+ is(elem.getAttribute("data-foo"), "bar",
+ "Should have a 'data-foo' attribute");
+ ok(elem.hasAttribute("data-foo"),
+ "Really should have a 'data-foo' attribute");
+ delete dataset.foo;
+ is(dataset.foo, undefined, "Should not have a 'foo' property again");
+ ok(!("foo" in dataset), "Really should not have a 'foo' property again");
+ is(elem.getAttribute("data-foo"), null,
+ "Should not have a 'data-foo' attribute again");
+ ok(!elem.hasAttribute("data-foo"),
+ "Really should not have a 'data-foo' attribute again");
+
+ // Check that deleters work correctly in the non-[OverrideBuiltins] case.
+ var storage = win.sessionStorage;
+ is(storage.foo, undefined, "Should not have a 'foo' property");
+ ok(!("foo" in storage), "Really should not have a 'foo' property");
+ is(storage.getItem("foo"), null, "Should not have an item named 'foo'");
+ storage.foo = "bar";
+ is(storage.foo, "bar", "Should have a 'foo' property");
+ ok("foo" in storage, "Really should have a 'foo' property");
+ is(storage.getItem("foo"), "bar", "Should have an item named 'foo'");
+ delete storage.foo;
+ is(storage.foo, undefined, "Should not have a 'foo' property again");
+ ok(!("foo" in storage), "Really should not have a 'foo' property again");
+ is(storage.getItem("foo"), null, "Should not have an item named 'foo' again");
+
+ // Non-static properties are not exposed on interface objects or instances.
+ is(win.HTMLInputElement.checkValidity, undefined,
+ "Shouldn't see non-static property on interface objects");
+ is(Object.getOwnPropertyDescriptor(win.HTMLInputElement, "checkValidity"), undefined,
+ "Shouldn't see non-static property on interface objects");
+ is(Object.getOwnPropertyNames(win.HTMLInputElement).indexOf("checkValidity"), -1,
+ "Shouldn't see non-static property on interface objects");
+ isnot(typeof doc.createElement("input").checkValidity, "undefined",
+ "Should see non-static property on prototype objects");
+ is(Object.getOwnPropertyDescriptor(doc.createElement("input"), "checkValidity"), undefined,
+ "Shouldn't see non-static property on instances");
+ isnot(typeof Object.getOwnPropertyDescriptor(win.HTMLInputElement.prototype, "checkValidity"), "undefined",
+ "Should see non-static property on prototype objects");
+
+ // Static properties are not exposed on prototype objects or instances.
+ isnot(typeof win.URL.createObjectURL, "undefined",
+ "Should see static property on interface objects");
+ isnot(typeof Object.getOwnPropertyDescriptor(win.URL, "createObjectURL"), "undefined",
+ "Should see static property on interface objects");
+ isnot(Object.getOwnPropertyNames(win.URL).indexOf("createObjectURL"), -1,
+ "Should see static property on interface objects");
+ is(new URL("http://example.org").createObjectURL, undefined,
+ "Shouldn't see static property on instances and prototype ojbects");
+ is(Object.getOwnPropertyDescriptor(new URL("http://example.org"), "createObjectURL"), undefined,
+ "Shouldn't see static property on instances");
+ is(Object.getOwnPropertyDescriptor(win.URL.prototype, "createObjectURL"), undefined,
+ "Shouldn't see static property on prototype objects");
+
+ // Unforgeable properties are not exposed on prototype objects or interface
+ // objects.
+ is(Window.document, undefined,
+ "Shouldn't see unforgeable property on interface objects");
+ is(Object.getOwnPropertyDescriptor(Window, "document"), undefined,
+ "Shouldn't see unforgeable property on interface objects");
+ is(Object.getOwnPropertyNames(Window).indexOf("document"), -1,
+ "Shouldn't see unforgeable property on interface objects");
+ isnot(typeof win.document, "undefined",
+ "Should see unforgeable property on instances");
+ isnot(typeof Object.getOwnPropertyDescriptor(win, "document"), "undefined",
+ "Should see unforgeable property on instances");
+ is(Object.getOwnPropertyDescriptor(Window.prototype, "document"), undefined,
+ "Shouldn't see unforgeable property on prototype objects");
+
+ // Constant properties are not exposted on instances.
+ isnot(typeof win.Node.ELEMENT_NODE, "undefined",
+ "Should see constant property on interface objects");
+ isnot(typeof Object.getOwnPropertyDescriptor(win.Node, "ELEMENT_NODE"), "undefined",
+ "Should see constant property on interface objects");
+ isnot(Object.getOwnPropertyNames(win.Node).indexOf("ELEMENT_NODE"), -1,
+ "Should see constant property on interface objects");
+ isnot(typeof elem.ELEMENT_NODE, "undefined",
+ "Should see constant property on prototype objects");
+ is(Object.getOwnPropertyDescriptor(elem, "ELEMENT_NODE"), undefined,
+ "Shouldn't see constant property on instances");
+ isnot(typeof Object.getOwnPropertyDescriptor(win.Node.prototype, "ELEMENT_NODE"), "undefined",
+ "Should see constant property on prototype objects");
+
+ // Interfaces can have both static and non-static properties with the same name.
+ isnot(typeof win.TestFunctions.staticAndNonStaticOverload, "undefined",
+ "Should see static property on interface objects (even with non-static property with the same name)");
+ isnot(typeof Object.getOwnPropertyDescriptor(win.TestFunctions, "staticAndNonStaticOverload"), "undefined",
+ "Should see static property on interface objects (even with non-static property with the same name)");
+ isnot(Object.getOwnPropertyNames(win.TestFunctions).indexOf("staticAndNonStaticOverload"), -1,
+ "Should see static property on interface objects (even with non-static property with the same name)");
+ isnot(typeof (new win.TestFunctions("")).staticAndNonStaticOverload, "undefined",
+ "Should see non-static property on prototype objects (even with static property with the same name)");
+ let testFunctions = new win.TestFunctions();
+ is(Object.getOwnPropertyDescriptor(testFunctions, "staticAndNonStaticOverload"), undefined,
+ "Shouldn't see non-static property on instances (even with static property with the same name)");
+ ok(!testFunctions.staticAndNonStaticOverload(),
+ "Should call the non-static overload on the instance");
+ ok(win.TestFunctions.staticAndNonStaticOverload(),
+ "Should call the static overload on the interface object");
+ isnot(typeof Object.getOwnPropertyDescriptor(win.TestFunctions.prototype, "staticAndNonStaticOverload"), "undefined",
+ "Should see non-static property on prototype objects (even with static property with the same name)");
+ is(Object.getOwnPropertyDescriptor(win.TestFunctions, "staticAndNonStaticOverload").value,
+ Object.getOwnPropertyDescriptor(win.TestFunctions, "staticAndNonStaticOverload").value,
+ "Should get the same value when getting the static property twice");
+ is(Object.getOwnPropertyDescriptor(win.TestFunctions.prototype, "staticAndNonStaticOverload").value,
+ Object.getOwnPropertyDescriptor(win.TestFunctions.prototype, "staticAndNonStaticOverload").value,
+ "Should get the same value when getting the non-static property twice");
+ isnot(Object.getOwnPropertyDescriptor(win.TestFunctions, "staticAndNonStaticOverload").value,
+ Object.getOwnPropertyDescriptor(win.TestFunctions.prototype, "staticAndNonStaticOverload").value,
+ "Should get different values for static and non-static properties with the same name");
+
+ // Adopting nodes should not lose expandos.
+ elem = document.createElement("span");
+ elem.expando = 5;
+ is(elem.expando, 5, "We just set this property");
+ document.adoptNode(elem);
+ is(elem.wrappedJSObject, undefined, "Shouldn't be an Xray anymore");
+ is(elem.expando, 5, "Expando should not get lost");
+
+ // Instanceof tests
+ var img = doc.createElement("img");
+ var img2 = document.createElement("img");
+ ok(img instanceof win.HTMLImageElement,
+ "Should be an instance of HTMLImageElement from its global");
+ ok(win.HTMLImageElement.isInstance(img), "isInstance should work");
+ ok(HTMLImageElement.isInstance(img), "isInstance should work cross-global");
+ ok(win.HTMLImageElement.isInstance(img2),
+ "isInstance should work cross-global in the other direction");
+ ok(img instanceof win.Image,
+ "Should be an instance of Image, because Image.prototype == HTMLImageElement.prototype");
+ ok(!win.Image.isInstance, "Shouldn't have an isInstance method here");
+ // Image does not have a Symbol.hasInstance, but its proto
+ // (Function.prototype) does.
+ checkXrayProperty(win.Image, Symbol.hasInstance,
+ [undefined, win.Function.prototype[Symbol.hasInstance]]);
+
+ // toString/@@toStringTag
+ let imageConstructor = win.Image;
+ is(win.Function.prototype.toString.apply(imageConstructor),
+ Function.prototype.toString.apply(Image),
+ "Applying Function.prototype.toString through an Xray should give the same result as applying it directly");
+ isDeeply(Object.getOwnPropertyDescriptor(win.CSS, Symbol.toStringTag),
+ Object.getOwnPropertyDescriptor(CSS, Symbol.toStringTag),
+ "Getting @@toStringTag on a namespace object through an Xray should give the same result as getting it directly");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(2);
+
+addLoadEvent(() => {
+ SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]},
+ test);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_enums.html b/dom/bindings/test/test_enums.html
new file mode 100644
index 0000000000..95f23885bb
--- /dev/null
+++ b/dom/bindings/test/test_enums.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Enums</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+/* global test, assert_equals */
+test(function() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("get", "foo");
+ assert_equals(xhr.responseType, "");
+ xhr.responseType = "foo";
+ assert_equals(xhr.responseType, "");
+}, "Assigning an invalid value to an enum attribute should not throw.");
+</script>
diff --git a/dom/bindings/test/test_exceptionSanitization.html b/dom/bindings/test/test_exceptionSanitization.html
new file mode 100644
index 0000000000..818d750136
--- /dev/null
+++ b/dom/bindings/test/test_exceptionSanitization.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1295322
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1295322</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1295322">Mozilla Bug 1295322</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+ /* global TestFunctions */
+ SimpleTest.waitForExplicitFinish();
+ async function runTests() {
+ await SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]});
+
+ var t = new TestFunctions();
+
+ try {
+ t.testThrowNsresult();
+ } catch (e) {
+ try {
+ is(e.name, "NS_BINDING_ABORTED", "Should have the right exception");
+ is(e.filename, location.href, "Should not be seeing where the exception really came from");
+ } catch (e2) {
+ ok(false, "Should be able to work with the exception");
+ }
+ }
+
+ try {
+ t.testThrowNsresultFromNative();
+ } catch (e) {
+ try {
+ is(e.name, "NS_ERROR_UNEXPECTED", "Should have the right exception");
+ is(e.filename, location.href, "Should not be seeing where the exception really came from");
+ } catch (e2) {
+ ok(false, "Should be able to work with the exception");
+ }
+ }
+
+ SimpleTest.finish();
+ }
+
+ runTests();
+ </script>
+</body>
+</html>
diff --git a/dom/bindings/test/test_exceptionThrowing.html b/dom/bindings/test/test_exceptionThrowing.html
new file mode 100644
index 0000000000..571d21b485
--- /dev/null
+++ b/dom/bindings/test/test_exceptionThrowing.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=847119
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 847119</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 847119 **/
+
+ var xhr = new XMLHttpRequest();
+ var domthrows = function() { xhr.open(); };
+
+ var count = 20000;
+
+ function f() {
+ var k = 0;
+ for (var j = 0; j < count; ++j) {
+ try { domthrows(); } catch (e) { ++k; }
+ }
+ return k;
+ }
+ function g() { return count; }
+
+ is(f(), count, "Should get count exceptions");
+ for (let h of [f, g]) {
+ try { is(h(), count, "Should get count exceptions here too"); } catch (e) {}
+ }
+ ok(true, "We should get here");
+
+ domthrows = function() { xhr.withCredentials = false; };
+ xhr.open("GET", "");
+ xhr.send();
+
+ is(f(), count, "Should get count exceptions from getter");
+ for (let h of [f, g]) {
+ try { is(h(), count, "Should get count exceptions from getter here too"); } catch (e) {}
+ }
+ ok(true, "We should get here too");
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=847119">Mozilla Bug 847119</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_exception_messages.html b/dom/bindings/test/test_exception_messages.html
new file mode 100644
index 0000000000..ffa1937e7e
--- /dev/null
+++ b/dom/bindings/test/test_exception_messages.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=882653
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 882653</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 882653 **/
+ // Each test is a string to eval, the expected exception message, and the
+ // test description.
+ var tests = [
+ [ "document.documentElement.appendChild.call({}, new Image())",
+ "'appendChild' called on an object that does not implement interface Node.",
+ "bogus method this object" ],
+ [ 'Object.getOwnPropertyDescriptor(Document.prototype, "documentElement").get.call({})',
+ "'get documentElement' called on an object that does not implement interface Document.",
+ "bogus getter this object" ],
+ [ 'Object.getOwnPropertyDescriptor(Element.prototype, "innerHTML").set.call({})',
+ "'set innerHTML' called on an object that does not implement interface Element.",
+ "bogus setter this object" ],
+ [ "document.documentElement.appendChild(5)",
+ "Node.appendChild: Argument 1 is not an object.",
+ "bogus interface argument" ],
+ [ "document.documentElement.appendChild(null)",
+ "Node.appendChild: Argument 1 is not an object.",
+ "null interface argument" ],
+ [ "document.createTreeWalker(document).currentNode = 5",
+ "TreeWalker.currentNode setter: Value being assigned is not an object.",
+ "interface setter call" ],
+ [ "document.documentElement.appendChild({})",
+ "Node.appendChild: Argument 1 does not implement interface Node.",
+ "wrong interface argument" ],
+ [ "document.createTreeWalker(document).currentNode = {}",
+ "TreeWalker.currentNode setter: Value being assigned does not implement interface Node.",
+ "wrong interface setter call" ],
+ [ 'document.createElement("canvas").getContext("2d").fill("bogus")',
+ "CanvasRenderingContext2D.fill: 'bogus' (value of argument 1) is not a valid value for enumeration CanvasWindingRule.",
+ "bogus enum value" ],
+ [ "document.createTreeWalker(document, 0xFFFFFFFF, { acceptNode: 5 }).nextNode()",
+ "Property 'acceptNode' is not callable.",
+ "non-callable callback interface operation property" ],
+ [ "(new TextDecoder).decode(new Uint8Array(), 5)",
+ "TextDecoder.decode: Argument 2 can't be converted to a dictionary.",
+ "primitive passed for a dictionary" ],
+ [ "URL.createObjectURL(null)",
+ "URL.createObjectURL: Argument 1 is not valid for any of the 1-argument overloads.",
+ "overload resolution failure" ],
+ [ 'document.createElement("select").add({})',
+ "HTMLSelectElement.add: Argument 1 could not be converted to any of: HTMLOptionElement, HTMLOptGroupElement.",
+ "invalid value passed for union" ],
+ [ 'document.createElement("canvas").getContext("2d").createLinearGradient(0, 1, 0, 1).addColorStop(NaN, "")',
+ "CanvasGradient.addColorStop: Argument 1 is not a finite floating-point value.",
+ "invalid float" ],
+ ];
+
+ for (var i = 0; i < tests.length; ++i) {
+ var msg = "Correct exception should be thrown for " + tests[i][2];
+ try {
+ // eslint-disable-next-line no-eval
+ eval(tests[i][0]);
+ ok(false, msg);
+ } catch (e) {
+ is(e.message, tests[i][1], msg);
+ }
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=882653">Mozilla Bug 882653</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_exception_options_from_jsimplemented.html b/dom/bindings/test/test_exception_options_from_jsimplemented.html
new file mode 100644
index 0000000000..d7d243b0d0
--- /dev/null
+++ b/dom/bindings/test/test_exception_options_from_jsimplemented.html
@@ -0,0 +1,166 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1107592
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1107592</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ /* global TestInterfaceJS */
+ /** Test for Bug 1107592 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function doTest() {
+ var file = location.href;
+
+ var asyncFrame;
+ /* Async parent frames from pushPrefEnv don't show up in e10s. */
+ if (!SpecialPowers.getBoolPref("javascript.options.asyncstack_capture_debuggee_only")) {
+ asyncFrame = `Async*@${file}:153:17
+`;
+ } else {
+ asyncFrame = "";
+ }
+
+ var t = new TestInterfaceJS();
+ try {
+ t.testThrowError();
+ } catch (e) {
+ ok(e instanceof Error, "Should have an Error here");
+ ok(!(e instanceof DOMException), "Should not have DOMException here");
+ ok(!("code" in e), "Should not have a 'code' property");
+ is(e.name, "Error", "Should not have an interesting name here");
+ is(e.message, "We are an Error", "Should have the right message");
+ is(e.stack,
+ `doTest@${file}:31:9
+${asyncFrame}`,
+ "Exception stack should still only show our code");
+ is(e.fileName,
+ file,
+ "Should have the right file name");
+ is(e.lineNumber, 31, "Should have the right line number");
+ is(e.columnNumber, 9, "Should have the right column number");
+ }
+
+ try {
+ t.testThrowDOMException();
+ } catch (e) {
+ ok(e instanceof Error, "Should also have an Error here");
+ ok(e instanceof DOMException, "Should have DOMException here");
+ is(e.name, "NotSupportedError", "Should have the right name here");
+ is(e.message, "We are a DOMException",
+ "Should also have the right message");
+ is(e.code, DOMException.NOT_SUPPORTED_ERR,
+ "Should have the right 'code'");
+ is(e.stack,
+ `doTest@${file}:50:9
+${asyncFrame}`,
+ "Exception stack should still only show our code");
+ is(e.filename,
+ file,
+ "Should still have the right file name");
+ is(e.lineNumber, 50, "Should still have the right line number");
+ todo_isnot(e.columnNumber, 0,
+ "No column number support for DOMException yet");
+ }
+
+ try {
+ t.testThrowTypeError();
+ } catch (e) {
+ ok(e instanceof TypeError, "Should have a TypeError here");
+ ok(!(e instanceof DOMException), "Should not have DOMException here (2)");
+ ok(!("code" in e), "Should not have a 'code' property (2)");
+ is(e.name, "TypeError", "Should be named TypeError");
+ is(e.message, "We are a TypeError",
+ "Should also have the right message (2)");
+ is(e.stack,
+ `doTest@${file}:72:9
+${asyncFrame}`,
+ "Exception stack for TypeError should only show our code");
+ is(e.fileName,
+ file,
+ "Should still have the right file name for TypeError");
+ is(e.lineNumber, 72, "Should still have the right line number for TypeError");
+ is(e.columnNumber, 9, "Should have the right column number for TypeError");
+ }
+
+ try {
+ t.testThrowCallbackError(function() { Array.prototype.forEach(); });
+ } catch (e) {
+ ok(e instanceof TypeError, "Should have a TypeError here (3)");
+ ok(!(e instanceof DOMException), "Should not have DOMException here (3)");
+ ok(!("code" in e), "Should not have a 'code' property (3)");
+ is(e.name, "TypeError", "Should be named TypeError (3)");
+ is(e.message, "missing argument 0 when calling function Array.prototype.forEach",
+ "Should also have the right message (3)");
+ is(e.stack,
+ `doTest/<@${file}:92:61
+doTest@${file}:92:9
+${asyncFrame}`,
+ "Exception stack for TypeError should only show our code (3)");
+ is(e.fileName,
+ file,
+ "Should still have the right file name for TypeError (3)");
+ is(e.lineNumber, 92, "Should still have the right line number for TypeError (3)");
+ is(e.columnNumber, 61, "Should have the right column number for TypeError (3)");
+ }
+
+ try {
+ t.testThrowXraySelfHosted();
+ } catch (e) {
+ ok(!(e instanceof Error), "Should have an Exception here (4)");
+ ok(!(e instanceof DOMException), "Should not have DOMException here (4)");
+ ok(!("code" in e), "Should not have a 'code' property (4)");
+ is(e.name, "NS_ERROR_UNEXPECTED", "Name should be sanitized (4)");
+ is(e.message, "", "Message should be sanitized (5)");
+ is(e.stack,
+ `doTest@${file}:113:9
+${asyncFrame}`,
+ "Exception stack for sanitized exception should only show our code (4)");
+ is(e.filename,
+ file,
+ "Should still have the right file name for sanitized exception (4)");
+ is(e.lineNumber, 113, "Should still have the right line number for sanitized exception (4)");
+ todo_isnot(e.columnNumber, 0, "Should have the right column number for sanitized exception (4)");
+ }
+
+ try {
+ t.testThrowSelfHosted();
+ } catch (e) {
+ ok(!(e instanceof Error), "Should have an Exception here (5)");
+ ok(!(e instanceof DOMException), "Should not have DOMException here (5)");
+ ok(!("code" in e), "Should not have a 'code' property (5)");
+ is(e.name, "NS_ERROR_UNEXPECTED", "Name should be sanitized (5)");
+ is(e.message, "", "Message should be sanitized (5)");
+ is(e.stack,
+ `doTest@${file}:132:9
+${asyncFrame}`,
+ "Exception stack for sanitized exception should only show our code (5)");
+ is(e.filename,
+ file,
+ "Should still have the right file name for sanitized exception (5)");
+ is(e.lineNumber, 132, "Should still have the right line number for sanitized exception (5)");
+ todo_isnot(e.columnNumber, 0, "Should have the right column number for sanitized exception (5)");
+ }
+
+ SimpleTest.finish();
+ }
+
+ SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]},
+ doTest);
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1107592">Mozilla Bug 1107592</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_exceptions_from_jsimplemented.html b/dom/bindings/test/test_exceptions_from_jsimplemented.html
new file mode 100644
index 0000000000..89faf0c46f
--- /dev/null
+++ b/dom/bindings/test/test_exceptions_from_jsimplemented.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=923010
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 923010</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ /** Test for Bug 923010 **/
+ try {
+ var conn = new RTCPeerConnection();
+
+ var candidate = new RTCIceCandidate({candidate: "x" });
+ conn.addIceCandidate(candidate)
+ .then(function() {
+ ok(false, "addIceCandidate succeeded when it should have failed");
+ }, function(reason) {
+ is(reason.lineNumber, 17, "Rejection should have been on line 17");
+ is(reason.message,
+ "Invalid candidate (both sdpMid and sdpMLineIndex are null).",
+ "Should have the rejection we expect");
+ })
+ .catch(function(reason) {
+ ok(false, "unexpected error: " + reason);
+ });
+ } catch (e) {
+ // b2g has no WebRTC, apparently
+ todo(false, "No WebRTC on b2g yet");
+ }
+
+ conn.close();
+ try {
+ conn.setIdentityProvider("example.com", { protocol: "foo" });
+ ok(false, "That call to setIdentityProvider should have thrown");
+ } catch (e) {
+ is(e.lineNumber, 36, "Exception should have been on line 36");
+ is(e.message,
+ "Peer connection is closed",
+ "Should have the exception we expect");
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=923010">Mozilla Bug 923010</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_forOf.html b/dom/bindings/test/test_forOf.html
new file mode 100644
index 0000000000..1be2506a21
--- /dev/null
+++ b/dom/bindings/test/test_forOf.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=725907
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 725907</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=725907">Mozilla Bug 725907</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<div id="basket">
+ <span id="egg0"></span>
+ <span id="egg1"><span id="duckling1"></span></span>
+ <span id="egg2"></span>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 725907 **/
+
+
+function runTestsForDocument(document, msgSuffix) {
+ function is(a, b, msg) { SimpleTest.is(a, b, msg + msgSuffix); }
+
+ var basket = document.getElementById("basket");
+ var egg3 = document.createElement("span");
+ egg3.id = "egg3";
+
+ var log = "";
+ for (let x of basket.childNodes) {
+ if (x.nodeType != x.TEXT_NODE)
+ log += x.id + ";";
+ }
+ is(log, "egg0;egg1;egg2;", "'for (x of div.childNodes)' should iterate over child nodes");
+
+ log = "";
+ for (let x of basket.childNodes) {
+ if (x.nodeType != x.TEXT_NODE) {
+ log += x.id + ";";
+ if (x.id == "egg1")
+ basket.appendChild(egg3);
+ }
+ }
+ is(log, "egg0;egg1;egg2;egg3;", "'for (x of div.childNodes)' should see elements added during iteration");
+
+ log = "";
+ basket.appendChild(document.createTextNode("some text"));
+ for (let x of basket.children)
+ log += x.id + ";";
+ is(log, "egg0;egg1;egg2;egg3;", "'for (x of div.children)' should iterate over child elements");
+
+ var count = 0;
+ // eslint-disable-next-line no-unused-vars
+ for (let x of document.getElementsByClassName("hazardous-materials"))
+ count++;
+ is(count, 0, "'for (x of emptyNodeList)' loop should run zero times");
+
+ log = "";
+ for (let x of document.querySelectorAll("span"))
+ log += x.id + ";";
+ is(log, "egg0;egg1;duckling1;egg2;egg3;", "for-of loop should work with a querySelectorAll() NodeList");
+}
+
+/* All the tests run twice. First, in this document, so without any wrappers. */
+runTestsForDocument(document, "");
+
+/* And once using the document of an iframe, so working with cross-compartment wrappers. */
+SimpleTest.waitForExplicitFinish();
+function iframeLoaded(iframe) {
+ runTestsForDocument(iframe.contentWindow.document, " (in iframe)");
+ SimpleTest.finish();
+}
+
+</script>
+
+<iframe src="forOf_iframe.html" onload="iframeLoaded(this)"></iframe>
+
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_integers.html b/dom/bindings/test/test_integers.html
new file mode 100644
index 0000000000..765ed1993d
--- /dev/null
+++ b/dom/bindings/test/test_integers.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <canvas id="c" width="1" height="1"></canvas>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+ function testInt64NonFinite(arg) {
+ // We can use a WebGLRenderingContext to test conversion to 64-bit signed
+ // ints edge cases.
+ var gl = $("c").getContext("experimental-webgl");
+ if (!gl) {
+ // No WebGL support on MacOS 10.5. Just skip this test
+ todo(false, "WebGL not supported");
+ return;
+ }
+ var error = gl.getError();
+
+ // on the b2g emulator we get GL_INVALID_FRAMEBUFFER_OPERATION
+ if (error == 0x0506) // GL_INVALID_FRAMEBUFFER_OPERATION
+ return;
+
+ is(error, 0, "Should not start in an error state");
+
+ var b = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, b);
+
+ var a = new Float32Array(1);
+ gl.bufferData(gl.ARRAY_BUFFER, a, gl.STATIC_DRAW);
+
+ gl.bufferSubData(gl.ARRAY_BUFFER, arg, a);
+
+ is(gl.getError(), 0, "Should have treated non-finite double as 0");
+ }
+
+ testInt64NonFinite(NaN);
+ testInt64NonFinite(Infinity);
+ testInt64NonFinite(-Infinity);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_interfaceLength.html b/dom/bindings/test/test_interfaceLength.html
new file mode 100644
index 0000000000..30fde932ce
--- /dev/null
+++ b/dom/bindings/test/test_interfaceLength.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1776790
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1776790</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1776790">Mozilla Bug 1776790</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+/* global TestInterfaceLength */
+
+add_task(async function init() {
+ await SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]});
+});
+
+/** Test for Bug 1776790 **/
+add_task(function test_interface_length() {
+ is(TestInterfaceLength.length, 0, "TestInterfaceLength.length");
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_interfaceLength_chrome.html b/dom/bindings/test/test_interfaceLength_chrome.html
new file mode 100644
index 0000000000..86ec985057
--- /dev/null
+++ b/dom/bindings/test/test_interfaceLength_chrome.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1776790
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1776790</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1776790">Mozilla Bug 1776790</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+/* global TestInterfaceLength */
+
+add_task(async function init() {
+ await SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]});
+});
+
+/** Test for Bug 1776790 **/
+add_task(function test_interface_length() {
+ is(TestInterfaceLength.length, 1, "TestInterfaceLength.length");
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_interfaceName.html b/dom/bindings/test/test_interfaceName.html
new file mode 100644
index 0000000000..bd06fc0cef
--- /dev/null
+++ b/dom/bindings/test/test_interfaceName.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1084001
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1084001</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1084001 **/
+ is(Image.name, "Image", "Image name");
+ is(Promise.name, "Promise", "Promise name");
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1084001">Mozilla Bug 1084001</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_interfaceToString.html b/dom/bindings/test/test_interfaceToString.html
new file mode 100644
index 0000000000..0a7ae9337f
--- /dev/null
+++ b/dom/bindings/test/test_interfaceToString.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=742156
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 742156</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=742156">Mozilla Bug 742156</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 742156 **/
+
+var nativeToString = ("" + String).replace("String", "EventTarget");
+try {
+ var eventTargetToString = "" + EventTarget;
+ is(eventTargetToString, nativeToString,
+ "Stringifying a DOM interface object should return the same string" +
+ "as stringifying a native function.");
+} catch (e) {
+ ok(false, "Stringifying a DOM interface object shouldn't throw.");
+}
+
+try {
+ eventTargetToString = Function.prototype.toString.call(EventTarget);
+ is(eventTargetToString, nativeToString,
+ "Stringifying a DOM interface object via Function.prototype.toString " +
+ "should return the same string as stringifying a native function.");
+} catch (e) {
+ ok(false, "Stringifying a DOM interface object shouldn't throw.");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_iterable.html b/dom/bindings/test/test_iterable.html
new file mode 100644
index 0000000000..4c2dc0fc4e
--- /dev/null
+++ b/dom/bindings/test/test_iterable.html
@@ -0,0 +1,241 @@
+<!-- Any copyright is dedicated to the Public Domain.
+- http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test Iterable Interface</title>
+ <script 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">
+ /* global TestInterfaceIterableSingle, TestInterfaceIterableDouble, TestInterfaceIterableDoubleUnion */
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]}, function() {
+ var base_properties = [["entries", "function", 0],
+ ["keys", "function", 0],
+ ["values", "function", 0],
+ ["forEach", "function", 1]];
+ var testExistence = function testExistence(prefix, obj, properties) {
+ for (var [name, type, args] of properties) {
+ // Properties are somewhere up the proto chain, hasOwnProperty won't work
+ 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 enumerable`);
+ break;
+ }
+ owner = Object.getPrototypeOf(owner);
+ }
+ }
+ };
+
+ var itr;
+ info("IterableSingle: Testing simple iterable creation and functionality");
+ itr = new TestInterfaceIterableSingle();
+ testExistence("IterableSingle: ", itr, base_properties);
+ is(itr[Symbol.iterator], Array.prototype[Symbol.iterator],
+ "IterableSingle: Should be using %ArrayIterator% for @@iterator");
+ is(itr.keys, Array.prototype.keys,
+ "IterableSingle: Should be using %ArrayIterator% for 'keys'");
+ is(itr.entries, Array.prototype.entries,
+ "IterableSingle: Should be using %ArrayIterator% for 'entries'");
+ is(itr.values, itr[Symbol.iterator],
+ "IterableSingle: Should be using @@iterator for 'values'");
+ is(itr.forEach, Array.prototype.forEach,
+ "IterableSingle: Should be using %ArrayIterator% for 'forEach'");
+ var keys = [...itr.keys()];
+ var values = [...itr.values()];
+ var entries = [...itr.entries()];
+ var key_itr = itr.keys();
+ var value_itr = itr.values();
+ var entries_itr = itr.entries();
+ for (let i = 0; i < 3; ++i) {
+ let key = key_itr.next();
+ let value = value_itr.next();
+ let entry = entries_itr.next();
+ is(key.value, i, "IterableSingle: Key iterator value should be " + i);
+ is(key.value, keys[i],
+ "IterableSingle: Key iterator value should match destructuring " + i);
+ is(value.value, key.value, "IterableSingle: Value iterator value should be " + key.value);
+ is(value.value, values[i],
+ "IterableSingle: Value iterator value should match destructuring " + i);
+ is(entry.value[0], i, "IterableSingle: Entry iterator value 0 should be " + i);
+ is(entry.value[1], i, "IterableSingle: Entry iterator value 1 should be " + i);
+ is(entry.value[0], entries[i][0],
+ "IterableSingle: Entry iterator value 0 should match destructuring " + i);
+ is(entry.value[1], entries[i][1],
+ "IterableSingle: Entry iterator value 1 should match destructuring " + i);
+ }
+
+ var callsToForEachCallback = 0;
+ var thisArg = {};
+ itr.forEach(function(value1, index, obj) {
+ is(index, callsToForEachCallback,
+ `IterableSingle: Should have the right index at ${callsToForEachCallback} calls to forEach callback`);
+ is(value1, values[index],
+ `IterableSingle: Should have the right value at ${callsToForEachCallback} calls to forEach callback`);
+ is(this, thisArg,
+ "IterableSingle: Should have the right this value for forEach callback");
+ is(obj, itr,
+ "IterableSingle: Should have the right third arg for forEach callback");
+ ++callsToForEachCallback;
+ }, thisArg);
+ is(callsToForEachCallback, 3,
+ "IterableSingle: Should have right total number of calls to forEach callback");
+
+ let key = key_itr.next();
+ let value = value_itr.next();
+ let entry = entries_itr.next();
+ is(key.value, undefined, "IterableSingle: Key iterator value should be undefined");
+ is(key.done, true, "IterableSingle: Key iterator done should be true");
+ is(value.value, undefined, "IterableSingle: Value iterator value should be undefined");
+ is(value.done, true, "IterableSingle: Value iterator done should be true");
+ is(entry.value, undefined, "IterableDouble: Entry iterator value should be undefined");
+ is(entry.done, true, "IterableSingle: Entry iterator done should be true");
+ is(Object.prototype.toString.call(Object.getPrototypeOf(key_itr)),
+ "[object Array Iterator]",
+ "iterator prototype should have the right brand");
+
+ // Simple dual type iterable creation and functionality test
+ info("IterableDouble: Testing simple iterable creation and functionality");
+ itr = new TestInterfaceIterableDouble();
+ testExistence("IterableDouble: ", itr, base_properties);
+ is(itr.entries, itr[Symbol.iterator],
+ "IterableDouble: Should be using @@iterator for 'entries'");
+ var elements = [["a", "b"], ["c", "d"], ["e", "f"]];
+ keys = [...itr.keys()];
+ values = [...itr.values()];
+ entries = [...itr.entries()];
+ key_itr = itr.keys();
+ value_itr = itr.values();
+ entries_itr = itr.entries();
+ for (let i = 0; i < 3; ++i) {
+ key = key_itr.next();
+ value = value_itr.next();
+ entry = entries_itr.next();
+ is(key.value, elements[i][0], "IterableDouble: Key iterator value should be " + elements[i][0]);
+ is(key.value, keys[i],
+ "IterableDouble: Key iterator value should match destructuring " + i);
+ is(value.value, elements[i][1], "IterableDouble: Value iterator value should be " + elements[i][1]);
+ is(value.value, values[i],
+ "IterableDouble: Value iterator value should match destructuring " + i);
+ is(entry.value[0], elements[i][0], "IterableDouble: Entry iterator value 0 should be " + elements[i][0]);
+ is(entry.value[1], elements[i][1], "IterableDouble: Entry iterator value 1 should be " + elements[i][1]);
+ is(entry.value[0], entries[i][0],
+ "IterableDouble: Entry iterator value 0 should match destructuring " + i);
+ is(entry.value[1], entries[i][1],
+ "IterableDouble: Entry iterator value 1 should match destructuring " + i);
+ }
+
+ callsToForEachCallback = 0;
+ thisArg = {};
+ itr.forEach(function(value1, key1, obj) {
+ is(key1, keys[callsToForEachCallback],
+ `IterableDouble: Should have the right key at ${callsToForEachCallback} calls to forEach callback`);
+ is(value1, values[callsToForEachCallback],
+ `IterableDouble: Should have the right value at ${callsToForEachCallback} calls to forEach callback`);
+ is(this, thisArg,
+ "IterableDouble: Should have the right this value for forEach callback");
+ is(obj, itr,
+ "IterableSingle: Should have the right third arg for forEach callback");
+ ++callsToForEachCallback;
+ }, thisArg);
+ is(callsToForEachCallback, 3,
+ "IterableDouble: Should have right total number of calls to forEach callback");
+
+ key = key_itr.next();
+ value = value_itr.next();
+ entry = entries_itr.next();
+ is(key.value, undefined, "IterableDouble: Key iterator value should be undefined");
+ is(key.done, true, "IterableDouble: Key iterator done should be true");
+ is(value.value, undefined, "IterableDouble: Value iterator value should be undefined");
+ is(value.done, true, "IterableDouble: Value iterator done should be true");
+ is(entry.value, undefined, "IterableDouble: Entry iterator value should be undefined");
+ is(entry.done, true, "IterableDouble: Entry iterator done should be true");
+ is(Object.prototype.toString.call(Object.getPrototypeOf(key_itr)),
+ "[object TestInterfaceIterableDouble Iterator]",
+ "iterator prototype should have the right brand");
+
+ // Simple dual type iterable creation and functionality test
+ info("IterableDoubleUnion: Testing simple iterable creation and functionality");
+ itr = new TestInterfaceIterableDoubleUnion();
+ testExistence("IterableDoubleUnion: ", itr, base_properties);
+ is(itr.entries, itr[Symbol.iterator],
+ "IterableDoubleUnion: Should be using @@iterator for 'entries'");
+ elements = [["long", 1], ["string", "a"]];
+ keys = [...itr.keys()];
+ values = [...itr.values()];
+ entries = [...itr.entries()];
+ key_itr = itr.keys();
+ value_itr = itr.values();
+ entries_itr = itr.entries();
+ for (let i = 0; i < elements.length; ++i) {
+ key = key_itr.next();
+ value = value_itr.next();
+ entry = entries_itr.next();
+ is(key.value, elements[i][0], "IterableDoubleUnion: Key iterator value should be " + elements[i][0]);
+ is(key.value, keys[i],
+ "IterableDoubleUnion: Key iterator value should match destructuring " + i);
+ is(value.value, elements[i][1], "IterableDoubleUnion: Value iterator value should be " + elements[i][1]);
+ is(value.value, values[i],
+ "IterableDoubleUnion: Value iterator value should match destructuring " + i);
+ is(entry.value[0], elements[i][0], "IterableDoubleUnion: Entry iterator value 0 should be " + elements[i][0]);
+ is(entry.value[1], elements[i][1], "IterableDoubleUnion: Entry iterator value 1 should be " + elements[i][1]);
+ is(entry.value[0], entries[i][0],
+ "IterableDoubleUnion: Entry iterator value 0 should match destructuring " + i);
+ is(entry.value[1], entries[i][1],
+ "IterableDoubleUnion: Entry iterator value 1 should match destructuring " + i);
+ }
+
+ callsToForEachCallback = 0;
+ thisArg = {};
+ itr.forEach(function(value1, key1, obj) {
+ is(key1, keys[callsToForEachCallback],
+ `IterableDoubleUnion: Should have the right key at ${callsToForEachCallback} calls to forEach callback`);
+ is(value1, values[callsToForEachCallback],
+ `IterableDoubleUnion: Should have the right value at ${callsToForEachCallback} calls to forEach callback`);
+ is(this, thisArg,
+ "IterableDoubleUnion: Should have the right this value for forEach callback");
+ is(obj, itr,
+ "IterableSingle: Should have the right third arg for forEach callback");
+ ++callsToForEachCallback;
+ }, thisArg);
+ is(callsToForEachCallback, 2,
+ "IterableDoubleUnion: Should have right total number of calls to forEach callback");
+
+ key = key_itr.next();
+ value = value_itr.next();
+ entry = entries_itr.next();
+ is(key.value, undefined, "IterableDoubleUnion: Key iterator value should be undefined");
+ is(key.done, true, "IterableDoubleUnion: Key iterator done should be true");
+ is(value.value, undefined, "IterableDoubleUnion: Value iterator value should be undefined");
+ is(value.done, true, "IterableDoubleUnion: Value iterator done should be true");
+ is(entry.value, undefined, "IterableDoubleUnion: Entry iterator value should be undefined");
+ is(entry.done, true, "IterableDoubleUnion: Entry iterator done should be true");
+ is(Object.prototype.toString.call(Object.getPrototypeOf(key_itr)),
+ "[object TestInterfaceIterableDoubleUnion Iterator]",
+ "iterator prototype should have the right brand");
+
+ SimpleTest.finish();
+ });
+ </script>
+ </body>
+</html>
diff --git a/dom/bindings/test/test_jsimplemented_cross_realm_this.html b/dom/bindings/test/test_jsimplemented_cross_realm_this.html
new file mode 100644
index 0000000000..f44e3e02ae
--- /dev/null
+++ b/dom/bindings/test/test_jsimplemented_cross_realm_this.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1464374-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1464374</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1464374">Mozilla Bug 1464374</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<iframe></iframe>
+<script type="application/javascript">
+ /* global TestInterfaceJS */
+ /** Test for Bug 1464374 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function doTest() {
+ var frame = frames[0];
+ var obj = new frame.TestInterfaceJS();
+ var ex;
+ try {
+ TestInterfaceJS.prototype.testThrowTypeError.call(obj);
+ } catch (e) {
+ ex = e;
+ }
+ ok(ex, "Should have an exception");
+ SimpleTest.finish();
+ }
+
+ SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]},
+ doTest);
+</script>
+
+</body>
+</html>
diff --git a/dom/bindings/test/test_jsimplemented_eventhandler.html b/dom/bindings/test/test_jsimplemented_eventhandler.html
new file mode 100644
index 0000000000..7b77604744
--- /dev/null
+++ b/dom/bindings/test/test_jsimplemented_eventhandler.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1186696
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1186696</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ /* global TestInterfaceJS */
+ /** Test for Bug 1186696 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function doTest() {
+ var values = [ function() {}, 5, null, undefined, "some string", {} ];
+
+ while (values.length) {
+ var value = values.pop();
+ var t = new TestInterfaceJS();
+ t.onsomething = value;
+ var gottenValue = t.onsomething;
+ if (typeof value == "object" || typeof value == "function") {
+ is(gottenValue, value, "Should get back the object-or-null we put in");
+ } else {
+ is(gottenValue, null, "Should get back null");
+ }
+ }
+
+ SimpleTest.finish();
+ }
+
+ SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]},
+ doTest);
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1186696">Mozilla Bug 1186696</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_jsimplemented_subclassing.html b/dom/bindings/test/test_jsimplemented_subclassing.html
new file mode 100644
index 0000000000..70be0d62aa
--- /dev/null
+++ b/dom/bindings/test/test_jsimplemented_subclassing.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1400275
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1400275</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1400275 **/
+ class Foo extends RTCPeerConnection {
+ }
+
+ var obj = new Foo();
+ is(Object.getPrototypeOf(obj), Foo.prototype,
+ "Should have the right prototype");
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1400275">Mozilla Bug 1400275</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_large_arraybuffers.html b/dom/bindings/test/test_large_arraybuffers.html
new file mode 100644
index 0000000000..64f6fc7276
--- /dev/null
+++ b/dom/bindings/test/test_large_arraybuffers.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1688616
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for large ArrayBuffers and views</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1688616">Mozilla Bug 1688616</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+ /* global TestFunctions */
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]}, go);
+
+ function checkThrowsTooLarge(f) {
+ let ex;
+ try{
+ f();
+ ok(false, "Should have thrown!");
+ } catch (e) {
+ ex = e;
+ }
+ ok(ex.toString().includes("larger than 2 GB"), "Got exception: " + ex);
+ }
+
+ function go() {
+ let test = new TestFunctions();
+
+ const gb = 1 * 1024 * 1024 * 1024;
+ let ab = new ArrayBuffer(5 * gb);
+ checkThrowsTooLarge(() => test.testNotAllowShared(ab));
+ checkThrowsTooLarge(() => test.testAllowShared(ab));
+ checkThrowsTooLarge(() => test.testDictWithAllowShared({arrayBuffer: ab}));
+ checkThrowsTooLarge(() => test.testUnionOfBuffferSource(ab));
+ checkThrowsTooLarge(() => { test.arrayBuffer = ab; });
+ checkThrowsTooLarge(() => { test.allowSharedArrayBuffer = ab; });
+ checkThrowsTooLarge(() => { test.sequenceOfArrayBuffer = [ab]; });
+ checkThrowsTooLarge(() => { test.sequenceOfAllowSharedArrayBuffer = [ab]; });
+
+ let ta = new Int8Array(ab, 0, 3 * gb);
+ checkThrowsTooLarge(() => test.testNotAllowShared(ta));
+ checkThrowsTooLarge(() => test.testAllowShared(ta));
+ checkThrowsTooLarge(() => test.testDictWithAllowShared({arrayBufferView: ta}));
+ checkThrowsTooLarge(() => test.testUnionOfBuffferSource(ta));
+ checkThrowsTooLarge(() => { test.arrayBufferView = ta; });
+ checkThrowsTooLarge(() => { test.allowSharedArrayBufferView = ta; });
+ checkThrowsTooLarge(() => { test.sequenceOfArrayBufferView = [ta]; });
+ checkThrowsTooLarge(() => { test.sequenceOfAllowSharedArrayBufferView = [ta]; });
+
+ let dv = new DataView(ab);
+ checkThrowsTooLarge(() => test.testNotAllowShared(dv));
+ checkThrowsTooLarge(() => test.testAllowShared(dv));
+ checkThrowsTooLarge(() => test.testDictWithAllowShared({arrayBufferView: dv}));
+ checkThrowsTooLarge(() => test.testUnionOfBuffferSource(dv));
+ checkThrowsTooLarge(() => { test.arrayBufferView = dv; });
+ checkThrowsTooLarge(() => { test.allowSharedArrayBufferView = dv; });
+ checkThrowsTooLarge(() => { test.sequenceOfArrayBufferView = [dv]; });
+ checkThrowsTooLarge(() => { test.sequenceOfAllowSharedArrayBufferView = [dv]; });
+
+ if (this.SharedArrayBuffer) {
+ let sab = new SharedArrayBuffer(5 * gb);
+ checkThrowsTooLarge(() => test.testAllowShared(sab));
+ checkThrowsTooLarge(() => test.testDictWithAllowShared({allowSharedArrayBuffer: sab}));
+ checkThrowsTooLarge(() => test.testUnionOfAllowSharedBuffferSource(sab));
+ checkThrowsTooLarge(() => { test.allowSharedArrayBuffer = sab; });
+ checkThrowsTooLarge(() => { test.sequenceOfAllowSharedArrayBuffer = [sab]; });
+
+ let sta = new Int8Array(sab);
+ checkThrowsTooLarge(() => test.testAllowShared(sta));
+ checkThrowsTooLarge(() => test.testDictWithAllowShared({allowSharedArrayBufferView: sta}));
+ checkThrowsTooLarge(() => test.testUnionOfAllowSharedBuffferSource(sta));
+ checkThrowsTooLarge(() => { test.allowSharedArrayBufferView = sta; });
+ checkThrowsTooLarge(() => { test.sequenceOfAllowSharedArrayBufferView = [sta]; });
+ }
+
+ // Small views on large buffers are fine.
+ let ta2 = new Int8Array(ab, 4 * gb);
+ is(ta2.byteLength, 1 * gb, "Small view on large ArrayBuffer");
+ test.testNotAllowShared(ta2);
+ test.arrayBufferView = ta2;
+
+ SimpleTest.finish();
+ }
+ </script>
+</body>
+</html>
diff --git a/dom/bindings/test/test_large_imageData.html b/dom/bindings/test/test_large_imageData.html
new file mode 100644
index 0000000000..68cb4bd50b
--- /dev/null
+++ b/dom/bindings/test/test_large_imageData.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1716622
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for large ImageData</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1716622">Mozilla Bug 1716622</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+ <canvas id="canvas" width="800" height="800"></canvas>
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ function go() {
+ var ctx = document.getElementById("canvas").getContext("2d");
+
+ var ex = null;
+ try {
+ ctx.createImageData(23175, 23175);
+ } catch (e) {
+ ex = e;
+ }
+ ok(ex.toString().includes("Invalid width or height"),
+ "Expected createImageData exception");
+
+ ex = null;
+ try {
+ ctx.createImageData(33000, 33000);
+ } catch (e) {
+ ex = e;
+ }
+ ok(ex.toString().includes("Invalid width or height"),
+ "Expected createImageData exception");
+
+ ex = null;
+ try {
+ ctx.getImageData(0, 0, 23175, 23175);
+ } catch (e) {
+ ex = e;
+ }
+ ok(ex.toString().includes("negative or greater than the allowed amount"),
+ "Expected getImageData exception");
+
+ ex = null;
+ try {
+ new ImageData(23175, 23175);
+ } catch (e) {
+ ex = e;
+ }
+ ok(ex.toString().includes("negative or greater than the allowed amount"),
+ "Expected ImageData constructor exception");
+
+ SimpleTest.finish();
+ }
+ go();
+ </script>
+</body>
+</html>
diff --git a/dom/bindings/test/test_lenientThis.html b/dom/bindings/test/test_lenientThis.html
new file mode 100644
index 0000000000..f4fb4200a5
--- /dev/null
+++ b/dom/bindings/test/test_lenientThis.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>[LenientThis]</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+/* global test, assert_equals */
+function noop1() { }
+function noop2() { }
+
+test(function() {
+ var desc = Object.getOwnPropertyDescriptor(Document.prototype, "onreadystatechange");
+
+ document.onreadystatechange = noop1;
+ assert_equals(document.onreadystatechange, noop1, "document.onreadystatechange == noop1");
+ assert_equals(desc.get.call({ }), undefined, "document.onreadystatechange getter.call({}) == undefined");
+}, "invoking Document.onreadystatechange's getter with an invalid this object returns undefined");
+
+test(function() {
+ var desc = Object.getOwnPropertyDescriptor(Document.prototype, "onreadystatechange");
+
+ document.onreadystatechange = noop1;
+ assert_equals(document.onreadystatechange, noop1, "document.onreadystatechange == noop1");
+ assert_equals(desc.set.call({ }, noop2), undefined, "document.onreadystatechange setter.call({}) == undefined");
+ assert_equals(document.onreadystatechange, noop1, "document.onreadystatechange == noop1 (still)");
+}, "invoking Document.onreadystatechange's setter with an invalid this object does nothing and returns undefined");
+</script>
diff --git a/dom/bindings/test/test_lookupGetter.html b/dom/bindings/test/test_lookupGetter.html
new file mode 100644
index 0000000000..5fe15059b7
--- /dev/null
+++ b/dom/bindings/test/test_lookupGetter.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=462428
+-->
+<head>
+ <title>Test for Bug 462428</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=462428">Mozilla Bug 462428</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 462428 **/
+var x = new XMLHttpRequest;
+x.open("GET", "");
+var getter = x.__lookupGetter__("readyState");
+ok(getter !== undefined, "But able to look it up the normal way");
+ok(!x.hasOwnProperty("readyState"), "property should still be on the prototype");
+
+var sawProp = false;
+for (var i in x) {
+ if (i === "readyState") {
+ sawProp = true;
+ }
+}
+
+ok(sawProp, "property should be enumerable");
+
+is(getter.call(x), 1, "the getter actually works");
+
+Object.getPrototypeOf(x).__defineSetter__("readyState", function() {});
+is(getter.call(x), 1, "the getter works after defineSetter");
+
+is(x.responseType, "", "Should have correct responseType up front");
+var setter = x.__lookupSetter__("responseType");
+setter.call(x, "document");
+is(x.responseType, "document", "the setter is bound correctly");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_namedNoIndexed.html b/dom/bindings/test/test_namedNoIndexed.html
new file mode 100644
index 0000000000..73a79d3894
--- /dev/null
+++ b/dom/bindings/test/test_namedNoIndexed.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=808991
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 808991</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=808991">Mozilla Bug 808991</a>
+<p id="display"></p>
+<div id="content" style="display: none" data-1="foo" data-bar="baz">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 808991 **/
+is($("content").dataset[1], "foo",
+ "Indexed props should work like named on dataset");
+is($("content").dataset["1"], "foo",
+ "Indexed props as strings should work like named on dataset");
+is($("content").dataset.bar, "baz",
+ "Named props should work on dataset");
+is($("content").dataset.bar, "baz",
+ "Named props as strings should work on dataset");
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_named_getter_enumerability.html b/dom/bindings/test/test_named_getter_enumerability.html
new file mode 100644
index 0000000000..3894633a3b
--- /dev/null
+++ b/dom/bindings/test/test_named_getter_enumerability.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for named getter enumerability</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+/* global test, assert_equals, assert_true, assert_false, assert_not_equals */
+test(function() {
+ var list = document.getElementsByTagName("div");
+ var desc = Object.getOwnPropertyDescriptor(list, "0");
+ assert_equals(typeof desc, "object", "Should have a '0' property");
+ assert_true(desc.enumerable, "'0' property should be enumerable");
+ desc = Object.getOwnPropertyDescriptor(list, "log");
+ assert_equals(typeof desc, "object", "Should have a 'log' property");
+ assert_false(desc.enumerable, "'log' property should not be enumerable");
+}, "Correct getOwnPropertyDescriptor behavior");
+test(function() {
+ var list = document.getElementsByTagName("div");
+ var props = [];
+ for (var prop in list) {
+ props.push(prop);
+ }
+ assert_not_equals(props.indexOf("0"), -1, "Should enumerate '0'");
+ assert_equals(props.indexOf("log"), -1, "Should not enumerate 'log'");
+}, "Correct enumeration behavior");
+test(function() {
+ var list = document.getElementsByTagName("div");
+ var props = Object.keys(list);
+ assert_not_equals(props.indexOf("0"), -1, "Keys should contain '0'");
+ assert_equals(props.indexOf("log"), -1, "Keys should not contain 'log'");
+}, "Correct keys() behavior");
+test(function() {
+ var list = document.getElementsByTagName("div");
+ var props = Object.getOwnPropertyNames(list);
+ assert_not_equals(props.indexOf("0"), -1,
+ "own prop names should contain '0'");
+ assert_not_equals(props.indexOf("log"), -1,
+ "own prop names should contain 'log'");
+}, "Correct getOwnPropertyNames() behavior");
+</script>
diff --git a/dom/bindings/test/test_observablearray.html b/dom/bindings/test/test_observablearray.html
new file mode 100644
index 0000000000..313fb67622
--- /dev/null
+++ b/dom/bindings/test/test_observablearray.html
@@ -0,0 +1,546 @@
+<!-- Any copyright is dedicated to the Public Domain.
+- http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test 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_length() {
+ 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, true, true, true, true];
+
+ let b = m.observableArrayBoolean;
+ ok(Array.isArray(b), "observable array should be an array type");
+ is(b.length, 5, "length of observable array should be 5");
+
+ [
+ // [length, shouldThrow, expectedResult]
+ ["invalid", true, false],
+ [b.length + 1, false, false],
+ [b.length, false, true],
+ [b.length - 1, false, true],
+ [0, false, true],
+ ].forEach(function([length, shouldThrow, expectedResult]) {
+ // Initialize
+ let oldValues = b.slice();
+ let oldLen = b.length;
+ let shouldSuccess = !shouldThrow && expectedResult;
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+ deleteCallbackTests = null;
+ if (shouldSuccess) {
+ let deleteCallbackIndex = b.length - 1;
+ deleteCallbackTests = function(_value, _index) {
+ info(`delete callback for ${_index}`);
+ is(_value, oldValues[deleteCallbackIndex], "deleteCallbackTests: test value argument");
+ is(_index, deleteCallbackIndex, "deleteCallbackTests: test index argument");
+ deleteCallbackIndex--;
+ };
+ }
+
+ // Test
+ info(`setting length to ${length}`);
+ try {
+ b.length = length;
+ ok(!shouldThrow, `setting length should throw`);
+ } catch(e) {
+ ok(shouldThrow, `setting length throws ${e}`);
+ }
+ is(setCallbackCount, 0, "setCallback should not be called");
+ is(deleteCallbackCount, shouldSuccess ? (oldLen - length) : 0, "deleteCallback count");
+ isDeeply(b, shouldSuccess ? oldValues.slice(0, length) : oldValues, "property values");
+ is(b.length, shouldSuccess ? length : oldLen, `length of observable array`);
+ });
+});
+
+add_task(function testObservableArray_length_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 = [true, true, false, false, false];
+
+ let b = m.observableArrayBoolean;
+ ok(Array.isArray(b), "observable array should be an array type");
+ is(b.length, 5, "length of observable array should be 5");
+
+ // Initialize
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+
+ // Test
+ info(`setting length to 0`);
+ try {
+ b.length = 0;
+ ok(false, `setting length should throw`);
+ } catch(e) {
+ ok(true, `setting length throws ${e}`);
+ }
+ is(setCallbackCount, 0, "setCallback should not be called");
+ is(deleteCallbackCount, 4, "deleteCallback should be called");
+ isDeeply(b, [true, true], "property values");
+ is(b.length, 2, `length of observable array`);
+});
+
+add_task(function testObservableArray_setter() {
+ 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");
+
+ [
+ // [values, shouldThrow]
+ ["invalid", true],
+ [[1,[],{},"invalid"], false],
+ [[0,NaN,null,undefined,""], false],
+ [[true,true], false],
+ [[false,false,false], false],
+ ].forEach(function([values, shouldThrow]) {
+ // Initialize
+ let oldValues = b.slice();
+ let oldLen = b.length;
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+ setCallbackTests = null;
+ deleteCallbackTests = null;
+ if (!shouldThrow) {
+ let setCallbackIndex = 0;
+ setCallbackTests = function(_value, _index) {
+ info(`set callback for ${_index}`);
+ is(_value, !!values[setCallbackIndex], "setCallbackTests: test value argument");
+ is(_index, setCallbackIndex, "setCallbackTests: test index argument");
+ setCallbackIndex++;
+ };
+
+ let deleteCallbackIndex = b.length - 1;
+ deleteCallbackTests = function(_value, _index) {
+ info(`delete callback for ${_index}`);
+ is(_value, oldValues[deleteCallbackIndex], "deleteCallbackTests: test value argument");
+ is(_index, deleteCallbackIndex, "deleteCallbackTests: test index argument");
+ deleteCallbackIndex--;
+ };
+ }
+
+ // Test
+ info(`setting value to ${JSON.stringify(values)}`);
+ try {
+ m.observableArrayBoolean = values;
+ ok(!shouldThrow, `setting value should not throw`);
+ } catch(e) {
+ ok(shouldThrow, `setting value throws ${e}`);
+ }
+ is(setCallbackCount, shouldThrow ? 0 : values.length, "setCallback count");
+ is(deleteCallbackCount, oldLen, "deleteCallback should be called");
+ isDeeply(b, shouldThrow ? [] : values.map(v => !!v), "property values");
+ is(b.length, shouldThrow ? 0 : values.length, `length of observable array`);
+ });
+});
+
+add_task(function testObservableArray_setter_invalid_item() {
+ let setCallbackCount = 0;
+ let deleteCallbackCount = 0;
+ let setCallbackTests = null;
+ let deleteCallbackTests = null;
+
+ let m = new TestInterfaceObservableArray({
+ setInterfaceCallback(value, index) {
+ setCallbackCount++;
+ if (typeof setCallbackTests === 'function') {
+ setCallbackTests(value, index);
+ }
+ },
+ deleteInterfaceCallback(value, index) {
+ deleteCallbackCount++;
+ if (typeof deleteCallbackTests === 'function') {
+ deleteCallbackTests(value, index);
+ }
+ },
+ });
+
+ let b = m.observableArrayInterface;
+ ok(Array.isArray(b), "observable array should be an array type");
+ is(b.length, 0, "length of observable array should be 0");
+
+ [
+ // [values, shouldThrow]
+ [[m,m,m,m], false],
+ [["invalid"], true],
+ [[m,m], false],
+ [[m,"invalid"], true],
+ [[m,m,m], false],
+ ].forEach(function([values, shouldThrow]) {
+ // Initialize
+ let oldValues = b.slice();
+ let oldLen = b.length;
+ let setCallbackIndex = 0;
+ setCallbackTests = function(_value, _index) {
+ info(`set callback for ${_index}`);
+ is(_value, values[setCallbackIndex], "setCallbackTests: test value argument");
+ is(_index, setCallbackIndex, "setCallbackTests: test index argument");
+ setCallbackIndex++;
+ };
+ let deleteCallbackIndex = b.length - 1;
+ deleteCallbackTests = function(_value, _index) {
+ info(`delete callback for ${_index}`);
+ is(_value, oldValues[deleteCallbackIndex], "deleteCallbackTests: test value argument");
+ is(_index, deleteCallbackIndex, "deleteCallbackTests: test index argument");
+ deleteCallbackIndex--;
+ };
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+
+ // Test
+ info(`setting value to ${values}`);
+ try {
+ m.observableArrayInterface = values;
+ ok(!shouldThrow, `setting value should not throw`);
+ } catch(e) {
+ ok(shouldThrow, `setting value throws ${e}`);
+ }
+ is(setCallbackCount, shouldThrow ? 0 : values.length, "setCallback count");
+ is(deleteCallbackCount, shouldThrow ? 0 : oldLen, "deleteCallback should be called");
+ isDeeply(b, shouldThrow ? oldValues : values, "property values");
+ is(b.length, shouldThrow ? oldLen : values.length, `length of observable array`);
+ });
+});
+
+add_task(function testObservableArray_setter_callback_throw() {
+ let setCallbackCount = 0;
+ let deleteCallbackCount = 0;
+
+ let m = new TestInterfaceObservableArray({
+ setBooleanCallback(value, index) {
+ setCallbackCount++;
+ if (index >= 3) {
+ throw new Error("setBooleanCallback");
+ }
+ },
+ deleteBooleanCallback(value, index) {
+ deleteCallbackCount++;
+ if (value) {
+ 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");
+
+ [
+ // [values, shouldThrow, expectedLength, expectedSetCbCount, expectedDeleteCbCount]
+ [[false,false], false, 2, 2, 3],
+ [[false,true,false,false], true, 3, 4, 2],
+ [[false,false,true], true, 2, 0, 2],
+ ].forEach(function([values, shouldThrow, expectedLength, expectedSetCbCount,
+ expectedDeleteCbCount]) {
+ // Initialize
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+
+ // Test
+ info(`setting value to ${values}`);
+ try {
+ m.observableArrayBoolean = values;
+ ok(!shouldThrow, `setting value should not throw`);
+ } catch(e) {
+ ok(shouldThrow, `setting length throws ${e}`);
+ }
+ is(setCallbackCount, expectedSetCbCount, "setCallback should be called");
+ is(deleteCallbackCount, expectedDeleteCbCount, "deleteCallback should be called");
+ is(b.length, expectedLength, `length of observable array`);
+ });
+});
+
+add_task(function testObservableArray_indexed_setter() {
+ 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, expectedResult]
+ [b.length + 1, false, false],
+ [b.length, false, true],
+ [b.length + 1, false, true],
+ [b.length + 1, true, true],
+ ].forEach(function([index, value, expectedResult]) {
+ // 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 {
+ b[index] = value;
+ ok(true, `setting value should not throw`);
+ } catch(e) {
+ ok(false, `setting value throws ${e}`);
+ }
+ is(setCallbackCount, expectedResult ? 1 : 0, "setCallback should be called");
+ is(deleteCallbackCount, (oldLen > index) ? 1 : 0, "deleteCallback should be called");
+ is(b[index], expectedResult ? value : oldValue, `property value`);
+ is(b.length, expectedResult ? Math.max(oldLen, index + 1) : oldLen, `length of observable array`);
+ });
+});
+
+add_task(function testObservableArray_indexed_setter_invalid() {
+ let setCallbackCount = 0;
+ let deleteCallbackCount = 0;
+ let setCallbackTests = null;
+ let deleteCallbackTests = null;
+
+ let m = new TestInterfaceObservableArray({
+ setInterfaceCallback(value, index) {
+ setCallbackCount++;
+ if (typeof setCallbackTests === 'function') {
+ setCallbackTests(value, index);
+ }
+ },
+ deleteInterfaceCallback(value, index) {
+ deleteCallbackCount++;
+ if (typeof deleteCallbackTests === 'function') {
+ deleteCallbackTests(value, index);
+ }
+ },
+ });
+
+ let b = m.observableArrayInterface;
+ 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, "invalid", true],
+ [b.length, m, false],
+ [b.length + 1, m, false],
+ [b.length + 1, "invalid", true],
+ ].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 {
+ b[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) && !shouldThrow) ? 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_indexed_setter_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 {
+ b[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 should be called");
+ is(deleteCallbackCount, (oldLen > index) ? 1 : 0, "deleteCallback should be called");
+ 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_object() {
+ let setCallbackCount = 0;
+ let deleteCallbackCount = 0;
+ let callbackIndex = 0;
+
+ let values = [
+ {property1: false, property2: "property2"},
+ {property1: [], property2: 2},
+ ];
+
+ let m = new TestInterfaceObservableArray({
+ setObjectCallback(value, index) {
+ setCallbackCount++;
+ is(index, callbackIndex++, "setCallbackTests: test index argument");
+ isDeeply(values[index], value, "setCallbackTests: test value argument");
+ },
+ deleteObjectCallback(value, index) {
+ deleteCallbackCount++;
+ },
+ });
+
+ m.observableArrayObject = values;
+
+ let b = m.observableArrayObject;
+ ok(Array.isArray(b), "observable array should be an array type");
+ is(b.length, 2, "length of observable array should be 2");
+ is(setCallbackCount, values.length, "setCallback should be called");
+ is(deleteCallbackCount, 0, "deleteCallback should not be called");
+
+ for(let i = 0; i < values.length; i++) {
+ isDeeply(values[i], b[i], `check index ${i}`);
+ }
+});
+
+add_task(function testObservableArray_xrays() {
+ let m = new TestInterfaceObservableArray({
+ setObjectCallback(value, index) {
+ ok(false, "Shouldn't reach setObjectCallback");
+ },
+ deleteObjectCallback(value, index) {
+ ok(false, "Shouldn't reach deleteObjectCallback");
+ },
+ });
+
+ let wrapper = SpecialPowers.wrap(m);
+ ok(SpecialPowers.Cu.isXrayWrapper(wrapper), "Should be a wrapper");
+ let observableArray = wrapper.observableArrayBoolean;
+ ok(!!observableArray, "Should not throw");
+ is("length" in observableArray, false, "Should be opaque");
+
+ try {
+ wrapper.observableArrayBoolean = [true, false, false];
+ ok(false, "Expected to throw, for now");
+ } catch (ex) {}
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/bindings/test/test_observablearray_helper.html b/dom/bindings/test/test_observablearray_helper.html
new file mode 100644
index 0000000000..d2b4897cac
--- /dev/null
+++ b/dom/bindings/test/test_observablearray_helper.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>
diff --git a/dom/bindings/test/test_observablearray_proxyhandler.html b/dom/bindings/test/test_observablearray_proxyhandler.html
new file mode 100644
index 0000000000..d7d8810981
--- /dev/null
+++ b/dom/bindings/test/test_observablearray_proxyhandler.html
@@ -0,0 +1,859 @@
+<!-- Any copyright is dedicated to the Public Domain.
+- http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test 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 testObservableArrayExoticObjects_defineProperty() {
+ 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);
+ }
+ },
+ });
+ m.observableArrayBoolean = [true, true, true];
+
+ 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 0");
+
+ // Test length
+ [
+ // [descriptor, shouldThrow, expectedResult]
+ // Invalid descriptor
+ [{configurable: true, value: 0}, false, false],
+ [{enumerable: true, value: 0}, false, false],
+ [{writable: false, value: 0}, false, false],
+ [{get: ()=>{}}, false, false],
+ [{set: ()=>{}}, false, false],
+ [{get: ()=>{}, set: ()=>{}}, false, false],
+ [{get: ()=>{}, value: 0}, true],
+ // Invalid length value
+ [{value: 1.9}, true],
+ [{value: "invalid"}, true],
+ [{value: {}}, true],
+ // length value should not greater than current length
+ [{value: b.length + 1}, false, false],
+ // descriptor without value
+ [{configurable: false, enumerable: false, writable: true}, false, true],
+ // Success
+ [{value: b.length}, false, true],
+ [{value: b.length - 1}, false, true],
+ [{value: 0}, false, true],
+ ].forEach(function([descriptor, shouldThrow, expectedResult]) {
+ // Initialize
+ let oldLen = b.length;
+ let oldValues = b.slice();
+ let deleteCallbackIndex = oldLen - 1;
+ let success = expectedResult && "value" in descriptor;
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+ setCallbackTests = null;
+ deleteCallbackTests = function(_value, _index) {
+ is(_value, oldValues[deleteCallbackIndex], "deleteCallbackTests: test value argument");
+ is(_index, deleteCallbackIndex, "deleteCallbackTests: test index argument");
+ deleteCallbackIndex--;
+ };
+
+ // Test
+ info(`defining "length" property with ${JSON.stringify(descriptor)}`);
+ try {
+ is(Reflect.defineProperty(b, "length", descriptor), expectedResult,
+ `Reflect.defineProperty should return ${expectedResult}`);
+ ok(!shouldThrow, "Reflect.defineProperty should not throw");
+ } catch(e) {
+ ok(shouldThrow, `Reflect.defineProperty throws ${e}`);
+ }
+ is(setCallbackCount, 0, "setCallback count");
+ is(deleteCallbackCount, success ? oldLen - descriptor.value : 0, "deleteCallback count");
+ isDeeply(b, success ? oldValues.slice(0, descriptor.value) : oldValues, "property values");
+ is(b.length, success ? descriptor.value : oldLen, "length of observable array");
+ });
+
+ // Test indexed value
+ [
+ // [index, descriptor, shouldThrow, expectedResult]
+ // Invalid descriptor
+ [0, {configurable: false, value: true}, false, false],
+ [0, {enumerable: false, value: true}, false, false],
+ [0, {writable: false, value: true}, false, false],
+ [0, {get: ()=>{}}, false, false],
+ [0, {set: ()=>{}}, false, false],
+ [0, {get: ()=>{}, set: ()=>{}}, false, false],
+ [0, {get: ()=>{}, value: true}, true],
+ // Index could not greater than last index + 1.
+ [b.length + 1, {configurable: true, enumerable: true, value: true}, false, false],
+ // descriptor without value
+ [b.length, {configurable: true, enumerable: true}, false, true],
+ // Success
+ [b.length, {configurable: true, enumerable: true, value: true}, false, true],
+ [b.length + 1, {configurable: true, enumerable: true, value: true}, false, true],
+ ].forEach(function([index, descriptor, shouldThrow, expectedResult]) {
+ // Initialize
+ let oldLen = b.length;
+ let oldValue = b[index];
+ let success = expectedResult && "value" in descriptor;
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+ setCallbackTests = function(_value, _index) {
+ is(_value, descriptor.value, "setCallbackTests: test value argument");
+ is(_index, index, "setCallbackTests: test index argument");
+ };
+ deleteCallbackTests = function(_value, _index) {
+ is(_value, oldValue, "deleteCallbackTests: test value argument");
+ is(_index, index, "deleteCallbackTests: test index argument");
+ };
+
+ // Test
+ info(`defining ${index} property with ${JSON.stringify(descriptor)}`);
+ try {
+ is(Reflect.defineProperty(b, index, descriptor), expectedResult,
+ `Reflect.defineProperty should return ${expectedResult}`);
+ ok(!shouldThrow, "Reflect.defineProperty should not throw");
+ } catch(e) {
+ ok(shouldThrow, `Reflect.defineProperty throws ${e}`);
+ }
+ is(setCallbackCount, success ? 1 : 0, "setCallback count");
+ is(deleteCallbackCount, (oldLen > index) ? 1 : 0, "deleteCallback count");
+ is(b[index], success ? descriptor.value : oldValue, "property value");
+ is(b.length, success ? Math.max(index + 1, oldLen) : oldLen, "length of observable array");
+ });
+
+ // Test other property
+ [
+ // [property, descriptor, shouldThrow, expectedResult]
+ ["prop1", {configurable: false, value: "value1"}, false, true],
+ ["prop1", {configurable: true, value: "value2"}, false, false],
+ ["prop2", {enumerable: false, value: 5}, false, true],
+ ["prop3", {enumerable: false, value: []}, false, true],
+ ["prop4", {enumerable: false, value: {}}, false, true],
+ ["prop5", {get: ()=>{}, value: true}, true, false],
+ ["prop6", {get: ()=>{}, set: ()=>{}}, false, true],
+ ].forEach(function([property, descriptor, shouldThrow, expectedResult]) {
+ // Initialize
+ let oldValue = b[property];
+ let oldLen = b.length;
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+ setCallbackTests = null;
+ deleteCallbackTests = null;
+
+ // Test
+ info(`defining ${property} property with ${JSON.stringify(descriptor)}`);
+ try {
+ is(Reflect.defineProperty(b, property, descriptor), expectedResult,
+ `Reflect.defineProperty should return ${expectedResult}`);
+ ok(!shouldThrow, "Reflect.defineProperty should not throw");
+ } catch(e) {
+ ok(shouldThrow, `Reflect.defineProperty throws ${e}`);
+ }
+ is(setCallbackCount, 0, "setCallback count");
+ is(deleteCallbackCount, 0, "deleteCallback count");
+ is(b[property], expectedResult ? descriptor.value : oldValue, "property value");
+ is(b.length, oldLen, "length of observable array");
+ });
+});
+
+add_task(function testObservableArrayExoticObjects_defineProperty_callback_throw() {
+ let setCallbackCount = 0;
+ let deleteCallbackCount = 0;
+
+ const minLen = 3;
+ let m = new TestInterfaceObservableArray({
+ setBooleanCallback(value, index) {
+ setCallbackCount++;
+ if (value) {
+ throw new Error("setBooleanCallback");
+ }
+ },
+ deleteBooleanCallback(value, index) {
+ deleteCallbackCount++;
+ if (index < minLen) {
+ throw new Error("deleteBooleanCallback");
+ }
+ },
+ });
+ m.observableArrayBoolean = [false, false, false, false, false];
+
+ let b = m.observableArrayBoolean;
+ ok(Array.isArray(b), "observable array should be an array type");
+ is(b.length, 5, "length of observable array should be 3");
+
+ // Test length
+ [
+ // [length, shouldThrow]
+ [b.length, false],
+ [b.length - 1, false],
+ [0, true],
+ ].forEach(function([length, shouldThrow]) {
+ // Initialize
+ let oldValues = b.slice();
+ let oldLen = b.length;
+ let descriptor = {value: length};
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+
+ // Test
+ info(`defining "length" property with ${JSON.stringify(descriptor)}`);
+ try {
+ ok(Reflect.defineProperty(b, "length", descriptor),
+ "Reflect.defineProperty should return true");
+ ok(!shouldThrow, "Reflect.defineProperty should not throw");
+ } catch(e) {
+ ok(shouldThrow, `Reflect.defineProperty throws ${e}`);
+ }
+ is(setCallbackCount, 0, "setCallback count");
+ is(deleteCallbackCount, oldLen - (shouldThrow ? minLen - 1 : length), "deleteCallback count");
+ isDeeply(b, oldValues.slice(0, shouldThrow ? minLen : length), "property values");
+ is(b.length, shouldThrow ? minLen : length, "length of observable array");
+ });
+
+ // Test indexed value
+ [
+ // [index, value, shouldThrow]
+ [b.length, true, true],
+ [b.length, false, false],
+ [b.length + 1, false, false],
+ [b.length + 1, true, true],
+ [0, true, true],
+ [0, false, true],
+ ].forEach(function([index, value, shouldThrow]) {
+ // Initialize
+ let oldValue = b[index];
+ let oldLen = b.length;
+ let descriptor = {configurable: true, enumerable: true, value};
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+
+ // Test
+ info(`defining ${index} property with ${JSON.stringify(descriptor)}`);
+ try {
+ ok(Reflect.defineProperty(b, index, descriptor), "Reflect.defineProperty should return true");
+ ok(!shouldThrow, "Reflect.defineProperty should not throw");
+ } catch(e) {
+ ok(shouldThrow, `Reflect.defineProperty throws ${e}`);
+ }
+ is(setCallbackCount, (index < minLen) ? 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");
+ });
+
+ // Test other property
+ [
+ // [property, descriptor, expectedResult]
+ ["prop1", {configurable: false, value: "value1"}, true],
+ ["prop1", {configurable: true, value: "value2"}, false],
+ ["prop2", {enumerable: false, value: 5}, true],
+ ["prop3", {enumerable: false, value: []}, true],
+ ["prop4", {enumerable: false, value: {}}, true],
+ ].forEach(function([property, descriptor, expectedResult]) {
+ // Initialize
+ let oldValue = b[property];
+ let oldLen = b.length;
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+
+ // Test
+ info(`defining ${property} property with ${JSON.stringify(descriptor)}`);
+ try {
+ is(Reflect.defineProperty(b, property, descriptor), expectedResult,
+ `Reflect.defineProperty should return ${expectedResult}`);
+ ok(true, "Reflect.defineProperty should not throw");
+ } catch(e) {
+ ok(false, `Reflect.defineProperty throws ${e}`);
+ }
+ is(setCallbackCount, 0, "setCallback count");
+ is(deleteCallbackCount, 0, "deleteCallback count");
+ is(b[property], expectedResult ? descriptor.value : oldValue, "property value");
+ is(b.length, oldLen, "length of observable array");
+ });
+});
+
+add_task(function testObservableArrayExoticObjects_deleteProperty() {
+ 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, true];
+
+ let b = m.observableArrayBoolean;
+ ok(Array.isArray(b), "observable array should be an array type");
+ is(b.length, 2, "length of observable array should be 2");
+
+ // Test length
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+ info("deleting length property");
+ ok(!Reflect.deleteProperty(b, "length"), "test result of deleting length property");
+ is(setCallbackCount, 0, "setCallback should not be called");
+ is(deleteCallbackCount, 0, "deleteCallback should not be called");
+ is(b.length, 2, "length should still be 2");
+
+ // Test indexed value
+ [
+ // [index, expectedResult]
+ [2, false],
+ [0, false],
+ [1, true],
+ ].forEach(function([index, expectedResult]) {
+ // Initialize
+ let oldLen = b.length;
+ let oldValue = b[index];
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+ deleteCallbackTests = function(_value, _index) {
+ is(_value, oldValue, "deleteCallbackTests: test value argument");
+ is(_index, index, "deleteCallbackTests: test index argument");
+ };
+
+ // Test
+ info(`deleting ${index} property`);
+ is(Reflect.deleteProperty(b, index), expectedResult,
+ `Reflect.deleteProperty should return ${expectedResult}`);
+ is(setCallbackCount, 0, "setCallback count");
+ is(deleteCallbackCount, expectedResult ? 1 : 0, "deleteCallback count");
+ is(b[index], expectedResult ? undefined : oldValue, "property value");
+ is(b.length, expectedResult ? oldLen - 1 : oldLen,
+ "length of observable array");
+ });
+
+ // Test other property
+ [
+ // [property, value]
+ ["prop1", "value1"],
+ ["prop2", 5],
+ ["prop3", []],
+ ["prop4", {}],
+ ].forEach(function([property, value]) {
+ // Initialize
+ b[property] = value;
+ let oldLen = b.length;
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+ deleteCallbackTests = null;
+
+ // Test
+ info(`deleting ${property} property`);
+ is(b[property], value, `property value should be ${value} before deleting`);
+ ok(Reflect.deleteProperty(b, property), "Reflect.deleteProperty should return true");
+ is(setCallbackCount, 0, "setCallback count");
+ is(deleteCallbackCount, 0, "deleteCallback count");
+ is(b[property], undefined, "property value should be undefined after deleting");
+ is(b.length, oldLen, "length of observable array");
+ });
+});
+
+add_task(function testObservableArrayExoticObjects_deleteProperty_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 = [true, false];
+
+ let b = m.observableArrayBoolean;
+ ok(Array.isArray(b), "observable array should be an array type");
+ is(b.length, 2, "length of observable array should be 2");
+
+ // Test indexed value
+ let index = b.length;
+ while (index--) {
+ // Initialize
+ let oldValue = b[index];
+ let oldLen = b.length;
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+
+ // Test
+ info(`deleting index ${index}`);
+ try {
+ ok(Reflect.deleteProperty(b, index), "Reflect.deleteProperty should return true");
+ ok(!oldValue, "Reflect.deleteProperty should not throw");
+ } catch(e) {
+ ok(oldValue, `Reflect.deleteProperty throws ${e}`);
+ }
+ is(setCallbackCount, 0, "setCallback count");
+ is(deleteCallbackCount, 1, "deleteCallback count");
+ is(b[index], oldValue ? oldValue : undefined, "property value");
+ is(b.length, oldValue ? oldLen : oldLen - 1, "length of observable array");
+ }
+
+ // Test other property
+ [
+ // [property, value]
+ ["prop1", "value1"],
+ ["prop2", 5],
+ ["prop3", []],
+ ["prop4", {}],
+ ["prop5", false],
+ ].forEach(function([property, value]) {
+ // Initialize
+ b[property] = value;
+ let oldLen = b.length;
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+
+ // Test
+ info(`deleting ${property} property`);
+ is(b[property], value, `property value should be ${JSON.stringify(value)} before deleting`);
+ try {
+ ok(Reflect.deleteProperty(b, property), `Reflect.deleteProperty should return true`);
+ ok(true, "Reflect.deleteProperty should not throw");
+ } catch(e) {
+ ok(false, `Reflect.deleteProperty throws ${e}`);
+ }
+ is(setCallbackCount, 0, "setCallback count");
+ is(deleteCallbackCount, 0, "deleteCallback count");
+ is(b[property], undefined, `property value should be undefined after deleting`);
+ is(b.length, oldLen, "length of observable array");
+ });
+});
+
+add_task(function testObservableArrayExoticObjects_get() {
+ let m = new TestInterfaceObservableArray();
+ m.observableArrayBoolean = [true, false];
+
+ let b = m.observableArrayBoolean;
+ ok(Array.isArray(b), "observable array should be an array type");
+ is(b.length, 2, "length of observable array should be 2");
+
+ // Test length
+ is(Reflect.get(b, "length"), 2, "test result of getting length property");
+
+ // Test indexed value
+ is(Reflect.get(b, 0), true, "test result of getting index 0");
+ is(Reflect.get(b, 1), false, "test result of getting index 1");
+ is(Reflect.get(b, 2), undefined, "test result of getting index 2");
+
+ // Test other property
+ [
+ // [property, value]
+ ["prop1", "value1"],
+ ["prop2", 5],
+ ["prop3", []],
+ ["prop4", {}],
+ ].forEach(function([property, value]) {
+ is(Reflect.get(b, property), undefined, `test ${property} property before setting property value`);
+ b[property] = value;
+ is(Reflect.get(b, property), value, `test ${property} property after setting property value`);
+ });
+});
+
+add_task(function testObservableArrayExoticObjects_getOwnPropertyDescriptor() {
+ function TestDescriptor(object, property, exist, configurable, enumerable,
+ writable, value) {
+ let descriptor = Reflect.getOwnPropertyDescriptor(object, property);
+ if (!exist) {
+ is(descriptor, undefined, `descriptor of ${property} property should be undefined`);
+ return;
+ }
+
+ is(descriptor.configurable, configurable, `test descriptor of ${property} property (configurable)`);
+ is(descriptor.enumerable, enumerable, `test descriptor of ${property} property (enumerable)`);
+ is(descriptor.writable, writable, `test descriptor of ${property} property (writable)`);
+ is(descriptor.value, value, `test descriptor of ${property} property (value)`);
+ }
+
+ let m = new TestInterfaceObservableArray();
+ m.observableArrayBoolean = [true, false];
+
+ let b = m.observableArrayBoolean;
+ ok(Array.isArray(b), "observable array should be an array type");
+ is(b.length, 2, "length of observable array should be 2");
+
+ // Test length
+ TestDescriptor(b, "length", true, false /* configurable */,
+ false /* enumerable */, true /* writable */ , 2 /* value */);
+
+ // Test indexed value
+ TestDescriptor(b, 0, true, true /* configurable */, true /* enumerable */,
+ true /* writable */ , true /* value */);
+ TestDescriptor(b, 1, true, true /* configurable */, true /* enumerable */,
+ true /* writable */ , false /* value */);
+ TestDescriptor(b, 2, false);
+
+ // Test other property
+ [
+ // [property, value, configurable, enumerable, writable]
+ ["prop1", "value1", true, true, true],
+ ["prop2", 5, true, true, false],
+ ["prop3", [], true, false, false],
+ ["prop4", {}, false, false, false],
+ ].forEach(function([property, value, configurable, enumerable, writable]) {
+ Object.defineProperty(b, property, {
+ value,
+ configurable,
+ enumerable,
+ writable,
+ });
+ TestDescriptor(b, property, true, configurable, enumerable, writable , value);
+ });
+});
+
+add_task(function testObservableArrayExoticObjects_has() {
+ let m = new TestInterfaceObservableArray();
+ m.observableArrayBoolean = [true, false];
+
+ let b = m.observableArrayBoolean;
+ ok(Array.isArray(b), "observable array should be an array type");
+ is(b.length, 2, "length of observable array should be 2");
+
+ // Test length
+ ok(Reflect.has(b, "length"), `test length property`);
+
+ // Test indexed value
+ ok(Reflect.has(b, 0), `test 0 property`);
+ ok(Reflect.has(b, 1), `test 1 property`);
+ ok(!Reflect.has(b, 2), `test 2 property`);
+
+ // Test other property
+ [
+ // [property, value]
+ ["prop1", "value1"],
+ ["prop2", 5],
+ ["prop3", []],
+ ["prop4", {}],
+ ].forEach(function([property, value]) {
+ ok(!Reflect.has(b, property), `test ${property} property before setting property value`);
+ b[property] = value;
+ ok(Reflect.has(b, property), `test ${property} property after setting property value`);
+ });
+});
+
+add_task(function testObservableArrayExoticObjects_ownKeys() {
+ let m = new TestInterfaceObservableArray();
+ m.observableArrayBoolean = [true, false];
+
+ let b = m.observableArrayBoolean;
+ ok(Array.isArray(b), "observable array should be an array type");
+ is(b.length, 2, "length of observable array should be 2");
+
+ // Add other properties
+ b.prop1 = "value1";
+ b.prop2 = 5;
+ b.prop3 = [];
+ b.prop4 = {};
+
+ let keys = Reflect.ownKeys(b);
+ SimpleTest.isDeeply(keys, ["0", "1", "length", "prop1", "prop2", "prop3", "prop4"], `test property keys`);
+});
+
+add_task(function testObservableArrayExoticObjects_preventExtensions() {
+ 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 should be 0");
+
+ // Test preventExtensions
+ ok(Reflect.isExtensible(b), "test isExtensible before preventExtensions");
+ ok(!Reflect.preventExtensions(b), "test preventExtensions");
+ ok(Reflect.isExtensible(b), "test isExtensible after preventExtensions");
+});
+
+add_task(function testObservableArrayExoticObjects_set() {
+ 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);
+ }
+ },
+ });
+ m.observableArrayBoolean = [true, true, true];
+
+ 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");
+
+ // Test length
+ [
+ // [length, shouldThrow, expectedResult]
+ // Invalid length value
+ [1.9, true],
+ ['invalid', true],
+ [{}, true],
+ // length value should not greater than current length
+ [b.length + 1, false, false],
+ // Success
+ [b.length, false, true],
+ [b.length - 1, false, true],
+ [0, false, true],
+ ].forEach(function([length, shouldThrow, expectedResult]) {
+ // Initialize
+ let oldLen = b.length;
+ let oldValues = b.slice();
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+ setCallbackTests = null;
+ let deleteCallbackIndex = oldLen - 1;
+ deleteCallbackTests = function(_value, _index) {
+ is(_value, oldValues[deleteCallbackIndex], "deleteCallbackTests: test value argument");
+ is(_index, deleteCallbackIndex, "deleteCallbackTests: test index argument");
+ deleteCallbackIndex--;
+ };
+
+ // Test
+ info(`setting "length" property value to ${length}`);
+ try {
+ is(Reflect.set(b, "length", length), expectedResult, `Reflect.set should return ${expectedResult}`);
+ ok(!shouldThrow, "Reflect.set should not throw");
+ } catch(e) {
+ ok(shouldThrow, `Reflect.set throws ${e}`);
+ }
+ is(setCallbackCount, 0, "setCallback count");
+ is(deleteCallbackCount, expectedResult ? oldLen - length : 0, "deleteCallback count");
+ isDeeply(b, expectedResult ? oldValues.slice(0, length) : oldValues, "property values");
+ is(b.length, expectedResult ? length : oldLen, "length of observable array");
+ });
+
+ // Test indexed value
+ [
+ // [index, value, shouldThrow, expectedResult]
+ // Index could not greater than last index.
+ [b.length + 1, true, false, false],
+ // Success
+ [b.length, true, false, true],
+ [b.length + 1, true, false, true],
+ ].forEach(function([index, value, shouldThrow, expectedResult]) {
+ // Initialize
+ let oldLen = b.length;
+ let oldValue = b[index];
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+ setCallbackTests = function(_value, _index) {
+ is(_value, value, "setCallbackTests: test value argument");
+ is(_index, index, "setCallbackTests: test index argument");
+ };
+ deleteCallbackTests = function(_value, _index) {
+ is(_value, oldValue, "deleteCallbackTests: test value argument");
+ is(_index, index, "deleteCallbackTests: test index argument");
+ };
+
+ // Test
+ info(`setting ${index} property to ${value}`);
+ try {
+ is(Reflect.set(b, index, value), expectedResult, `Reflect.set should return ${expectedResult}`);
+ ok(!shouldThrow, "Reflect.set should not throw");
+ } catch(e) {
+ ok(shouldThrow, `Reflect.set throws ${e}`);
+ }
+ is(setCallbackCount, expectedResult ? 1 : 0, "setCallback count");
+ is(deleteCallbackCount, (oldLen > index) ? 1 : 0, "deleteCallback count");
+ is(b[index], expectedResult ? value : oldValue, "property value");
+ is(b.length, expectedResult ? Math.max(index + 1, oldLen) : oldLen, "length of observable array");
+ });
+
+ // Test other property
+ [
+ // [property, value]
+ ["prop1", "value1"],
+ ["prop1", "value2"],
+ ["prop2", 5],
+ ["prop3", []],
+ ["prop4", {}],
+ ].forEach(function([property, value]) {
+ // Initialize
+ let oldLen = b.length;
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+ setCallbackTests = null;
+ deleteCallbackTests = null;
+
+ // Test
+ info(`setting ${property} property to ${value}`);
+ ok(Reflect.set(b, property, value), "Reflect.defineProperty should return true");
+ is(setCallbackCount, 0, "setCallback count");
+ is(deleteCallbackCount, 0, "deleteCallback count");
+ is(b[property], value, "property value");
+ is(b.length, oldLen, "length of observable array");
+ });
+});
+
+add_task(function testObservableArrayExoticObjects_set_callback_throw() {
+ let setCallbackCount = 0;
+ let deleteCallbackCount = 0;
+
+ const minLen = 3;
+ let m = new TestInterfaceObservableArray({
+ setBooleanCallback(value, index) {
+ setCallbackCount++;
+ if (value) {
+ throw new Error("setBooleanCallback");
+ }
+ },
+ deleteBooleanCallback(value, index) {
+ deleteCallbackCount++;
+ if (index < minLen) {
+ throw new Error("deleteBooleanCallback");
+ }
+ },
+ });
+ m.observableArrayBoolean = [false, false, false, false, false];
+
+ let b = m.observableArrayBoolean;
+ ok(Array.isArray(b), "observable array should be an array type");
+ is(b.length, 5, "length of observable array should be 3");
+
+ // Test length
+ [
+ // [value, shouldThrow]
+ [b.length, false],
+ [b.length - 1, false],
+ [0, true],
+ ].forEach(function([length, shouldThrow]) {
+ // Initialize
+ let oldValues = b.slice();
+ let oldLen = b.length;
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+
+ // Test
+ info(`setting "length" property to ${length}`);
+ try {
+ ok(Reflect.set(b, "length", length), "Reflect.set should return true");
+ ok(!shouldThrow, `Reflect.set should not throw`);
+ } catch(e) {
+ ok(shouldThrow, `Reflect.set throws ${e}`);
+ }
+ is(setCallbackCount, 0, "setCallback should not be called");
+ is(deleteCallbackCount, oldLen - (shouldThrow ? minLen - 1 : length), "deleteCallback count");
+ isDeeply(b, oldValues.slice(0, shouldThrow ? minLen : length), "property values");
+ is(b.length, shouldThrow ? minLen : length, "length of observable array");
+ });
+
+ // Test indexed value
+ [
+ // [index, value, shouldThrow]
+ [b.length, true, true],
+ [b.length, false, false],
+ [b.length + 1, false, false],
+ [b.length + 1, 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 ${index} property to ${value}`);
+ try {
+ ok(Reflect.set(b, index, value), "Reflect.set should return true");
+ ok(!shouldThrow, `Reflect.set should not throw`);
+ } catch(e) {
+ ok(shouldThrow, `Reflect.set throws ${e}`);
+ }
+ is(setCallbackCount, (index < minLen) ? 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");
+ });
+
+ // Test other property
+ [
+ ["prop1", "value1"],
+ ["prop1", "value2"],
+ ["prop2", 5],
+ ["prop3", []],
+ ["prop4", {}],
+ ].forEach(function([property, value]) {
+ // Initialize
+ let oldLen = b.length;
+ setCallbackCount = 0;
+ deleteCallbackCount = 0;
+
+ // Test
+ info(`setting ${property} property to ${JSON.stringify(value)}`);
+ try {
+ ok(Reflect.set(b, property, value), "Reflect.set should return true");
+ ok(true, `Reflect.set should not throw`);
+ } catch(e) {
+ ok(false, `Reflect.set throws ${e}`);
+ }
+ is(setCallbackCount, 0, "setCallback should not be called");
+ is(deleteCallbackCount, 0, "deleteCallback should be called");
+ is(b[property], value, "property value");
+ is(b.length, oldLen, "length of observable array");
+ });
+});
+
+add_task(function testObservableArrayExoticObjects_invalidtype() {
+ let m = new TestInterfaceObservableArray();
+ let i = m.observableArrayInterface;
+ ok(Array.isArray(i), "Observable array should be an array type");
+ is(i.length, 0, "length should be 0");
+
+ [true, "invalid"].forEach(function(value) {
+ SimpleTest.doesThrow(() => {
+ let descriptor = {configurable: true, enumerable: true, writable: true, value};
+ Reflect.defineProperty(i, i.length, descriptor);
+ }, `defining ${i.length} property with ${JSON.stringify(value)} should throw`);
+
+ SimpleTest.doesThrow(() => {
+ Reflect.set(i, i.length, value);
+ }, `setting ${i.length} property to ${JSON.stringify(value)} should throw`);
+ });
+
+ is(i.length, 0, "length should still be 0");
+});
+</script>
+</body>
+</html>
diff --git a/dom/bindings/test/test_oom_reporting.html b/dom/bindings/test/test_oom_reporting.html
new file mode 100644
index 0000000000..3a9d734ba6
--- /dev/null
+++ b/dom/bindings/test/test_oom_reporting.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug **/
+ SimpleTest.waitForExplicitFinish();
+
+ SimpleTest.expectUncaughtException();
+ setTimeout(function() {
+ SpecialPowers.Cu.getJSTestingFunctions().throwOutOfMemory();
+ }, 0);
+
+ addEventListener("error", function(e) {
+ is(e.type, "error", "Should have an error event");
+ is(e.message, "uncaught exception: out of memory",
+ "Should have the right error message");
+ // Make sure we finish async, in case the expectUncaughtException assertion
+ // about having seen the exception runs after our listener
+ SimpleTest.executeSoon(SimpleTest.finish);
+ });
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_prefOnConstructor.html b/dom/bindings/test/test_prefOnConstructor.html
new file mode 100644
index 0000000000..0d8213476e
--- /dev/null
+++ b/dom/bindings/test/test_prefOnConstructor.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1604340
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1604340</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ /* global WrapperCachedNonISupportsTestInterface */
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]}, go);
+
+ async function go() {
+ await SpecialPowers.pushPrefEnv({set: [["dom.webidl.test1", false]]});
+ let result = null;
+ let constructorThrew = false;
+
+ try {
+ result = new WrapperCachedNonISupportsTestInterface();
+ } catch {
+ constructorThrew = true;
+ }
+
+ is(result, null, "The result value should remain null if the constructor threw an exception as intended.");
+ ok(constructorThrew, "The constructor should throw an exception if its pref is not set to true.");
+
+ await SpecialPowers.pushPrefEnv({set: [["dom.webidl.test1", true]]});
+ result = null;
+ constructorThrew = false;
+
+ try {
+ result = new WrapperCachedNonISupportsTestInterface();
+ } catch {
+ constructorThrew = true;
+ }
+
+ isnot(result, null, "Constructor should have executed successfully.");
+ ok(!constructorThrew, "The constructor should not throw an exception if its pref is set.");
+
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1604340">Mozilla Bug 1604340</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_primitive_this.html b/dom/bindings/test/test_primitive_this.html
new file mode 100644
index 0000000000..086ce0f3f2
--- /dev/null
+++ b/dom/bindings/test/test_primitive_this.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=603201
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 603201</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 603201 **/
+
+ SimpleTest.waitForExplicitFinish();
+ function runTest() {
+ var nodes = document.body.childNodes;
+
+ Object.setPrototypeOf(Number.prototype, nodes);
+
+ Object.defineProperty(nodes, "getter", {get() {
+ "use strict";
+ is(this, 1);
+ return "getter";
+ }});
+ Object.defineProperty(Object.getPrototypeOf(nodes), "getter2", {get() {
+ "use strict";
+ is(this, 1);
+ return "getter2";
+ }});
+
+ var number = 1;
+ is(number.getter, "getter");
+ is(number.getter2, "getter2");
+
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body onload="runTest();">
+<pre>Test</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_promise_rejections_from_jsimplemented.html b/dom/bindings/test/test_promise_rejections_from_jsimplemented.html
new file mode 100644
index 0000000000..ab7e15dc57
--- /dev/null
+++ b/dom/bindings/test/test_promise_rejections_from_jsimplemented.html
@@ -0,0 +1,143 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1107592
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1107592</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ /* global TestInterfaceJS, thereIsNoSuchContentFunction1, thereIsNoSuchContentFunction2, thereIsNoSuchContentFunction3 */
+ /** Test for Bug 1107592 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function checkExn(lineNumber, name, message, code, filename, testNumber, stack, exn) {
+ is(exn.lineNumber, lineNumber,
+ "Should have the right line number in test " + testNumber);
+ is(exn.name, name,
+ "Should have the right exception name in test " + testNumber);
+ is("filename" in exn ? exn.filename : exn.fileName, filename,
+ "Should have the right file name in test " + testNumber);
+ is(exn.message, message,
+ "Should have the right message in test " + testNumber);
+ is(exn.code, code, "Should have the right .code in test " + testNumber);
+ if (message === "") {
+ is(exn.name, "InternalError",
+ "Should have one of our synthetic exceptions in test " + testNumber);
+ }
+ is(exn.stack, stack, "Should have the right stack in test " + testNumber);
+ }
+
+ function ensurePromiseFail(testNumber, value) {
+ ok(false, "Test " + testNumber + " should not have a fulfilled promise");
+ }
+
+ function doTest() {
+ var t = new TestInterfaceJS();
+
+
+ var asyncStack = !SpecialPowers.getBoolPref("javascript.options.asyncstack_capture_debuggee_only");
+ var ourFile = location.href;
+ var unwrapError = "Promise rejection value is a non-unwrappable cross-compartment wrapper.";
+ var parentFrame = asyncStack ? `Async*@${ourFile}:130:17
+` : "";
+
+ Promise.all([
+ t.testPromiseWithThrowingChromePromiseInit().then(
+ ensurePromiseFail.bind(null, 1),
+ checkExn.bind(null, 49, "InternalError", unwrapError,
+ undefined, ourFile, 1,
+ `doTest@${ourFile}:49:9
+` +
+ parentFrame)),
+ t.testPromiseWithThrowingContentPromiseInit(function() {
+ thereIsNoSuchContentFunction1();
+ }).then(
+ ensurePromiseFail.bind(null, 2),
+ checkExn.bind(null, 57, "ReferenceError",
+ "thereIsNoSuchContentFunction1 is not defined",
+ undefined, ourFile, 2,
+ `doTest/<@${ourFile}:57:11
+doTest@${ourFile}:56:9
+` +
+ parentFrame)),
+ t.testPromiseWithThrowingChromeThenFunction().then(
+ ensurePromiseFail.bind(null, 3),
+ checkExn.bind(null, 0, "InternalError", unwrapError, undefined, "", 3, asyncStack ? (`Async*doTest@${ourFile}:67:9
+` +
+ parentFrame) : "")),
+ t.testPromiseWithThrowingContentThenFunction(function() {
+ thereIsNoSuchContentFunction2();
+ }).then(
+ ensurePromiseFail.bind(null, 4),
+ checkExn.bind(null, 73, "ReferenceError",
+ "thereIsNoSuchContentFunction2 is not defined",
+ undefined, ourFile, 4,
+ `doTest/<@${ourFile}:73:11
+` +
+ (asyncStack ? `Async*doTest@${ourFile}:72:9
+` : "") +
+ parentFrame)),
+ t.testPromiseWithThrowingChromeThenable().then(
+ ensurePromiseFail.bind(null, 5),
+ checkExn.bind(null, 0, "InternalError", unwrapError, undefined, "", 5, asyncStack ? (`Async*doTest@${ourFile}:84:9
+` +
+ parentFrame) : "")),
+ t.testPromiseWithThrowingContentThenable({
+ then() { thereIsNoSuchContentFunction3(); },
+ }).then(
+ ensurePromiseFail.bind(null, 6),
+ checkExn.bind(null, 90, "ReferenceError",
+ "thereIsNoSuchContentFunction3 is not defined",
+ undefined, ourFile, 6,
+ `then@${ourFile}:90:22
+` + (asyncStack ? `Async*doTest@${ourFile}:89:9\n` + parentFrame : ""))),
+ t.testPromiseWithDOMExceptionThrowingPromiseInit().then(
+ ensurePromiseFail.bind(null, 7),
+ checkExn.bind(null, 98, "NotFoundError",
+ "We are a second DOMException",
+ DOMException.NOT_FOUND_ERR, ourFile, 7,
+ `doTest@${ourFile}:98:9
+` +
+ parentFrame)),
+ t.testPromiseWithDOMExceptionThrowingThenFunction().then(
+ ensurePromiseFail.bind(null, 8),
+ checkExn.bind(null, asyncStack ? 106 : 0, "NetworkError",
+ "We are a third DOMException",
+ DOMException.NETWORK_ERR, asyncStack ? ourFile : "", 8,
+ (asyncStack ? `Async*doTest@${ourFile}:106:9
+` +
+ parentFrame : ""))),
+ t.testPromiseWithDOMExceptionThrowingThenable().then(
+ ensurePromiseFail.bind(null, 9),
+ checkExn.bind(null, asyncStack ? 114 : 0, "TypeMismatchError",
+ "We are a fourth DOMException",
+ DOMException.TYPE_MISMATCH_ERR,
+ asyncStack ? ourFile : "", 9,
+ (asyncStack ? `Async*doTest@${ourFile}:114:9
+` +
+ parentFrame : ""))),
+ ]).then(SimpleTest.finish,
+ function(err) {
+ ok(false, "One of our catch statements totally failed with err" + err + ", stack: " + (err ? err.stack : ""));
+ SimpleTest.finish();
+ });
+ }
+
+ SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]},
+ doTest);
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1107592">Mozilla Bug 1107592</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_proxies_via_xray.html b/dom/bindings/test/test_proxies_via_xray.html
new file mode 100644
index 0000000000..d7d5dc2d1e
--- /dev/null
+++ b/dom/bindings/test/test_proxies_via_xray.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1021066
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1021066</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1021066">Mozilla Bug 1021066</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id="t" src="http://example.org/tests/dom/bindings/test/file_proxies_via_xray.html"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1021066 **/
+
+function test() {
+ "use strict"; // So we'll get exceptions on sets
+ var doc = document.getElementById("t").contentWindow.document;
+ ok(!("x" in doc), "Should have an Xray here");
+ is(doc.x, undefined, "Really should have an Xray here");
+ is(doc.wrappedJSObject.x, 5, "And wrapping the right thing");
+
+ // Test overridebuiltins binding without named setter
+ is(doc.y, doc.getElementById("y"),
+ "Named getter should work on Document");
+ try {
+ doc.z = 5;
+ ok(false, "Should have thrown on set of readonly property on Document");
+ } catch (e) {
+ ok(/read-only/.test(e.message),
+ "Threw the right exception on set of readonly property on Document");
+ }
+
+ doc.w = 5;
+ is(doc.w, 5, "Should be able to set things that are not named props");
+
+ // Test non-overridebuiltins binding without named setter
+ var l = doc.getElementsByTagName("img");
+ is(l.y, doc.getElementById("y"),
+ "Named getter should work on HTMLCollection");
+ try {
+ l.z = 5;
+ ok(false, "Should have thrown on set of readonly property on HTMLCollection");
+ } catch (e) {
+ ok(/read-only/.test(e.message),
+ "Should throw the right exception on set of readonly property on HTMLCollection");
+ }
+ try {
+ l[10] = 5;
+ ok(false, "Should have thrown on set of indexed property on HTMLCollection");
+ } catch (e) {
+ ok(/doesn't have an indexed property setter/.test(e.message),
+ "Should throw the right exception on set of indexed property on HTMLCollection");
+ }
+
+ // Test overridebuiltins binding with named setter
+ var d = doc.documentElement.dataset;
+ d.foo = "bar";
+ // Check that this actually got passed on to the underlying object.
+ is(d.wrappedJSObject.foo, "bar",
+ "Set should get forwarded to the underlying object");
+ is(doc.documentElement.getAttribute("data-foo"), "bar",
+ "Attribute setter should have been called");
+ d.foo = "baz";
+ // Check that this actually got passed on to the underlying object.
+ is(d.wrappedJSObject.foo, "baz",
+ "Set should get forwarded to the underlying object again");
+ is(doc.documentElement.getAttribute("data-foo"), "baz",
+ "Attribute setter should have been called again");
+
+ // Test non-overridebuiltins binding with named setter
+ var s = doc.defaultView.localStorage;
+ s.test_proxies_via_xray = "bar";
+ // Check that this actually got passed on to the underlying object.
+ is(s.wrappedJSObject.test_proxies_via_xray, "bar",
+ "Set should get forwarded to the underlying object without overridebuiltins");
+ s.test_proxies_via_xray = "baz";
+ // Check that this actually got passed on to the underlying object.
+ is(s.wrappedJSObject.test_proxies_via_xray, "baz",
+ "Set should get forwarded to the underlying object again without overridebuiltins");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_proxy_accessors.html b/dom/bindings/test/test_proxy_accessors.html
new file mode 100644
index 0000000000..9474369688
--- /dev/null
+++ b/dom/bindings/test/test_proxy_accessors.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1700052
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1700052</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1700052">Mozilla Bug 1700052</a>
+<p id="display"></p>
+<form id="theform"></form>
+<pre id="test">
+<script>
+function expandoTests() {
+ // Get a DOM proxy with an expando object. Define/redefine a "foo" getter/setter
+ // and ensure the right function is called.
+
+ var obj = document.getElementById("theform");
+ var count1 = 0, count2 = 0, count3 = 0;
+ var fun1 = function() { count1++; };
+ var fun2 = function() { count2++; };
+ var fun3 = function() { count3++; };
+
+ Object.defineProperty(obj, "foo", {configurable: true, get: fun1, set: fun1});
+
+ for (var i = 0; i < 100; i++) {
+ obj.foo;
+ obj.foo = i;
+ if (i === 50) {
+ Object.defineProperty(obj, "foo", {configurable: true, get: fun2, set: fun2});
+ } else if (i === 80) {
+ Object.defineProperty(obj, "foo", {configurable: true, get: fun3, set: fun3});
+ }
+ }
+
+ is(count1, 102, "call count for fun1 must match");
+ is(count2, 60, "call count for fun2 must match");
+ is(count3, 38, "call count for fun3 must match");
+}
+expandoTests();
+
+function unshadowedTests() {
+ // Same as above, but for non-shadowing properties on the prototype.
+
+ var obj = document.getElementById("theform");
+ var proto = Object.getPrototypeOf(obj);
+
+ var count1 = 0, count2 = 0;
+ var fun1 = function() { count1++; };
+ var fun2 = function() { count2++; };
+
+ for (var i = 0; i < 100; i++) {
+ obj.name;
+ obj.name = "test";
+ if (i === 50) {
+ let desc = Object.getOwnPropertyDescriptor(proto, "name");
+ desc.get = desc.set = fun1;
+ Object.defineProperty(proto, "name", desc);
+ }
+ if (i === 90) {
+ let desc = Object.getOwnPropertyDescriptor(proto, "name");
+ desc.get = desc.set = fun2;
+ Object.defineProperty(proto, "name", desc);
+ }
+ }
+
+ is(count1, 80, "call count for fun1 must match");
+ is(count2, 18, "call count for fun2 must match");
+}
+unshadowedTests();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_proxy_expandos.html b/dom/bindings/test/test_proxy_expandos.html
new file mode 100644
index 0000000000..bfe6ab598c
--- /dev/null
+++ b/dom/bindings/test/test_proxy_expandos.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=965992
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 965992</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=965992">Mozilla Bug 965992</a>
+<p id="display"></p>
+<form id="theform"></form>
+<pre id="test">
+<script type="application/javascript">
+
+// Ensure we are in JIT code and attach IC stubs.
+const iterations = 50;
+
+function testFoo(obj, kind, expected) {
+ for (var i = 0; i < iterations; i++) {
+ obj.foo = i;
+ is(obj.foo, (expected === undefined) ? i : expected,
+ "Looking up an expando should work - " + kind);
+ }
+}
+
+function getPropTests(obj) {
+ // Start with a plain data property.
+ obj.foo = "bar";
+ testFoo(obj, "plain");
+
+ // Now change it to a scripted getter/setter.
+ var count = 0;
+ var getterSetterVal = 0;
+ Object.defineProperty(obj, "foo", {configurable: true, get() {
+ is(this, obj, "Getter should have the proxy as |this|");
+ is(arguments.length, 0, "Shouldn't pass arguments to getters");
+ count++;
+ return getterSetterVal;
+ }, set(v) {
+ is(this, obj, "Setter should have the proxy as |this|");
+ is(arguments.length, 1, "Should pass 1 argument to setters");
+ getterSetterVal = v;
+ count++;
+ }});
+ testFoo(obj, "scripted getter/setter");
+ is(count, iterations * 2, "Should have called the getter/setter enough times");
+
+ // Now try a native getter/setter.
+ Object.defineProperty(obj, "foo", {get: Math.abs, set: Math.abs, configurable: true});
+ testFoo(obj, "native getter/setter", NaN);
+}
+
+function getElemTests(obj) {
+ // Define two expando properties, then test inline caches for obj[prop]
+ // correctly guard on prop being the same.
+ var count = 0, getterSetterVal = 0;
+ obj.elem1 = 1;
+ Object.defineProperty(obj, "elem2", {
+ get() { count++; return getterSetterVal; },
+ set(v) { getterSetterVal = v; count++; },
+ });
+ for (var i = 0; i < iterations; i++) {
+ var prop = ((i & 1) == 0) ? "elem1" : "elem2";
+ obj[prop] = i;
+ is(obj[prop], i, "Should return correct property value");
+ }
+ is(count, iterations, "Should have called the getter/setter enough times");
+}
+
+var directExpando = document.getElementsByTagName("*");
+var indirectExpando = document.getElementById("theform");
+
+getPropTests(directExpando);
+getPropTests(indirectExpando);
+
+getElemTests(indirectExpando);
+getElemTests(directExpando);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_remoteProxyAsPrototype.html b/dom/bindings/test/test_remoteProxyAsPrototype.html
new file mode 100644
index 0000000000..3c5216a443
--- /dev/null
+++ b/dom/bindings/test/test_remoteProxyAsPrototype.html
@@ -0,0 +1,33 @@
+<!-- Any copyright is dedicated to the Public Domain.
+- http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test for bug 1773732</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script>
+
+SimpleTest.waitForExplicitFinish();
+
+function go() {
+ let frame = document.createElement("iframe");
+ frame.onload = () => {
+ let win = frame.contentWindow;
+ is(SpecialPowers.Cu.isRemoteProxy(win), SpecialPowers.useRemoteSubframes,
+ "win is a remote proxy if Fission is enabled");
+ let o = {};
+ Object.setPrototypeOf(o, win);
+ is(Object.getPrototypeOf(o), win, "should have expected proto");
+ SimpleTest.finish();
+ };
+ frame.src = "https://example.com";
+ document.body.appendChild(frame);
+};
+go();
+
+</script>
+</body>
+</html>
diff --git a/dom/bindings/test/test_returnUnion.html b/dom/bindings/test/test_returnUnion.html
new file mode 100644
index 0000000000..eca681068a
--- /dev/null
+++ b/dom/bindings/test/test_returnUnion.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1048659
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1048659</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ /* global TestInterfaceJS */
+ /** Test for returning unions from JS-implemented WebIDL. **/
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]}, go);
+
+ function go() {
+ var t = new TestInterfaceJS();
+ var t2 = new TestInterfaceJS();
+
+ is(t.pingPongUnion(t2), t2, "ping pong union for left case should be identity");
+ is(t.pingPongUnion(12), 12, "ping pong union for right case should be identity");
+
+ is(t.pingPongUnionContainingNull("this is not a string"), "this is not a string",
+ "ping pong union containing union for left case should be identity");
+ is(t.pingPongUnionContainingNull(null), null,
+ "ping pong union containing null for right case null should be identity");
+ is(t.pingPongUnionContainingNull(t2), t2,
+ "ping pong union containing null for right case should be identity");
+
+ is(t.pingPongNullableUnion(t2), t2, "ping pong nullable union for left case should be identity");
+ is(t.pingPongNullableUnion(12), 12, "ping pong nullable union for right case should be identity");
+ is(t.pingPongNullableUnion(null), null, "ping pong nullable union for null case should be identity");
+
+ var rejectedBadUnion = false;
+ var result = null;
+ try {
+ result = t.returnBadUnion();
+ } catch (e) {
+ rejectedBadUnion = true;
+ }
+ is(result, null, "bad union should not set a value for result");
+ ok(rejectedBadUnion, "bad union should throw an exception");
+
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1048659">Mozilla Bug 1048659</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_sequence_detection.html b/dom/bindings/test/test_sequence_detection.html
new file mode 100644
index 0000000000..714d9a5cb1
--- /dev/null
+++ b/dom/bindings/test/test_sequence_detection.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1066432
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1066432</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ /* global TestInterfaceJS */
+ /** Test for Bug 1066432 **/
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]}, function() {
+ var testInterfaceJS = new TestInterfaceJS();
+ ok(testInterfaceJS, "got a TestInterfaceJS object");
+
+ var nonIterableObject = {[Symbol.iterator]: 5};
+
+ try {
+ testInterfaceJS.testSequenceOverload(nonIterableObject);
+ ok(false, "Should have thrown in the overload case"); // see long comment above!
+ } catch (e) {
+ is(e.name, "TypeError", "Should get a TypeError for the overload case");
+ ok(e.message.includes("not iterable"),
+ "Should have a message about being non-iterable in the overload case");
+ }
+
+ try {
+ testInterfaceJS.testSequenceUnion(nonIterableObject);
+ ok(false, "Should have thrown in the union case");
+ } catch (e) {
+ is(e.name, "TypeError", "Should get a TypeError for the union case");
+ ok(e.message.includes("not iterable"),
+ "Should have a message about being non-iterable in the union case");
+ }
+
+ SimpleTest.finish();
+ });
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1066432">Mozilla Bug 1066432</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_sequence_wrapping.html b/dom/bindings/test/test_sequence_wrapping.html
new file mode 100644
index 0000000000..bac95e01d3
--- /dev/null
+++ b/dom/bindings/test/test_sequence_wrapping.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=775852
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 775852</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=775852">Mozilla Bug 775852</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <canvas width="1" height="1" id="c"></canvas>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 775852 **/
+function doTest() {
+ var gl = $("c").getContext("experimental-webgl");
+ if (!gl) {
+ // No WebGL support on MacOS 10.5. Just skip this test
+ todo(false, "WebGL not supported");
+ return;
+ }
+ var setterCalled = false;
+
+ var extLength = gl.getSupportedExtensions().length;
+ ok(extLength > 0,
+ "This test won't work right if we have no supported extensions");
+
+ Object.defineProperty(Array.prototype, "0",
+ {
+ set(val) {
+ setterCalled = true;
+ },
+ });
+
+ // Test that our property got defined correctly
+ var arr = [];
+ arr[0] = 5;
+ is(setterCalled, true, "Setter should be called when setting prop on array");
+
+ setterCalled = false;
+
+ is(gl.getSupportedExtensions().length, extLength,
+ "We should still have the same number of extensions");
+
+ is(setterCalled, false,
+ "Setter should not be called when getting supported extensions");
+}
+doTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_setWithNamedGetterNoNamedSetter.html b/dom/bindings/test/test_setWithNamedGetterNoNamedSetter.html
new file mode 100644
index 0000000000..ec5b048987
--- /dev/null
+++ b/dom/bindings/test/test_setWithNamedGetterNoNamedSetter.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1043690
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1043690</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1043690">Mozilla Bug 1043690</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<form>
+ <input name="action">
+</form>
+</div>
+ <script type="application/javascript">
+
+ /** Test for Bug 1043690 **/
+ var f = document.querySelector("form");
+ var i = document.querySelector("input");
+ is(f.getAttribute("action"), null, "Should have no action attribute");
+ is(f.action, i, "form.action should be the input");
+ f.action = "http://example.org";
+ is(f.getAttribute("action"), "http://example.org",
+ "Should have an action attribute now");
+ is(f.action, i, "form.action should still be the input");
+ i.remove();
+ is(f.action, "http://example.org/",
+ "form.action should no longer be shadowed");
+
+
+ </script>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_stringBindings.html b/dom/bindings/test/test_stringBindings.html
new file mode 100644
index 0000000000..ad8b60df07
--- /dev/null
+++ b/dom/bindings/test/test_stringBindings.html
@@ -0,0 +1,109 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1334537
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1334537</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1334537 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function go() {
+ // Need a new global that will pick up our pref.
+ var ifr = document.createElement("iframe");
+ document.body.appendChild(ifr);
+
+ var t = new ifr.contentWindow.TestFunctions();
+ var testString = "abcdefghijklmnopqrstuvwxyz";
+ const substringLength = 10;
+ var shortTestString = testString.substring(0, substringLength);
+
+ t.setStringData(testString);
+ // Note: we want to do all our gets before we start running code we don't
+ // control inside the test harness, if we really want to exercise the string
+ // cache in controlled ways.
+
+ var asShortDOMString = t.getStringDataAsDOMString(substringLength);
+ var asFullDOMString = t.getStringDataAsDOMString();
+ var asShortAString = t.getStringDataAsAString(substringLength);
+ var asAString = t.getStringDataAsAString();
+
+ is(asShortDOMString, shortTestString, "Short DOMString should be short");
+ is(asFullDOMString, testString, "Full DOMString should be test string");
+ is(asShortAString, shortTestString, "Short AString should be short");
+ is(asAString, testString, "Full AString should be test string");
+
+ // These strings should match the strings used in TestFunctions.cpp, but we
+ // want to launder them through something on the JS side that will convert
+ // them into strings stored in the JS heap.
+ function launder(str) {
+ // This should be sufficient, but if the JIT gets too smart we may need
+ // to get more clever.
+ return str.split("").join("");
+ }
+ var shortString = launder(t.getShortLiteralString());
+ var mediumString = launder(t.getMediumLiteralString());
+ var longString = launder(t.getLongLiteralString());
+
+ // A short or medium non-external string will become an inline FakeString.
+ is(t.getStringType(shortString), "inline",
+ "Short string should become inline");
+ is(t.getStringType(mediumString), "inline",
+ "Medium string should become inline");
+ // A long string will become a stringbuffer FakeString on the way in.
+ is(t.getStringType(longString), "stringbuffer",
+ "Long string should become a stringbuffer");
+
+ // A short literal string will become an inline JS string on the way out
+ // and then become an inline FakeString on the way in.
+ is(t.getStringType(t.getShortLiteralString()), "inline",
+ "Short literal string should become inline");
+ // A medium or long literal string will become an external JS string on the
+ // way out and then become a literal FakeString on the way in.
+ is(t.getStringType(t.getMediumLiteralString()), "literal",
+ "Medium literal string should become literal");
+ is(t.getStringType(t.getLongLiteralString()), "literal",
+ "Long literal string should become literal");
+
+ // A short stringbuffer string will become an inline JS string on the way
+ // out and then become an inline FakeString on the way in.
+ is(t.getStringType(t.getStringbufferString(shortString)), "inline",
+ "Short stringbuffer string should become inline");
+ // A medium or long stringbuffer string will become an external JS string
+ // on the way out and then become a stringbuffer FakeString on the way in.
+ is(t.getStringType(t.getStringbufferString(mediumString)), "stringbuffer",
+ "Medium stringbuffer string should become stringbuffer");
+ is(t.getStringType(t.getStringbufferString(longString)), "stringbuffer",
+ "Long stringbuffer string should become stringbuffer");
+
+ // Now test that roundtripping works fine. We need to make sure the string
+ // we are storing is not equal to any of the ones we have used above, to
+ // avoid the external string cache interfering.
+ t.setStringData(longString + "unique"); // Should store with stringbuffer.
+ ok(t.stringbufferMatchesStored(t.getStringDataAsAString()),
+ "Stringbuffer should have roundtripped");
+
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]},
+ go);
+ });
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1334537">Mozilla Bug 1334537</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_throwing_method_noDCE.html b/dom/bindings/test/test_throwing_method_noDCE.html
new file mode 100644
index 0000000000..92d1a0b7f9
--- /dev/null
+++ b/dom/bindings/test/test_throwing_method_noDCE.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test that we don't DCE functions that can throw</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+/* global test, assert_true */
+test(function() {
+ function test(root) {
+ var threw = false;
+ try {
+ root.querySelectorAll("");
+ } catch (e) { threw = true; }
+ // Hot loop to make sure the JIT heuristics ion-compile this function even
+ // though it's throwing exceptions (which would normally make us back off
+ // of ion compilation).
+ for (var i = 0; i < 1500; i++) {
+ // empty
+ }
+ return threw;
+ }
+
+ var threw = false;
+ var el = document.createElement("div");
+ for (var i = 0; i < 200; i++)
+ threw = test(el);
+ assert_true(threw);
+}, "Shouldn't optimize away throwing functions");
+</script>
diff --git a/dom/bindings/test/test_toJSON.html b/dom/bindings/test/test_toJSON.html
new file mode 100644
index 0000000000..9bd9a5989a
--- /dev/null
+++ b/dom/bindings/test/test_toJSON.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1465602
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1465602</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1465602">Mozilla Bug 1465602</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe></iframe>
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+ /* global TestFunctions */
+ /** Test for Bug 1465602 **/
+
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]}, go);
+
+ function go() {
+ var ourObj = new TestFunctions();
+ is(ourObj.one, 1, "Basic sanity check for our 'one'");
+ is(ourObj.two, undefined, "Basic sanity check for our 'two'");
+
+ var otherObj = new frames[0].TestFunctions();
+ is(otherObj.one, 1, "Basic sanity check for subframe 'one'");
+ is(otherObj.two, 2, "Basic sanity check for subframe 'two'");
+
+ var ourToJSON = ourObj.toJSON();
+ is(ourToJSON.one, 1, "We should have correct value for 'one'");
+ is(ourToJSON.two, undefined, "We should have correct value for 'two'");
+ ok(!Object.hasOwnProperty(ourToJSON, "two"),
+ "We should not have a property named 'two'");
+
+ var otherToJSON = otherObj.toJSON();
+ is(otherToJSON.one, 1, "Subframe should have correct value for 'one'");
+ is(otherToJSON.two, 2, "Subframe should have correct value for 'two'");
+
+ var mixedToJSON = ourObj.toJSON.call(otherObj);
+ is(mixedToJSON.one, 1, "First test should have correct value for 'one'");
+ is(mixedToJSON.two, 2, "First test should have correct value for 'two'");
+
+ mixedToJSON = otherObj.toJSON.call(ourObj);
+ is(mixedToJSON.one, 1, "Second test should have correct value for 'one'");
+ is(mixedToJSON.two, undefined,
+ "Second test should have correct value for 'two'");
+
+ SimpleTest.finish();
+ }
+ </script>
+</body>
+</html>
diff --git a/dom/bindings/test/test_traceProtos.html b/dom/bindings/test/test_traceProtos.html
new file mode 100644
index 0000000000..b649b6ec8c
--- /dev/null
+++ b/dom/bindings/test/test_traceProtos.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=744772
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 744772</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=744772">Mozilla Bug 744772</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 744772 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function callback() {
+ new XMLHttpRequest().upload;
+ ok(true, "Accessing unreferenced DOM interface objects shouldn't crash");
+ SimpleTest.finish();
+}
+
+delete window.XMLHttpRequestUpload;
+SpecialPowers.exactGC(callback);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_treat_non_object_as_null.html b/dom/bindings/test/test_treat_non_object_as_null.html
new file mode 100644
index 0000000000..785a2ebc71
--- /dev/null
+++ b/dom/bindings/test/test_treat_non_object_as_null.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=952365
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 952365</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 952365 **/
+
+ var onvolumechange;
+ var x = {};
+
+ (function() {
+ onvolumechange = x;
+ is(onvolumechange, x,
+ "Should preserve an object value when assigning to event handler");
+ // Test that we don't try to actually call the non-callable object
+ window.dispatchEvent(new Event("volumechange"));
+ onvolumechange = 5;
+ is(onvolumechange, null,
+ "Non-object values should become null when assigning to event handler");
+ })();
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=952365">Mozilla Bug 952365</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/bindings/test/test_unforgeablesonexpando.html b/dom/bindings/test/test_unforgeablesonexpando.html
new file mode 100644
index 0000000000..3db4350c9a
--- /dev/null
+++ b/dom/bindings/test/test_unforgeablesonexpando.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for making sure named getters don't override the unforgeable location on HTMLDocument</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<img name="location">
+<script>
+/* global test, assert_equals */
+test(function() {
+ assert_equals(document.location, window.location,
+ 'The <img name="location"> should not override the location getter');
+}, "document.location is the right thing");
+test(function() {
+ var doc = new DOMParser().parseFromString("<img name='location'>", "text/html");
+ assert_equals(doc.location, null,
+ 'The <img name="location"> should not override the location getter on a data document');
+}, "document.location is the right thing on non-rendered document");
+</script>
diff --git a/dom/bindings/test/test_usvstring.html b/dom/bindings/test/test_usvstring.html
new file mode 100644
index 0000000000..bef4d5e1fa
--- /dev/null
+++ b/dom/bindings/test/test_usvstring.html
@@ -0,0 +1,43 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test USVString</title>
+ <script 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">
+/* global TestInterfaceJS */
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]}, function() {
+ var testInterfaceJS = new TestInterfaceJS();
+ ok(testInterfaceJS, "got a TestInterfaceJS object");
+ // For expected values, see algorithm definition here:
+ // http://heycam.github.io/webidl/#dfn-obtain-unicode
+ var testList = [
+ { string: "foo",
+ expected: "foo" },
+ { string: "This is U+2070E: \ud841\udf0e",
+ expected: "This is U+2070E: \ud841\udf0e" },
+ { string: "Missing low surrogate: \ud841",
+ expected: "Missing low surrogate: \ufffd" },
+ { string: "Missing low surrogate with trailer: \ud841!!",
+ expected: "Missing low surrogate with trailer: \ufffd!!" },
+ { string: "Missing high surrogate: \udf0e",
+ expected: "Missing high surrogate: \ufffd" },
+ { string: "Missing high surrogate with trailer: \udf0e!!",
+ expected: "Missing high surrogate with trailer: \ufffd!!" },
+ { string: "U+2070E after malformed: \udf0e\ud841\udf0e",
+ expected: "U+2070E after malformed: \ufffd\ud841\udf0e" },
+ ];
+ testList.forEach(function(test) {
+ is(testInterfaceJS.convertSVS(test.string), test.expected, "Convert '" + test.string + "'");
+ });
+ SimpleTest.finish();
+});
+</script>
+</body>
+</html>
diff --git a/dom/bindings/test/test_worker_UnwrapArg.html b/dom/bindings/test/test_worker_UnwrapArg.html
new file mode 100644
index 0000000000..8bc23fa630
--- /dev/null
+++ b/dom/bindings/test/test_worker_UnwrapArg.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1127206
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1127206</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1127206 **/
+ SimpleTest.waitForExplicitFinish();
+ var blob = new Blob([
+ `try { new File({}); }
+ catch (e) {
+ postMessage("throwing on random object");
+ }
+ try { new File(new Blob(["abc"])); }
+ catch (e) {
+ postMessage("throwing on Blob");
+ }
+ try { new File("abc"); }
+ catch (e) {
+ postMessage("throwing on string");
+ }
+ postMessage('finishTest')`]);
+ var url = URL.createObjectURL(blob);
+ var w = new Worker(url);
+ var expectedResults = [
+ "throwing on random object",
+ "throwing on Blob",
+ "throwing on string",
+ ];
+ var curIndex = 0;
+ w.onmessage = function(e) {
+ if (curIndex == expectedResults.length) {
+ is(e.data, "finishTest", "What message is this?");
+ SimpleTest.finish();
+ } else {
+ is(e.data, expectedResults[curIndex],
+ "Message " + (curIndex + 1) + " should be correct");
+ ++curIndex;
+ }
+ };
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1127206">Mozilla Bug 1127206</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>