diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /dom/bindings | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
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> |