diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/bindings | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/bindings')
245 files changed, 59965 insertions, 0 deletions
diff --git a/dom/bindings/AtomList.h b/dom/bindings/AtomList.h new file mode 100644 index 000000000..53765e101 --- /dev/null +++ b/dom/bindings/AtomList.h @@ -0,0 +1,27 @@ +/* -*- 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 "mozilla/dom/GeneratedAtomList.h" + +namespace mozilla { +namespace dom { + +template<class T> +T* GetAtomCache(JSContext* aCx) +{ + auto atomCache = static_cast<PerThreadAtomCache*>(JS_GetContextPrivate(aCx)); + + return static_cast<T*>(atomCache); +} + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_AtomList_h diff --git a/dom/bindings/BindingDeclarations.h b/dom/bindings/BindingDeclarations.h new file mode 100644 index 000000000..c712511ab --- /dev/null +++ b/dom/bindings/BindingDeclarations.h @@ -0,0 +1,535 @@ +/* -*- 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/Value.h" + +#include "mozilla/Maybe.h" +#include "mozilla/RootedOwningNonNull.h" +#include "mozilla/RootedRefPtr.h" + +#include "mozilla/dom/DOMString.h" + +#include "nsCOMPtr.h" +#include "nsStringGlue.h" +#include "nsTArray.h" + +class nsIPrincipal; +class nsWrapperCache; + +namespace mozilla { +namespace dom { + +// Struct that serves as a base class for all dictionaries. Particularly useful +// so we can use IsBaseOf 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; + } +}; + +// Struct that serves as a base class for all typed arrays and array buffers and +// array buffer views. Particularly useful so we can use IsBaseOf 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 IsBaseOf to detect owning union +// template arguments. +struct AllOwningUnionBase { +}; + + +struct EnumEntry { + const char* value; + size_t length; +}; + +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; + +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() + {} + + explicit Optional_base(const T& aValue) + { + mImpl.emplace(aValue); + } + + 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(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: + Optional() : + Optional_base<T, T>() + {} + + explicit Optional(const T& aValue) : + Optional_base<T, T>(aValue) + {} +}; + +template<typename T> +class Optional<JS::Handle<T> > : + public Optional_base<JS::Handle<T>, JS::Rooted<T> > +{ +public: + 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() : + Optional_base<JSObject*, JSObject*>() + {} + + 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 { +struct FakeString; +} // namespace binding_detail + +template<> +class Optional<nsAString> +{ +public: + Optional() : mPassed(false) {} + + bool WasPassed() const + { + return mPassed; + } + + void operator=(const nsAString* str) + { + MOZ_ASSERT(str); + mStr = str; + mPassed = true; + } + + // 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* str) + { + MOZ_ASSERT(str); + mStr = reinterpret_cast<const nsString*>(str); + mPassed = true; + } + + const nsAString& 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; + + bool mPassed; + const nsAString* mStr; +}; + +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: + 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>() + {} +}; + +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()); +} + +struct MOZ_STACK_CLASS ParentObject { + template<class T> + MOZ_IMPLICIT ParentObject(T* aObject) : + mObject(aObject), + mWrapperCache(GetWrapperCache(aObject)), + mUseXBLScope(false) + {} + + template<class T, template<typename> class SmartPtr> + MOZ_IMPLICIT ParentObject(const SmartPtr<T>& aObject) : + mObject(aObject.get()), + mWrapperCache(GetWrapperCache(aObject.get())), + mUseXBLScope(false) + {} + + ParentObject(nsISupports* aObject, nsWrapperCache* aCache) : + mObject(aObject), + mWrapperCache(aCache), + mUseXBLScope(false) + {} + + // 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; + bool mUseXBLScope; +}; + +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 +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_BindingDeclarations_h__ diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp new file mode 100644 index 000000000..33f5f7a44 --- /dev/null +++ b/dom/bindings/BindingUtils.cpp @@ -0,0 +1,3549 @@ +/* -*- 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/FloatingPoint.h" +#include "mozilla/Preferences.h" +#include "mozilla/SizePrintfMacros.h" +#include "mozilla/Unused.h" +#include "mozilla/UseCounter.h" + +#include "AccessCheck.h" +#include "jsfriendapi.h" +#include "nsContentUtils.h" +#include "nsGlobalWindow.h" +#include "nsIDocShell.h" +#include "nsIDOMGlobalPropertyInitializer.h" +#include "nsIPermissionManager.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 "nsGlobalWindow.h" + +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/DOMError.h" +#include "mozilla/dom/DOMErrorBinding.h" +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/ElementBinding.h" +#include "mozilla/dom/HTMLObjectElement.h" +#include "mozilla/dom/HTMLObjectElementBinding.h" +#include "mozilla/dom/HTMLSharedObjectElement.h" +#include "mozilla/dom/HTMLEmbedElementBinding.h" +#include "mozilla/dom/HTMLAppletElementBinding.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/ResolveSystemBinding.h" +#include "mozilla/dom/WebIDLGlobalNameHash.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerScope.h" +#include "mozilla/dom/XrayExpandoClass.h" +#include "mozilla/jsipc/CrossProcessObjectWrappers.h" +#include "nsDOMClassInfo.h" +#include "ipc/ErrorIPCUtils.h" +#include "mozilla/UseCounter.h" + +namespace mozilla { +namespace dom { + +using namespace workers; + +const JSErrorFormatString ErrorFormatString[] = { +#define MSG_DEF(_name, _argc, _exn, _str) \ + { #_name, _str, _argc, _exn }, +#include "mozilla/dom/Errors.msg" +#undef MSG_DEF +}; + +#define MSG_DEF(_name, _argc, _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 + +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; +} + +void +binding_detail::ThrowErrorMessage(JSContext* aCx, const unsigned aErrorNumber, ...) +{ + va_list ap; + va_start(ap, aErrorNumber); + JS_ReportErrorNumberUTF8VA(aCx, GetErrorMessage, nullptr, aErrorNumber, ap); + va_end(ap); +} + +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; + } + const ErrNum errorNumber = aSecurityError ? + MSG_METHOD_THIS_UNWRAPPING_DENIED : + MSG_METHOD_THIS_DOES_NOT_IMPLEMENT_INTERFACE; + MOZ_RELEASE_ASSERT(GetErrorArgCount(errorNumber) <= 2); + JS_ReportErrorNumberUC(aCx, GetErrorMessage, nullptr, + static_cast<const unsigned>(errorNumber), + funcNameStr.get(), 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, prototypes::ID aProtoId) +{ + nsPrintfCString errorMessage("%s attribute setter", + NamesOfInterfacesWithProtos(aProtoId)); + return ThrowErrorMessage(aCx, MSG_MISSING_ARGUMENTS, errorMessage.get()); +} + +} // namespace dom + +namespace binding_danger { + +template<typename CleanupPolicy> +struct TErrorResult<CleanupPolicy>::Message { + Message() { MOZ_COUNT_CTOR(TErrorResult::Message); } + ~Message() { MOZ_COUNT_DTOR(TErrorResult::Message); } + + nsTArray<nsString> mArgs; + dom::ErrNum mErrorNumber; + + bool HasCorrectNumberOfArguments() + { + return GetErrorArgCount(mErrorNumber) == mArgs.Length(); + } +}; + +template<typename CleanupPolicy> +nsTArray<nsString>& +TErrorResult<CleanupPolicy>::CreateErrorMessageHelper(const dom::ErrNum errorNumber, + nsresult errorType) +{ + AssertInOwningThread(); + mResult = errorType; + + mMessage = new Message(); + mMessage->mErrorNumber = errorNumber; + return mMessage->mArgs; +} + +template<typename CleanupPolicy> +void +TErrorResult<CleanupPolicy>::SerializeMessage(IPC::Message* aMsg) const +{ + using namespace IPC; + AssertInOwningThread(); + MOZ_ASSERT(mUnionState == HasMessage); + MOZ_ASSERT(mMessage); + WriteParam(aMsg, mMessage->mArgs); + WriteParam(aMsg, mMessage->mErrorNumber); +} + +template<typename CleanupPolicy> +bool +TErrorResult<CleanupPolicy>::DeserializeMessage(const IPC::Message* aMsg, + PickleIterator* aIter) +{ + using namespace IPC; + AssertInOwningThread(); + nsAutoPtr<Message> readMessage(new Message()); + if (!ReadParam(aMsg, aIter, &readMessage->mArgs) || + !ReadParam(aMsg, aIter, &readMessage->mErrorNumber)) { + return false; + } + if (!readMessage->HasCorrectNumberOfArguments()) { + return false; + } + + MOZ_ASSERT(mUnionState == HasNothing); + mMessage = readMessage.forget(); +#ifdef DEBUG + mUnionState = HasMessage; +#endif // DEBUG + return true; +} + +template<typename CleanupPolicy> +void +TErrorResult<CleanupPolicy>::SetPendingExceptionWithMessage(JSContext* aCx) +{ + AssertInOwningThread(); + MOZ_ASSERT(mMessage, "SetPendingExceptionWithMessage() can be called only once"); + MOZ_ASSERT(mUnionState == HasMessage); + + Message* message = mMessage; + MOZ_RELEASE_ASSERT(message->HasCorrectNumberOfArguments()); + const uint32_t argCount = message->mArgs.Length(); + const char16_t* args[JS::MaxNumErrorArguments + 1]; + for (uint32_t i = 0; i < argCount; ++i) { + args[i] = message->mArgs.ElementAt(i).get(); + } + args[argCount] = nullptr; + + JS_ReportErrorNumberUCArray(aCx, dom::GetErrorMessage, nullptr, + static_cast<const unsigned>(message->mErrorNumber), + argCount > 0 ? args : nullptr); + + ClearMessage(); + mResult = NS_OK; +} + +template<typename CleanupPolicy> +void +TErrorResult<CleanupPolicy>::ClearMessage() +{ + AssertInOwningThread(); + MOZ_ASSERT(IsErrorWithMessage()); + delete mMessage; + 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 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. + mJSException.setUndefined(); + if (!js::AddRawValueRoot(cx, &mJSException, "TErrorResult::mJSException")) { + // Don't use NS_ERROR_DOM_JS_EXCEPTION, because that indicates we have + // in fact rooted mJSException. + mResult = NS_ERROR_OUT_OF_MEMORY; + } else { + mJSException = exn; + mResult = NS_ERROR_DOM_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, mJSException); + if (JS_WrapValue(cx, &exception)) { + JS_SetPendingException(cx, exception); + } + mJSException = exception; + // If JS_WrapValue failed, not much we can do about it... No matter + // what, go ahead and unroot mJSException. + js::RemoveRawValueRoot(cx, &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; +}; + +template<typename CleanupPolicy> +void +TErrorResult<CleanupPolicy>::SerializeDOMExceptionInfo(IPC::Message* aMsg) const +{ + using namespace IPC; + AssertInOwningThread(); + MOZ_ASSERT(mDOMExceptionInfo); + MOZ_ASSERT(mUnionState == HasDOMExceptionInfo); + WriteParam(aMsg, mDOMExceptionInfo->mMessage); + WriteParam(aMsg, mDOMExceptionInfo->mRv); +} + +template<typename CleanupPolicy> +bool +TErrorResult<CleanupPolicy>::DeserializeDOMExceptionInfo(const IPC::Message* aMsg, + PickleIterator* aIter) +{ + using namespace IPC; + AssertInOwningThread(); + nsCString message; + nsresult rv; + if (!ReadParam(aMsg, aIter, &message) || + !ReadParam(aMsg, aIter, &rv)) { + return false; + } + + MOZ_ASSERT(mUnionState == HasNothing); + MOZ_ASSERT(IsDOMException()); + mDOMExceptionInfo = 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_DOM_DOMEXCEPTION; + mDOMExceptionInfo = new DOMExceptionInfo(rv, message); +#ifdef DEBUG + mUnionState = HasDOMExceptionInfo; +#endif +} + +template<typename CleanupPolicy> +void +TErrorResult<CleanupPolicy>::SetPendingDOMException(JSContext* cx) +{ + AssertInOwningThread(); + MOZ_ASSERT(mDOMExceptionInfo, + "SetPendingDOMException() can be called only once"); + MOZ_ASSERT(mUnionState == HasDOMExceptionInfo); + + dom::Throw(cx, mDOMExceptionInfo->mRv, mDOMExceptionInfo->mMessage); + + ClearDOMExceptionInfo(); + mResult = NS_OK; +} + +template<typename CleanupPolicy> +void +TErrorResult<CleanupPolicy>::ClearDOMExceptionInfo() +{ + AssertInOwningThread(); + MOZ_ASSERT(IsDOMException()); + MOZ_ASSERT(mUnionState == HasDOMExceptionInfo || !mDOMExceptionInfo); + delete mDOMExceptionInfo; + 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); + mJSException.setUndefined(); + js::RemoveRawValueRoot(cx, &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()) { + mMessage = aRHS.mMessage; + aRHS.mMessage = nullptr; + } else if (aRHS.IsJSException()) { + JSContext* cx = dom::danger::GetJSContext(); + MOZ_ASSERT(cx); + mJSException.setUndefined(); + if (!js::AddRawValueRoot(cx, &mJSException, "TErrorResult::mJSException")) { + MOZ_CRASH("Could not root mJSException, we're about to OOM"); + } + mJSException = aRHS.mJSException; + aRHS.mJSException.setUndefined(); + js::RemoveRawValueRoot(cx, &aRHS.mJSException); + } else if (aRHS.IsDOMException()) { + mDOMExceptionInfo = aRHS.mDOMExceptionInfo; + aRHS.mDOMExceptionInfo = nullptr; + } else { + // Null out the union on both sides for hygiene purposes. + mMessage = aRHS.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> +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 + aRv.mMessage = new Message(); + aRv.mMessage->mArgs = mMessage->mArgs; + aRv.mMessage->mErrorNumber = mMessage->mErrorNumber; + } else if (IsDOMException()) { +#ifdef DEBUG + aRv.mUnionState = HasDOMExceptionInfo; +#endif + aRv.mDOMExceptionInfo = new DOMExceptionInfo(mDOMExceptionInfo->mRv, + mDOMExceptionInfo->mMessage); + } else if (IsJSException()) { +#ifdef DEBUG + aRv.mUnionState = HasJSException; +#endif + JSContext* cx = dom::danger::GetJSContext(); + JS::Rooted<JS::Value> exception(cx, 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) +{ + 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); + return; + } + if (IsJSException()) { + SetPendingJSException(cx); + return; + } + if (IsDOMException()) { + SetPendingDOMException(cx); + 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_DOM_EXCEPTION_ON_JSCONTEXT; + } else { + mResult = NS_ERROR_UNCATCHABLE_EXCEPTION; + } +} + +template class TErrorResult<JustAssertCleanupPolicy>; +template class TErrorResult<AssertAndSuppressCleanupPolicy>; +template class TErrorResult<JustSuppressCleanupPolicy>; + +} // 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 +DefineUnforgeableMethods(JSContext* cx, JS::Handle<JSObject*> obj, + const Prefable<const JSFunctionSpec>* props) +{ + return DefinePrefable(cx, obj, props); +} + +bool +DefineUnforgeableAttributes(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, + unsigned /* indent */) +{ + const js::Class* clasp = js::GetObjectClass(aObject); + MOZ_ASSERT(IsDOMIfaceAndProtoClass(clasp)); + + const DOMIfaceAndProtoJSClass* ifaceAndProtoJSClass = + DOMIfaceAndProtoJSClass::FromJSClass(clasp); + return JS_NewStringCopyZ(aCx, ifaceAndProtoJSClass->mToString); +} + +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, const char* name, + JS::Handle<JSObject*> constructor) +{ + bool alreadyDefined; + if (!JS_AlreadyHasOwnProperty(cx, global, name, &alreadyDefined)) { + return false; + } + + // This is Enumerable: False per spec. + return alreadyDefined || + JS_DefineProperty(cx, global, name, constructor, JSPROP_RESOLVING); +} + +static JSObject* +CreateInterfaceObject(JSContext* cx, JS::Handle<JSObject*> global, + JS::Handle<JSObject*> constructorProto, + const js::Class* constructorClass, + unsigned ctorNargs, const NamedConstructor* namedConstructors, + JS::Handle<JSObject*> proto, + const NativeProperties* properties, + const NativeProperties* chromeOnlyProperties, + const char* name, bool defineOnGlobal) +{ + JS::Rooted<JSObject*> constructor(cx); + MOZ_ASSERT(constructorProto); + MOZ_ASSERT(constructorClass); + constructor = JS_NewObjectWithGivenProto(cx, Jsvalify(constructorClass), + constructorProto); + if (!constructor) { + return nullptr; + } + + if (!JS_DefineProperty(cx, constructor, "length", ctorNargs, + JSPROP_READONLY)) { + return nullptr; + } + + // Might as well intern, since we're going to need an atomized + // version of name anyway when we stick our constructor on the + // global. + JS::Rooted<JSString*> nameStr(cx, JS_AtomizeAndPinString(cx, name)); + if (!nameStr) { + return nullptr; + } + + if (!JS_DefineProperty(cx, constructor, "name", nameStr, JSPROP_READONLY)) { + return nullptr; + } + + if (DOMIfaceAndProtoJSClass::FromJSClass(constructorClass)->wantsInterfaceHasInstance) { + JS::Rooted<jsid> hasInstanceId(cx, + SYMBOL_TO_JSID(JS::GetWellKnownSymbol(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 (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) { + 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 (proto && !JS_LinkConstructorAndPrototype(cx, constructor, proto)) { + return nullptr; + } + + if (defineOnGlobal && !DefineConstructor(cx, global, name, 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, + JS_STUBGETTER, JS_STUBSETTER) || + (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 js::Class* protoClass, + const NativeProperties* properties, + const NativeProperties* chromeOnlyProperties, + const char* const* unscopableNames, + bool isGlobal) +{ + JS::Rooted<JSObject*> ourProto(cx, + JS_NewObjectWithUniqueType(cx, Jsvalify(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_NewPlainObject(cx)); + if (!unscopableObj) { + return nullptr; + } + + for (; *unscopableNames; ++unscopableNames) { + if (!JS_DefineProperty(cx, unscopableObj, *unscopableNames, + JS::TrueHandleValue, JSPROP_ENUMERATE)) { + return nullptr; + } + } + + JS::Rooted<jsid> unscopableId(cx, + SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, JS::SymbolCode::unscopables))); + // Readonly and non-enumerable to match Array.prototype. + if (!JS_DefinePropertyById(cx, ourProto, unscopableId, unscopableObj, + JSPROP_READONLY)) { + 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 js::Class* protoClass, JS::Heap<JSObject*>* protoCache, + JS::Handle<JSObject*> constructorProto, + const js::Class* constructorClass, + unsigned ctorNargs, const NamedConstructor* namedConstructors, + JS::Heap<JSObject*>* constructorCache, + const NativeProperties* properties, + const NativeProperties* chromeOnlyProperties, + const char* name, bool defineOnGlobal, + const char* const* unscopableNames, + bool isGlobal) +{ + 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(bool(name) == bool(constructorClass), + "Must have name precisely when we have an interface object"); + 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"); + + JS::Rooted<JSObject*> proto(cx); + if (protoClass) { + proto = + CreateInterfacePrototypeObject(cx, global, protoProto, protoClass, + properties, chromeOnlyProperties, + unscopableNames, isGlobal); + if (!proto) { + return; + } + + *protoCache = proto; + } + else { + MOZ_ASSERT(!proto); + } + + JSObject* interface; + if (constructorClass) { + interface = CreateInterfaceObject(cx, global, constructorProto, + constructorClass, ctorNargs, + namedConstructors, proto, properties, + chromeOnlyProperties, name, + defineOnGlobal); + 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; + } +} + +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 && cache->IsDOMBinding()) { + JS::Rooted<JSObject*> obj(aCx, cache->GetWrapper()); + if (!obj) { + obj = cache->WrapObject(aCx, nullptr); + } + + if (obj && aAllowNativeWrapper && !JS_WrapObject(aCx, &obj)) { + return false; + } + + if (obj) { + aRetval.setObject(*obj); + return true; + } + } + + MOZ_ASSERT(NS_IsMainThread()); + + if (!XPCConvert::NativeInterface2JSObject(aRetval, nullptr, 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(JSObject* obj) +{ + MOZ_ASSERT(IsDOMObject(obj)); + + if (nsISupports* native = UnwrapDOMObjectToISupports(obj)) { + nsWrapperCache* cache = nullptr; + CallQueryInterface(native, &cache); + if (cache) { + cache->PreserveWrapper(native); + } + return true; + } + + // If this DOMClass is not cycle collected, then it isn't wrappercached, + // so it does not need to be preserved. If it is cycle collected, then + // we can't tell if it is wrappercached or not, so we just return false. + const DOMJSClass* domClass = GetDOMClass(obj); + return domClass && !domClass->mParticipant; +} + +// Can only be called with a DOM JSClass. +bool +InstanceClassHasProtoAtDepth(const js::Class* 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) +{ + if (!NativeInterface2JSObjectAndThrowIfFailed(cx, scope, rval, helper, iid, + allowNativeWrapper)) { + return false; + } + +#ifdef DEBUG + JSObject* jsobj = rval.toObjectOrNull(); + if (jsobj && + js::GetGlobalForObjectCrossCompartment(jsobj) == jsobj) { + NS_ASSERTION(js::GetObjectClass(jsobj)->flags & JSCLASS_IS_GLOBAL, + "Why did we recreate this wrapper?"); + } +#endif + + return true; +} + +bool +VariantToJsval(JSContext* aCx, nsIVariant* aVariant, + JS::MutableHandle<JS::Value> aRetval) +{ + nsresult rv; + if (!XPCVariant::VariantDataToJS(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 +QueryInterface(JSContext* cx, unsigned argc, JS::Value* vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::Rooted<JS::Value> thisv(cx, JS_THIS(cx, vp)); + if (thisv.isNull()) + return false; + + // Get the object. It might be a security wrapper, in which case we do a checked + // unwrap. + JS::Rooted<JSObject*> origObj(cx, &thisv.toObject()); + JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrap(origObj, + /* stopAtWindowProxy = */ false)); + if (!obj) { + JS_ReportErrorASCII(cx, "Permission denied to access object"); + return false; + } + + // Switch this to UnwrapDOMObjectToISupports once our global objects are + // using new bindings. + nsCOMPtr<nsISupports> native; + UnwrapArg<nsISupports>(obj, getter_AddRefs(native)); + if (!native) { + return Throw(cx, NS_ERROR_FAILURE); + } + + if (argc < 1) { + return Throw(cx, NS_ERROR_XPC_NOT_ENOUGH_ARGS); + } + + if (!args[0].isObject()) { + return Throw(cx, NS_ERROR_XPC_BAD_CONVERT_JS); + } + + nsCOMPtr<nsIJSID> iid; + obj = &args[0].toObject(); + if (NS_FAILED(UnwrapArg<nsIJSID>(obj, getter_AddRefs(iid)))) { + return Throw(cx, NS_ERROR_XPC_BAD_CONVERT_JS); + } + MOZ_ASSERT(iid); + + if (iid->GetID()->Equals(NS_GET_IID(nsIClassInfo))) { + nsresult rv; + nsCOMPtr<nsIClassInfo> ci = do_QueryInterface(native, &rv); + if (NS_FAILED(rv)) { + return Throw(cx, rv); + } + + return WrapObject(cx, ci, &NS_GET_IID(nsIClassInfo), args.rval()); + } + + nsCOMPtr<nsISupports> unused; + nsresult rv = native->QueryInterface(*iid->GetID(), getter_AddRefs(unused)); + if (NS_FAILED(rv)) { + return Throw(cx, rv); + } + + *vp = thisv; + return true; +} + +void +GetInterfaceImpl(JSContext* aCx, nsIInterfaceRequestor* aRequestor, + nsWrapperCache* aCache, nsIJSID* aIID, + JS::MutableHandle<JS::Value> aRetval, ErrorResult& aError) +{ + const nsID* iid = aIID->GetID(); + + RefPtr<nsISupports> result; + aError = aRequestor->GetInterface(*iid, getter_AddRefs(result)); + if (aError.Failed()) { + return; + } + + if (!WrapObject(aCx, result, iid, aRetval)) { + aError.Throw(NS_ERROR_FAILURE); + } +} + +bool +UnforgeableValueOf(JSContext* cx, unsigned argc, JS::Value* vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + args.rval().set(args.thisv()); + return true; +} + +bool +ThrowingConstructor(JSContext* cx, unsigned argc, JS::Value* vp) +{ + return ThrowErrorMessage(cx, MSG_ILLEGAL_CONSTRUCTOR); +} + +bool +ThrowConstructorWithoutNew(JSContext* cx, const char* name) +{ + return ThrowErrorMessage(cx, MSG_CONSTRUCTOR_WITHOUT_NEW, 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 js::Class* clasp = js::GetObjectClass(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(cx, obj)) { + type = eInterface; + return GetNativePropertyHooksFromConstructorFunction(obj); + } + + MOZ_ASSERT(IsDOMIfaceAndProtoClass(js::GetObjectClass(obj))); + const DOMIfaceAndProtoJSClass* ifaceAndProtoJSClass = + DOMIfaceAndProtoJSClass::FromJSClass(js::GetObjectClass(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 (JSID_IS_STRING(id)) { + 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; +} + +static bool +XrayResolveAttribute(JSContext* cx, JS::Handle<JSObject*> wrapper, + JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + const Prefable<const JSPropertySpec>* attributes, + const jsid* attributeIds, + const JSPropertySpec* attributeSpecs, + JS::MutableHandle<JS::PropertyDescriptor> desc, + bool& cacheOnHolder) +{ + for (; attributes->specs; ++attributes) { + if (attributes->isEnabled(cx, obj)) { + // Set i to be the index into our full list of ids/specs that we're + // looking at now. + size_t i = attributes->specs - attributeSpecs; + for ( ; attributeIds[i] != JSID_VOID; ++i) { + if (id == attributeIds[i]) { + cacheOnHolder = true; + + const JSPropertySpec& attrSpec = attributeSpecs[i]; + // 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. + desc.setAttributes(attrSpec.flags); + // They all have getters, so we can just make it. + JS::Rooted<JSObject*> funobj(cx, + XrayCreateFunction(cx, wrapper, attrSpec.accessors.getter.native, 0, id)); + if (!funobj) + return false; + desc.setGetterObject(funobj); + desc.attributesRef() |= JSPROP_GETTER; + if (attrSpec.accessors.setter.native.op) { + // We have a setter! Make it. + funobj = + XrayCreateFunction(cx, wrapper, attrSpec.accessors.setter.native, 1, id); + if (!funobj) + return false; + desc.setSetterObject(funobj); + desc.attributesRef() |= JSPROP_SETTER; + } else { + desc.setSetter(nullptr); + } + desc.object().set(wrapper); + return true; + } + } + } + } + return true; +} + +static bool +XrayResolveMethod(JSContext* cx, JS::Handle<JSObject*> wrapper, + JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + const Prefable<const JSFunctionSpec>* methods, + const jsid* methodIds, + const JSFunctionSpec* methodSpecs, + JS::MutableHandle<JS::PropertyDescriptor> desc, + bool& cacheOnHolder) +{ + const Prefable<const JSFunctionSpec>* method; + for (method = methods; method->specs; ++method) { + if (method->isEnabled(cx, obj)) { + // Set i to be the index into our full list of ids/specs that we're + // looking at now. + size_t i = method->specs - methodSpecs; + for ( ; methodIds[i] != JSID_VOID; ++i) { + if (id == methodIds[i]) { + cacheOnHolder = true; + + const JSFunctionSpec& methodSpec = methodSpecs[i]; + 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.value().setObject(*funobj); + desc.setAttributes(methodSpec.flags); + desc.object().set(wrapper); + desc.setSetter(nullptr); + desc.setGetter(nullptr); + return true; + } + } + } + } + return true; +} + +// Try to resolve a property as an unforgeable property from the given +// NativeProperties, if it's there. nativeProperties is allowed to be null (in +// which case we of course won't resolve anything). +static bool +XrayResolveUnforgeableProperty(JSContext* cx, JS::Handle<JSObject*> wrapper, + JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::MutableHandle<JS::PropertyDescriptor> desc, + bool& cacheOnHolder, + const NativeProperties* nativeProperties) +{ + if (!nativeProperties) { + return true; + } + + if (nativeProperties->HasUnforgeableAttributes()) { + if (!XrayResolveAttribute(cx, wrapper, obj, id, + nativeProperties->UnforgeableAttributes(), + nativeProperties->UnforgeableAttributeIds(), + nativeProperties->UnforgeableAttributeSpecs(), + desc, cacheOnHolder)) { + return false; + } + + if (desc.object()) { + return true; + } + } + + if (nativeProperties->HasUnforgeableMethods()) { + if (!XrayResolveMethod(cx, wrapper, obj, id, + nativeProperties->UnforgeableMethods(), + nativeProperties->UnforgeableMethodIds(), + nativeProperties->UnforgeableMethodSpecs(), + desc, cacheOnHolder)) { + return false; + } + + if (desc.object()) { + return true; + } + } + + return true; +} + +static bool +XrayResolveProperty(JSContext* cx, JS::Handle<JSObject*> wrapper, + JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::MutableHandle<JS::PropertyDescriptor> desc, + bool& cacheOnHolder, DOMObjectType type, + const NativeProperties* nativeProperties) +{ + bool hasMethods = false; + if (type == eInterface) { + hasMethods = nativeProperties->HasStaticMethods(); + } else { + hasMethods = nativeProperties->HasMethods(); + } + if (hasMethods) { + const Prefable<const JSFunctionSpec>* methods; + const jsid* methodIds; + const JSFunctionSpec* methodSpecs; + if (type == eInterface) { + methods = nativeProperties->StaticMethods(); + methodIds = nativeProperties->StaticMethodIds(); + methodSpecs = nativeProperties->StaticMethodSpecs(); + } else { + methods = nativeProperties->Methods(); + methodIds = nativeProperties->MethodIds(); + methodSpecs = nativeProperties->MethodSpecs(); + } + JS::Rooted<jsid> methodId(cx); + if (nativeProperties->iteratorAliasMethodIndex != -1 && + id == SYMBOL_TO_JSID( + JS::GetWellKnownSymbol(cx, JS::SymbolCode::iterator))) { + methodId = + nativeProperties->MethodIds()[nativeProperties->iteratorAliasMethodIndex]; + } else { + methodId = id; + } + if (!XrayResolveMethod(cx, wrapper, obj, methodId, methods, methodIds, + methodSpecs, desc, cacheOnHolder)) { + return false; + } + if (desc.object()) { + return true; + } + } + + if (type == eInterface) { + if (nativeProperties->HasStaticAttributes()) { + if (!XrayResolveAttribute(cx, wrapper, obj, id, + nativeProperties->StaticAttributes(), + nativeProperties->StaticAttributeIds(), + nativeProperties->StaticAttributeSpecs(), + desc, cacheOnHolder)) { + return false; + } + if (desc.object()) { + return true; + } + } + } else { + if (nativeProperties->HasAttributes()) { + if (!XrayResolveAttribute(cx, wrapper, obj, id, + nativeProperties->Attributes(), + nativeProperties->AttributeIds(), + nativeProperties->AttributeSpecs(), + desc, cacheOnHolder)) { + return false; + } + if (desc.object()) { + return true; + } + } + } + + if (nativeProperties->HasConstants()) { + const Prefable<const ConstantSpec>* constant; + for (constant = nativeProperties->Constants(); constant->specs; ++constant) { + if (constant->isEnabled(cx, obj)) { + // Set i to be the index into our full list of ids/specs that we're + // looking at now. + size_t i = constant->specs - nativeProperties->ConstantSpecs(); + for ( ; nativeProperties->ConstantIds()[i] != JSID_VOID; ++i) { + if (id == nativeProperties->ConstantIds()[i]) { + cacheOnHolder = true; + + desc.setAttributes(JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); + desc.object().set(wrapper); + desc.value().set(nativeProperties->ConstantSpecs()[i].value); + return true; + } + } + } + } + } + + return true; +} + +static bool +ResolvePrototypeOrConstructor(JSContext* cx, JS::Handle<JSObject*> wrapper, + JS::Handle<JSObject*> obj, + size_t protoAndIfaceCacheIndex, unsigned attrs, + JS::MutableHandle<JS::PropertyDescriptor> desc, + bool& cacheOnHolder) +{ + JS::Rooted<JSObject*> global(cx, js::GetGlobalForObjectCrossCompartment(obj)); + { + JSAutoCompartment ac(cx, global); + ProtoAndIfaceCache& protoAndIfaceCache = *GetProtoAndIfaceCache(global); + JSObject* protoOrIface = + protoAndIfaceCache.EntrySlotIfExists(protoAndIfaceCacheIndex); + if (!protoOrIface) { + return false; + } + + cacheOnHolder = true; + + desc.object().set(wrapper); + desc.setAttributes(attrs); + desc.setGetter(nullptr); + desc.setSetter(nullptr); + desc.value().set(JS::ObjectValue(*protoOrIface)); + } + return JS_WrapPropertyDescriptor(cx, desc); +} + +#ifdef DEBUG + +static void +DEBUG_CheckXBLCallable(JSContext *cx, JSObject *obj) +{ + // In general, we shouldn't have cross-compartment wrappers here, because + // we should be running in an XBL scope, and the content prototype should + // contain wrappers to functions defined in the XBL scope. But if the node + // has been adopted into another compartment, those prototypes will now point + // to a different XBL scope (which is ok). + MOZ_ASSERT_IF(js::IsCrossCompartmentWrapper(obj), + xpc::IsContentXBLScope(js::GetObjectCompartment(js::UncheckedUnwrap(obj)))); + MOZ_ASSERT(JS::IsCallable(obj)); +} + +static void +DEBUG_CheckXBLLookup(JSContext *cx, JS::PropertyDescriptor *desc) +{ + if (!desc->obj) + return; + if (!desc->value.isUndefined()) { + MOZ_ASSERT(desc->value.isObject()); + DEBUG_CheckXBLCallable(cx, &desc->value.toObject()); + } + if (desc->getter) { + MOZ_ASSERT(desc->attrs & JSPROP_GETTER); + DEBUG_CheckXBLCallable(cx, JS_FUNC_TO_DATA_PTR(JSObject *, desc->getter)); + } + if (desc->setter) { + MOZ_ASSERT(desc->attrs & JSPROP_SETTER); + DEBUG_CheckXBLCallable(cx, JS_FUNC_TO_DATA_PTR(JSObject *, desc->setter)); + } +} +#else +#define DEBUG_CheckXBLLookup(a, b) {} +#endif + +/* static */ bool +XrayResolveOwnProperty(JSContext* cx, JS::Handle<JSObject*> wrapper, + JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::MutableHandle<JS::PropertyDescriptor> desc, + bool& cacheOnHolder) +{ + cacheOnHolder = false; + + DOMObjectType type; + const NativePropertyHooks *nativePropertyHooks = + GetNativePropertyHooks(cx, obj, type); + const NativePropertiesHolder& nativeProperties = + nativePropertyHooks->mNativeProperties; + ResolveOwnProperty resolveOwnProperty = + nativePropertyHooks->mResolveOwnProperty; + + if (type == eNamedPropertiesObject) { + // None of these should be cached on the holder, since they're dynamic. + return resolveOwnProperty(cx, wrapper, obj, id, desc); + } + + // Check for unforgeable properties first. + if (IsInstance(type)) { + const NativePropertiesHolder& nativeProperties = + nativePropertyHooks->mNativeProperties; + if (!XrayResolveUnforgeableProperty(cx, wrapper, obj, id, desc, cacheOnHolder, + nativeProperties.regular)) { + return false; + } + + if (!desc.object() && xpc::AccessCheck::isChrome(wrapper) && + !XrayResolveUnforgeableProperty(cx, wrapper, obj, id, desc, cacheOnHolder, + nativeProperties.chromeOnly)) { + return false; + } + + if (desc.object()) { + return true; + } + } + + if (IsInstance(type)) { + if (resolveOwnProperty) { + if (!resolveOwnProperty(cx, wrapper, obj, id, desc)) { + return false; + } + + if (desc.object()) { + // None of these should be cached on the holder, since they're dynamic. + return true; + } + } + + // If we're a special scope for in-content XBL, our script expects to see + // the bound XBL methods and attributes when accessing content. However, + // these members are implemented in content via custom-spliced prototypes, + // and thus aren't visible through Xray wrappers unless we handle them + // explicitly. So we check if we're running in such a scope, and if so, + // whether the wrappee is a bound element. If it is, we do a lookup via + // specialized XBL machinery. + // + // While we have to do some sketchy walking through content land, we should + // be protected by read-only/non-configurable properties, and any functions + // we end up with should _always_ be living in our own scope (the XBL scope). + // Make sure to assert that. + JS::Rooted<JSObject*> maybeElement(cx, obj); + Element* element; + if (xpc::ObjectScope(wrapper)->IsContentXBLScope() && + NS_SUCCEEDED(UNWRAP_OBJECT(Element, &maybeElement, element))) { + if (!nsContentUtils::LookupBindingMember(cx, element, id, desc)) { + return false; + } + + DEBUG_CheckXBLLookup(cx, desc.address()); + + if (desc.object()) { + // XBL properties shouldn't be cached on the holder, as they might be + // shadowed by own properties returned from mResolveOwnProperty. + desc.object().set(wrapper); + + 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 (IdEquals(id, "prototype")) { + return nativePropertyHooks->mPrototypeID == prototypes::id::_ID_Count || + ResolvePrototypeOrConstructor(cx, wrapper, obj, + nativePropertyHooks->mPrototypeID, + JSPROP_PERMANENT | JSPROP_READONLY, + desc, cacheOnHolder); + } + + if (id == SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, JS::SymbolCode::hasInstance)) && + DOMIfaceAndProtoJSClass::FromJSClass(js::GetObjectClass(obj))-> + wantsInterfaceHasInstance) { + cacheOnHolder = true; + JSNativeWrapper interfaceHasInstanceWrapper = { InterfaceHasInstance, + nullptr }; + JSObject* funObj = XrayCreateFunction(cx, wrapper, + interfaceHasInstanceWrapper, 1, id); + if (!funObj) { + return false; + } + + desc.value().setObject(*funObj); + desc.setAttributes(JSPROP_READONLY | JSPROP_PERMANENT); + desc.object().set(wrapper); + desc.setSetter(nullptr); + desc.setGetter(nullptr); + return true; + } + } else { + MOZ_ASSERT(IsInterfacePrototype(type)); + + if (IdEquals(id, "constructor")) { + return nativePropertyHooks->mConstructorID == constructors::id::_ID_Count || + ResolvePrototypeOrConstructor(cx, wrapper, obj, + nativePropertyHooks->mConstructorID, + 0, desc, cacheOnHolder); + } + + // 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 (nativeProperties.regular && + !XrayResolveProperty(cx, wrapper, obj, id, desc, cacheOnHolder, type, + nativeProperties.regular)) { + return false; + } + + if (!desc.object() && + nativeProperties.chromeOnly && + xpc::AccessCheck::isChrome(js::GetObjectCompartment(wrapper)) && + !XrayResolveProperty(cx, wrapper, obj, id, desc, cacheOnHolder, type, + nativeProperties.chromeOnly)) { + 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 *defined) +{ + if (!js::IsProxy(obj)) + return true; + + const DOMProxyHandler* handler = GetDOMProxyHandler(obj); + return handler->defineProperty(cx, wrapper, id, desc, result, defined); +} + +template<typename SpecType> +bool +XrayAttributeOrMethodKeys(JSContext* cx, JS::Handle<JSObject*> wrapper, + JS::Handle<JSObject*> obj, + const Prefable<const SpecType>* list, + const jsid* ids, const SpecType* specList, + unsigned flags, JS::AutoIdVector& props) +{ + for (; list->specs; ++list) { + if (list->isEnabled(cx, obj)) { + // Set i to be the index into our full list of ids/specs that we're + // looking at now. + size_t i = list->specs - specList; + for ( ; ids[i] != JSID_VOID; ++i) { + // Skip non-enumerable properties and symbol-keyed properties unless + // they are specially requested via flags. + if (((flags & JSITER_HIDDEN) || + (specList[i].flags & JSPROP_ENUMERATE)) && + ((flags & JSITER_SYMBOLS) || !JSID_IS_SYMBOL(ids[i])) && + !props.append(ids[i])) { + return false; + } + } + } + } + return true; +} + +#define ADD_KEYS_IF_DEFINED(FieldName) { \ + if (nativeProperties->Has##FieldName##s() && \ + !XrayAttributeOrMethodKeys(cx, wrapper, obj, \ + nativeProperties->FieldName##s(), \ + nativeProperties->FieldName##Ids(), \ + nativeProperties->FieldName##Specs(), \ + flags, props)) { \ + return false; \ + } \ +} + + +bool +XrayOwnPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper, + JS::Handle<JSObject*> obj, + unsigned flags, JS::AutoIdVector& 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 if (type == eInterface) { + ADD_KEYS_IF_DEFINED(StaticMethod); + ADD_KEYS_IF_DEFINED(StaticAttribute); + } else if (type != eGlobalInterfacePrototype) { + MOZ_ASSERT(IsInterfacePrototype(type)); + ADD_KEYS_IF_DEFINED(Method); + ADD_KEYS_IF_DEFINED(Attribute); + } + + if (nativeProperties->HasConstants()) { + const Prefable<const ConstantSpec>* constant; + for (constant = nativeProperties->Constants(); constant->specs; ++constant) { + if (constant->isEnabled(cx, obj)) { + // Set i to be the index into our full list of ids/specs that we're + // looking at now. + size_t i = constant->specs - nativeProperties->ConstantSpecs(); + for ( ; nativeProperties->ConstantIds()[i] != JSID_VOID; ++i) { + if (!props.append(nativeProperties->ConstantIds()[i])) { + return false; + } + } + } + } + } + + 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::AutoIdVector& 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 (nativeProperties.regular && + !XrayOwnPropertyKeys(cx, wrapper, obj, flags, props, type, + nativeProperties.regular)) { + return false; + } + + if (nativeProperties.chromeOnly && + xpc::AccessCheck::isChrome(js::GetObjectCompartment(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::AutoIdVector& props) +{ + DOMObjectType type; + const NativePropertyHooks* nativePropertyHooks = + GetNativePropertyHooks(cx, obj, type); + EnumerateOwnProperties enumerateOwnProperties = + nativePropertyHooks->mEnumerateOwnProperties; + + if (type == eNamedPropertiesObject) { + return enumerateOwnProperties(cx, wrapper, obj, props); + } + + 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); +} + +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); + +NativePropertyHooks sEmptyNativePropertyHooks = { + nullptr, + nullptr, + nullptr, + { + nullptr, + nullptr + }, + prototypes::id::_ID_Count, + constructors::id::_ID_Count, + nullptr +}; + +const js::ClassOps sBoringInterfaceObjectClassClassOps = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* enumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + nullptr, /* finalize */ + ThrowingConstructor, /* call */ + nullptr, /* hasInstance */ + ThrowingConstructor, /* construct */ + nullptr, /* trace */ +}; + +const js::ObjectOps sInterfaceObjectClassObjectOps = { + nullptr, /* lookupProperty */ + nullptr, /* defineProperty */ + nullptr, /* hasProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* getOwnPropertyDescriptor */ + nullptr, /* deleteProperty */ + nullptr, /* watch */ + nullptr, /* unwatch */ + nullptr, /* getElements */ + nullptr, /* enumerate */ + 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::AutoIdVector& 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, PromiseFlatString(aJSON).get(), 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; +} + +nsresult +ReparentWrapper(JSContext* aCx, JS::Handle<JSObject*> aObjArg) +{ + js::AssertSameCompartment(aCx, aObjArg); + + // 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_CHECK_RECURSION_CONSERVATIVE(aCx, return NS_ERROR_FAILURE); + + JS::Rooted<JSObject*> aObj(aCx, aObjArg); + const DOMJSClass* domClass = GetDOMClass(aObj); + + // DOM things are always parented to globals. + JS::Rooted<JSObject*> oldParent(aCx, + js::GetGlobalForObjectCrossCompartment(aObj)); + MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(oldParent) == oldParent); + + JS::Rooted<JSObject*> newParent(aCx, + domClass->mGetAssociatedGlobal(aCx, aObj)); + MOZ_ASSERT(JS_IsGlobalObject(newParent)); + + JSAutoCompartment oldAc(aCx, oldParent); + + JSCompartment* oldCompartment = js::GetObjectCompartment(oldParent); + JSCompartment* newCompartment = js::GetObjectCompartment(newParent); + if (oldCompartment == newCompartment) { + MOZ_ASSERT(oldParent == newParent); + return NS_OK; + } + + nsISupports* native = UnwrapDOMObjectToISupports(aObj); + if (!native) { + return NS_OK; + } + + bool isProxy = js::IsProxy(aObj); + JS::Rooted<JSObject*> expandoObject(aCx); + if (isProxy) { + expandoObject = DOMProxyHandler::GetAndClearExpandoObject(aObj); + } + + JSAutoCompartment newAc(aCx, newParent); + + // 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) { + return NS_ERROR_FAILURE; + } + + JS::Rooted<JSObject*> newobj(aCx, JS_CloneObject(aCx, aObj, proto)); + if (!newobj) { + return NS_ERROR_FAILURE; + } + + JS::Rooted<JSObject*> propertyHolder(aCx); + JS::Rooted<JSObject*> copyFrom(aCx, isProxy ? expandoObject : aObj); + if (copyFrom) { + propertyHolder = JS_NewObjectWithGivenProto(aCx, nullptr, nullptr); + if (!propertyHolder) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (!JS_CopyPropertiesFrom(aCx, propertyHolder, copyFrom)) { + return NS_ERROR_FAILURE; + } + } else { + propertyHolder = nullptr; + } + + // Expandos from other compartments are attached to the target JS object. + // Copy them over, and let the old ones die a natural death. + + // Note that at this point the DOM_OBJECT_SLOT for |newobj| has not been set. + // CloneExpandoChain() will use this property of |newobj| when it calls + // preserveWrapper() via attachExpandoObject() if |aObj| has expandos set, and + // preserveWrapper() will not do anything in this case. This is safe because + // if expandos are present then the wrapper will already have been preserved + // for this native. + if (!xpc::XrayUtils::CloneExpandoChain(aCx, newobj, aObj)) { + return NS_ERROR_FAILURE; + } + + // 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|. + // + // NB: It's important to do this _after_ copying the properties to + // propertyHolder. Otherwise, an object with |foo.x === foo| will + // crash when JS_CopyPropertiesFrom tries to call wrap() on foo.x. + js::SetReservedOrProxyPrivateSlot(newobj, DOM_OBJECT_SLOT, + js::GetReservedOrProxyPrivateSlot(aObj, DOM_OBJECT_SLOT)); + js::SetReservedOrProxyPrivateSlot(aObj, DOM_OBJECT_SLOT, JS::PrivateValue(nullptr)); + + aObj = xpc::TransplantObject(aCx, aObj, newobj); + if (!aObj) { + MOZ_CRASH(); + } + + nsWrapperCache* cache = nullptr; + CallQueryInterface(native, &cache); + bool preserving = cache->PreservingWrapper(); + cache->SetPreservingWrapper(false); + cache->SetWrapper(aObj); + cache->SetPreservingWrapper(preserving); + + if (propertyHolder) { + JS::Rooted<JSObject*> copyTo(aCx); + if (isProxy) { + copyTo = DOMProxyHandler::EnsureExpandoObject(aCx, aObj); + } else { + copyTo = aObj; + } + + if (!copyTo || !JS_CopyPropertiesFrom(aCx, copyTo, propertyHolder)) { + MOZ_CRASH(); + } + } + + JS::Rooted<JSObject*> maybeObjLC(aCx, aObj); + nsObjectLoadingContent* htmlobject; + nsresult rv = UNWRAP_OBJECT(HTMLObjectElement, &maybeObjLC, htmlobject); + if (NS_FAILED(rv)) { + rv = UnwrapObject<prototypes::id::HTMLEmbedElement, + HTMLSharedObjectElement>(&maybeObjLC, htmlobject); + if (NS_FAILED(rv)) { + rv = UnwrapObject<prototypes::id::HTMLAppletElement, + HTMLSharedObjectElement>(&maybeObjLC, htmlobject); + if (NS_FAILED(rv)) { + htmlobject = nullptr; + } + } + } + if (htmlobject) { + htmlobject->SetupProtoChain(aCx, aObj); + } + + // Now we can just return the wrapper + return NS_OK; +} + +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)) { + obj = js::CheckedUnwrap(obj, /* 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::GetGlobalForObjectCrossCompartment(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. + nsCOMPtr<nsISupports> supp = xpc::UnwrapReflectorToISupports(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; + } + + JSCompartment* compartment = js::GetContextCompartment(mCx); + MOZ_ASSERT(compartment); + JSPrincipals* principals = JS_GetCompartmentPrincipals(compartment); + return nsJSPrincipals::get(principals); +} + +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; +} + +bool +InterfaceHasInstance(JSContext* cx, unsigned argc, JS::Value* vp) +{ + 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 to OrdinaryHasInstance. But note that we + // should CheckedUnwrap here, because otherwise we won't get the right + // answers. + JS::Rooted<JSObject*> thisObj(cx, js::CheckedUnwrap(&args.thisv().toObject())); + if (!thisObj) { + // Just fall back on the normal thing, in case it still happens to work. + return CallOrdinaryHasInstance(cx, args); + } + + const js::Class* thisClass = js::GetObjectClass(thisObj); + + if (!IsDOMIfaceAndProtoClass(thisClass)) { + return CallOrdinaryHasInstance(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 to OrdinaryHasInstance. + if (clasp->mType != eInterface || + clasp->mPrototypeID == prototypes::id::_ID_Count) { + return CallOrdinaryHasInstance(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 (jsipc::IsWrappedCPOW(instance)) { + bool boolp = false; + if (!jsipc::DOMInstanceOf(cx, js::UncheckedUnwrap(instance), clasp->mPrototypeID, + clasp->mDepth, &boolp)) { + return false; + } + args.rval().setBoolean(boolp); + return true; + } + + 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 +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(nsIDocument::eLenientThis); + } + return true; +} + +bool +GetContentGlobalForJSImplementedObject(JSContext* cx, JS::Handle<JSObject*> obj, + nsIGlobalObject** globalObj) +{ + // Be very careful to not get tricked here. + MOZ_ASSERT(NS_IsMainThread()); + if (!xpc::AccessCheck::isChrome(js::GetObjectCompartment(obj))) { + NS_RUNTIMEABORT("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()) { + ThrowErrorMessage(cx, 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; +} + +already_AddRefed<nsIGlobalObject> +ConstructJSImplementation(const char* aContractId, + const GlobalObject& aGlobal, + JS::MutableHandle<JSObject*> aObject, + ErrorResult& aRv) +{ + // Get the global object to use as a parent and for initialization. + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); + if (!global) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + ConstructJSImplementation(aContractId, global, aObject, aRv); + + if (aRv.Failed()) { + return nullptr; + } + return global.forget(); +} + +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; + + // 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); + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal); + 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; +} + + +template<typename T> static void +NormalizeUSVStringInternal(JSContext* aCx, T& aString) +{ + char16_t* start = aString.BeginWriting(); + // Must use const here because we can't pass char** to UTF16CharEnumerator as + // it expects const char**. Unclear why this is illegal... + const char16_t* nextChar = start; + const char16_t* end = aString.Data() + aString.Length(); + while (nextChar < end) { + uint32_t enumerated = UTF16CharEnumerator::NextChar(&nextChar, end); + if (enumerated == UCS2_REPLACEMENT_CHAR) { + int32_t lastCharIndex = (nextChar - start) - 1; + start[lastCharIndex] = static_cast<char16_t>(enumerated); + } + } +} + +void +NormalizeUSVString(JSContext* aCx, nsAString& aString) +{ + NormalizeUSVStringInternal(aCx, aString); +} + +void +NormalizeUSVString(JSContext* aCx, binding_detail::FakeString& aString) +{ + NormalizeUSVStringInternal(aCx, aString); +} + +bool +ConvertJSValueToByteString(JSContext* cx, JS::Handle<JS::Value> v, + bool nullable, 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, "%" PRIuSIZE, 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); + ThrowErrorMessage(cx, MSG_INVALID_BYTESTRING, 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; + } + + JS_EncodeStringToBuffer(cx, s, result.BeginWriting(), length); + + return true; +} + +void +FinalizeGlobal(JSFreeOp* aFreeOp, JSObject* aObj) +{ + MOZ_ASSERT(js::GetObjectClass(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) +{ + MOZ_ASSERT(JS_IsGlobalObject(aObj), + "Should have a global here, since we plan to enumerate standard " + "classes!"); + + return JS_EnumerateStandardClasses(aCx, aObj); +} + +bool +IsNonExposedGlobal(JSContext* aCx, JSObject* aGlobal, + uint32_t aNonExposedGlobals) +{ + MOZ_ASSERT(aNonExposedGlobals, "Why did we get called?"); + MOZ_ASSERT((aNonExposedGlobals & + ~(GlobalNames::Window | + GlobalNames::BackstagePass | + GlobalNames::DedicatedWorkerGlobalScope | + GlobalNames::SharedWorkerGlobalScope | + GlobalNames::ServiceWorkerGlobalScope | + GlobalNames::WorkerDebuggerGlobalScope | + GlobalNames::WorkletGlobalScope)) == 0, + "Unknown non-exposed global type"); + + const char* name = js::GetObjectClass(aGlobal)->name; + + if ((aNonExposedGlobals & GlobalNames::Window) && + !strcmp(name, "Window")) { + return true; + } + + if ((aNonExposedGlobals & GlobalNames::BackstagePass) && + !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::WorkletGlobalScope) && + !strcmp(name, "WorkletGlobalScope")) { + return true; + } + + return false; +} + +void +HandlePrerenderingViolation(nsPIDOMWindowInner* aWindow) +{ + // Freeze the window and its workers, and its children too. + aWindow->Freeze(); + + // Suspend event handling on the document + nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc(); + if (doc) { + doc->SuppressEventHandling(nsIDocument::eEvents); + } +} + +bool +EnforceNotInPrerendering(JSContext* aCx, JSObject* aObj) +{ + JS::Rooted<JSObject*> thisObj(aCx, js::CheckedUnwrap(aObj)); + if (!thisObj) { + // Without a this object, we cannot check the safety. + return true; + } + nsGlobalWindow* window = xpc::WindowGlobalOrNull(thisObj); + if (!window) { + // Without a window, we cannot check the safety. + return true; + } + + if (window->GetIsPrerendered()) { + HandlePrerenderingViolation(window->AsInner()); + // When the bindings layer sees a false return value, it returns false form + // the JSNative in order to trigger an uncatchable exception. + return false; + } + + return true; +} + +bool +GenericBindingGetter(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 (!args.thisv().isObject()) { + return ThrowInvalidThis(cx, args, false, protoID); + } + JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject()); + + // NOTE: we want to leave obj in its initial compartment, so don't want to + // pass it to UnwrapObject. + JS::Rooted<JSObject*> rootSelf(cx, obj); + void* self; + { + binding_detail::MutableObjectHandleWrapper wrapper(&rootSelf); + nsresult rv = binding_detail::UnwrapObjectInternal<void, true>(wrapper, + self, + protoID, + info->depth); + if (NS_FAILED(rv)) { + return ThrowInvalidThis(cx, args, + rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO, + protoID); + } + } + + 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 ok; +} + +bool +GenericBindingSetter(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 (!args.thisv().isObject()) { + return ThrowInvalidThis(cx, args, false, protoID); + } + JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject()); + + // NOTE: we want to leave obj in its initial compartment, so don't want to + // pass it to UnwrapObject. + JS::Rooted<JSObject*> rootSelf(cx, obj); + void* self; + { + binding_detail::MutableObjectHandleWrapper wrapper(&rootSelf); + nsresult rv = binding_detail::UnwrapObjectInternal<void, true>(wrapper, + self, + protoID, + info->depth); + if (NS_FAILED(rv)) { + return ThrowInvalidThis(cx, args, + rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO, + protoID); + } + } + if (args.length() == 0) { + return ThrowNoSetterArg(cx, 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; +} + +bool +GenericBindingMethod(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 (!args.thisv().isObject()) { + return ThrowInvalidThis(cx, args, false, protoID); + } + JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject()); + + // NOTE: we want to leave obj in its initial compartment, so don't want to + // pass it to UnwrapObject. + JS::Rooted<JSObject*> rootSelf(cx, obj); + void* self; + { + binding_detail::MutableObjectHandleWrapper wrapper(&rootSelf); + nsresult rv = binding_detail::UnwrapObjectInternal<void, true>(wrapper, + self, + protoID, + info->depth); + if (NS_FAILED(rv)) { + return ThrowInvalidThis(cx, args, + rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO, + protoID); + } + } + 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 ok; +} + +bool +GenericPromiseReturningBindingMethod(JSContext* cx, unsigned argc, JS::Value* vp) +{ + // Make sure to save the callee before someone maybe messes with rval(). + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::Rooted<JSObject*> callee(cx, &args.callee()); + + // We could invoke GenericBindingMethod here, but that involves an + // extra call. Manually inline it instead. + const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev()); + prototypes::ID protoID = static_cast<prototypes::ID>(info->protoID); + if (!args.thisv().isObject()) { + ThrowInvalidThis(cx, args, false, protoID); + return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee), + args.rval()); + } + JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject()); + + // NOTE: we want to leave obj in its initial compartment, so don't want to + // pass it to UnwrapObject. + JS::Rooted<JSObject*> rootSelf(cx, obj); + void* self; + { + binding_detail::MutableObjectHandleWrapper wrapper(&rootSelf); + nsresult rv = binding_detail::UnwrapObjectInternal<void, true>(wrapper, + self, + protoID, + info->depth); + if (NS_FAILED(rv)) { + ThrowInvalidThis(cx, args, rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO, + protoID); + return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee), + args.rval()); + } + } + MOZ_ASSERT(info->type() == JSJitInfo::Method); + JSJitMethodOp method = info->method; + bool ok = method(cx, obj, self, JSJitMethodCallArgs(args)); + if (ok) { +#ifdef DEBUG + AssertReturnTypeMatchesJitinfo(info, args.rval()); +#endif + return true; + } + + // Promise-returning methods always return objects + MOZ_ASSERT(info->returnType() == JSVAL_TYPE_OBJECT); + return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee), + args.rval()); +} + +bool +StaticMethodPromiseWrapper(JSContext* cx, unsigned argc, JS::Value* vp) +{ + // Make sure to save the callee before someone maybe messes with rval(). + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::Rooted<JSObject*> callee(cx, &args.callee()); + + 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, xpc::XrayAwareCalleeGlobal(callee), + args.rval()); +} + +bool +ConvertExceptionToPromise(JSContext* cx, + JSObject* promiseScope, + JS::MutableHandle<JS::Value> rval) +{ +#ifndef SPIDERMONKEY_PROMISE + GlobalObject global(cx, promiseScope); + if (global.Failed()) { + return false; + } + + 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); + + nsCOMPtr<nsIGlobalObject> globalObj = + do_QueryInterface(global.GetAsSupports()); + if (!globalObj) { + ErrorResult rv; + rv.Throw(NS_ERROR_UNEXPECTED); + return !rv.MaybeSetPendingException(cx); + } + + ErrorResult rv; + RefPtr<Promise> promise = Promise::Reject(globalObj, cx, exn, rv); + if (rv.MaybeSetPendingException(cx)) { + // We just give up. We put the exception from the ErrorResult on + // the JSContext just to make sure to not leak memory on the + // ErrorResult, but now just put the original exception back. + JS_SetPendingException(cx, exn); + return false; + } + + return GetOrCreateDOMReflector(cx, promise, rval); +#else // SPIDERMONKEY_PROMISE + { + JSAutoCompartment ac(cx, promiseScope); + + 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); + } + + // Now make sure we rewrap promise back into the compartment we want + return JS_WrapValue(cx, rval); +#endif // SPIDERMONKEY_PROMISE +} + +/* static */ +void +CreateGlobalOptions<nsGlobalWindow>::TraceGlobal(JSTracer* aTrc, JSObject* aObj) +{ + xpc::TraceXPCGlobal(aTrc, aObj); +} + +static bool sRegisteredDOMNames = false; + +nsresult +RegisterDOMNames() +{ + if (sRegisteredDOMNames) { + return NS_OK; + } + + // Register new DOM bindings + WebIDLGlobalNameHash::Init(); + + nsresult rv = nsDOMClassInfo::Init(); + if (NS_FAILED(rv)) { + NS_ERROR("Could not initialize nsDOMClassInfo"); + return rv; + } + + sRegisteredDOMNames = true; + + return NS_OK; +} + +/* static */ +bool +CreateGlobalOptions<nsGlobalWindow>::PostCreateGlobal(JSContext* aCx, + JS::Handle<JSObject*> aGlobal) +{ + nsresult rv = RegisterDOMNames(); + if (NS_FAILED(rv)) { + return Throw(aCx, rv); + } + + // Invoking the XPCWrappedNativeScope constructor automatically hooks it + // up to the compartment of aGlobal. + (void) new XPCWrappedNativeScope(aCx, aGlobal); + return true; +} + +#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) +{ + nsIPrincipal* objPrin = nsContentUtils::ObjectPrincipal(js::UncheckedUnwrap(aObject)); + return nsContentUtils::SubjectPrincipal()->Subsumes(objPrin); +} + +nsresult +UnwrapArgImpl(JS::Handle<JSObject*> src, + const nsIID &iid, + void **ppArg) +{ + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr<nsISupports> iface = xpc::UnwrapReflectorToISupports(src); + 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::IsCallerChrome()) { + return NS_ERROR_XPC_BAD_CONVERT_JS; + } + + RefPtr<nsXPCWrappedJS> wrappedJS; + nsresult rv = nsXPCWrappedJS::GetNewOrUsed(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 +UnwrapXPConnectImpl(JSContext* cx, + JS::MutableHandle<JS::Value> src, + const nsIID &iid, + void **ppArg) +{ + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_AVAILABLE; + } + + MOZ_ASSERT(src.isObject()); + // Unwrap ourselves, because we're going to want access to the unwrapped + // object. + JS::Rooted<JSObject*> obj(cx, + js::CheckedUnwrap(&src.toObject(), + /* stopAtWindowProxy = */ false)); + if (!obj) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr<nsISupports> iface = xpc::UnwrapReflectorToISupports(obj); + if (!iface) { + return NS_ERROR_XPC_BAD_CONVERT_JS; + } + + if (NS_FAILED(iface->QueryInterface(iid, ppArg))) { + return NS_ERROR_XPC_BAD_CONVERT_JS; + } + + // Now update our source to keep rooting our object. + src.setObject(*obj); + return NS_OK; +} + +nsresult +UnwrapWindowProxyImpl(JS::Handle<JSObject*> src, + nsPIDOMWindowOuter** ppArg) +{ + nsCOMPtr<nsPIDOMWindowInner> inner; + nsresult rv = UnwrapArg<nsPIDOMWindowInner>(src, getter_AddRefs(inner)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsPIDOMWindowOuter> outer = inner->GetOuterWindow(); + outer.forget(ppArg); + return NS_OK; +} + +bool +SystemGlobalResolve(JSContext* cx, JS::Handle<JSObject*> obj, + JS::Handle<jsid> id, bool* resolvedp) +{ + if (!ResolveGlobal(cx, obj, id, resolvedp)) { + return false; + } + + if (*resolvedp) { + return true; + } + + return ResolveSystemBinding(cx, obj, id, resolvedp); +} + +bool +SystemGlobalEnumerate(JSContext* cx, JS::Handle<JSObject*> obj) +{ + bool ignored = false; + return EnumerateGlobal(cx, obj) && + ResolveSystemBinding(cx, obj, JSID_VOIDHANDLE, &ignored); +} + +template<decltype(JS::NewMapObject) Method> +bool +GetMaplikeSetlikeBackingObject(JSContext* aCx, JS::Handle<JSObject*> aObj, + size_t aSlotIndex, + JS::MutableHandle<JSObject*> aBackingObj, + bool* aBackingObjCreated) +{ + JS::Rooted<JSObject*> reflector(aCx); + reflector = IsDOMObject(aObj) ? aObj : js::UncheckedUnwrap(aObj, + /* 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 compartments, + // make sure to create the backing object in reflector compartment. + { + JSAutoCompartment ac(aCx, reflector); + JS::Rooted<JSObject*> newBackingObj(aCx); + newBackingObj.set(Method(aCx)); + if (NS_WARN_IF(!newBackingObj)) { + return false; + } + js::SetReservedSlot(reflector, aSlotIndex, JS::ObjectValue(*newBackingObj)); + } + slotValue = js::GetReservedSlot(reflector, aSlotIndex); + *aBackingObjCreated = true; + } else { + *aBackingObjCreated = false; + } + if (!MaybeWrapNonDOMObjectValue(aCx, &slotValue)) { + return false; + } + aBackingObj.set(&slotValue.toObject()); + return true; +} + +bool +GetMaplikeBackingObject(JSContext* aCx, JS::Handle<JSObject*> aObj, + size_t aSlotIndex, + JS::MutableHandle<JSObject*> aBackingObj, + bool* aBackingObjCreated) +{ + return GetMaplikeSetlikeBackingObject<JS::NewMapObject>(aCx, aObj, aSlotIndex, + aBackingObj, + aBackingObjCreated); +} + +bool +GetSetlikeBackingObject(JSContext* aCx, JS::Handle<JSObject*> aObj, + size_t aSlotIndex, + JS::MutableHandle<JSObject*> aBackingObj, + bool* aBackingObjCreated) +{ + return GetMaplikeSetlikeBackingObject<JS::NewSetObject>(aCx, aObj, aSlotIndex, + aBackingObj, + aBackingObjCreated); +} + +bool +ForEachHandler(JSContext* aCx, unsigned aArgc, JS::Value* aVp) +{ + JS::CallArgs args = CallArgsFromVp(aArgc, aVp); + // Unpack callback and object from slots + JS::Rooted<JS::Value> + callbackFn(aCx, js::GetFunctionNativeReserved(&args.callee(), + FOREACH_CALLBACK_SLOT)); + JS::Rooted<JS::Value> + maplikeOrSetlikeObj(aCx, + js::GetFunctionNativeReserved(&args.callee(), + FOREACH_MAPLIKEORSETLIKEOBJ_SLOT)); + MOZ_ASSERT(aArgc == 3); + JS::AutoValueVector newArgs(aCx); + // Arguments are passed in as value, key, object. Keep value and key, replace + // object with the maplike/setlike object. + 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 js::Class* newTargetClass = js::GetObjectClass(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, + JS::MutableHandle<JSObject*> aDesiredProto) +{ + if (!aCallArgs.isConstructing()) { + aDesiredProto.set(nullptr); + return true; + } + + // 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()); + 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. + newTarget = js::CheckedUnwrap(newTarget); + if (newTarget && newTarget != originalNewTarget) { + protoID = GetProtoIdForNewtarget(newTarget); + } + } + + if (protoID != prototypes::id::_ID_Count) { + ProtoAndIfaceCache& protoAndIfaceCache = + *GetProtoAndIfaceCache(js::GetGlobalForObjectCrossCompartment(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 just fall back to using null and assume that our + // caller will then pick the right default. The actual defaulting behavior + // here still needs to be defined in the Web IDL specification. + // + // 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. + JS::Rooted<JS::Value> protoVal(aCx); + if (!JS_GetProperty(aCx, originalNewTarget, "prototype", &protoVal)) { + return false; + } + + if (!protoVal.isObject()) { + aDesiredProto.set(nullptr); + return true; + } + + aDesiredProto.set(&protoVal.toObject()); + return true; +} + +#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); + JSAutoCompartment ac(aCx, reflector); + JS::Rooted<JSObject*> reflectorProto(aCx); + bool ok = JS_GetPrototype(aCx, reflector, &reflectorProto); + MOZ_ASSERT(ok); + // aGivenProto may not be in the right compartment 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 +SetDocumentAndPageUseCounter(JSContext* aCx, JSObject* aObject, + UseCounter aUseCounter) +{ + nsGlobalWindow* win = xpc::WindowGlobalOrNull(js::UncheckedUnwrap(aObject)); + if (win && win->GetDocument()) { + win->GetDocument()->SetDocumentAndPageUseCounter(aUseCounter); + } +} + +namespace { + +// 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 +{ + nsIDocument::DeprecatedOperations mOperation; + +public: + DeprecationWarningRunnable(WorkerPrivate* aWorkerPrivate, + nsIDocument::DeprecatedOperations aOperation) + : WorkerProxyToMainThreadRunnable(aWorkerPrivate) + , mOperation(aOperation) + { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + } + +private: + void + RunOnMainThread() override + { + MOZ_ASSERT(NS_IsMainThread()); + + // Walk up to our containing page + WorkerPrivate* wp = mWorkerPrivate; + while (wp->GetParent()) { + wp = wp->GetParent(); + } + + nsPIDOMWindowInner* window = wp->GetWindow(); + if (window && window->GetExtantDoc()) { + window->GetExtantDoc()->WarnOnceAbout(mOperation); + } + } + + void + RunBackOnWorkerThread() override + {} +}; + +} // anonymous namespace + +void +DeprecationWarning(JSContext* aCx, JSObject* aObject, + nsIDocument::DeprecatedOperations aOperation) +{ + GlobalObject global(aCx, aObject); + if (global.Failed()) { + NS_ERROR("Could not create global for DeprecationWarning"); + return; + } + + if (NS_IsMainThread()) { + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global.GetAsSupports()); + if (window && window->GetExtantDoc()) { + window->GetExtantDoc()->WarnOnceAbout(aOperation); + } + + return; + } + + WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx); + if (!workerPrivate) { + return; + } + + RefPtr<DeprecationWarningRunnable> runnable = + new DeprecationWarningRunnable(workerPrivate, aOperation); + runnable->Dispatch(); +} + +namespace binding_detail { +JSObject* +UnprivilegedJunkScopeOrWorkerGlobal() +{ + if (NS_IsMainThread()) { + return xpc::UnprivilegedJunkScope(); + } + + return GetCurrentThreadWorkerGlobal(); +} +} // namespace binding_detail + +} // namespace dom +} // namespace mozilla diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h new file mode 100644 index 000000000..3e58390c9 --- /dev/null +++ b/dom/bindings/BindingUtils.h @@ -0,0 +1,3441 @@ +/* -*- 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 "jsfriendapi.h" +#include "jswrapper.h" +#include "js/Conversions.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Alignment.h" +#include "mozilla/Array.h" +#include "mozilla/Assertions.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/DeferredFinalize.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/CallbackObject.h" +#include "mozilla/dom/DOMJSClass.h" +#include "mozilla/dom/DOMJSProxyHandler.h" +#include "mozilla/dom/Exceptions.h" +#include "mozilla/dom/NonRefcountedDOMObject.h" +#include "mozilla/dom/Nullable.h" +#include "mozilla/dom/RootedDictionary.h" +#include "mozilla/SegmentedVector.h" +#include "mozilla/dom/workers/Workers.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/Likely.h" +#include "mozilla/MemoryReporting.h" +#include "nsAutoPtr.h" +#include "nsIDocument.h" +#include "nsIGlobalObject.h" +#include "nsIXPConnect.h" +#include "nsJSUtils.h" +#include "nsISupportsImpl.h" +#include "qsObjectHelper.h" +#include "xpcpublic.h" +#include "nsIVariant.h" +#include "mozilla/dom/FakeString.h" + +#include "nsWrapperCacheInlines.h" + +class nsIJSID; + +namespace mozilla { + +enum UseCounter : int16_t; + +namespace dom { +template<typename DataType> class MozMap; + +nsresult +UnwrapArgImpl(JS::Handle<JSObject*> src, const nsIID& iid, void** ppArg); + +nsresult +UnwrapWindowProxyImpl(JS::Handle<JSObject*> src, nsPIDOMWindowOuter** 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(JS::Handle<JSObject*> src, Interface** ppArg) +{ + return UnwrapArgImpl(src, NS_GET_TEMPLATE_IID(Interface), + reinterpret_cast<void**>(ppArg)); +} + +template <> +inline nsresult +UnwrapArg<nsPIDOMWindowOuter>(JS::Handle<JSObject*> src, nsPIDOMWindowOuter** ppArg) +{ + return UnwrapWindowProxyImpl(src, ppArg); +} + +nsresult +UnwrapXPConnectImpl(JSContext* cx, JS::MutableHandle<JS::Value> src, + const nsIID& iid, void** ppArg); + +/* + * Convert a jsval being used as a Web IDL interface implementation to an XPCOM + * pointer; this is only used for Web IDL interfaces that specify + * hasXPConnectImpls. This is not the same as UnwrapArg because caller _can_ + * assume that if unwrapping succeeds "val" will be updated so it's rooting the + * XPCOM pointer. Also, UnwrapXPConnect doesn't need to worry about doing + * XPCWrappedJS things. + * + * val must be an ObjectValue. + */ +template<class Interface> +inline nsresult +UnwrapXPConnect(JSContext* cx, JS::MutableHandle<JS::Value> val, + Interface** ppThis) +{ + return UnwrapXPConnectImpl(cx, val, NS_GET_TEMPLATE_IID(Interface), + reinterpret_cast<void**>(ppThis)); +} + +bool +ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs, + bool aSecurityError, const char* aInterfaceName); + +bool +ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs, + bool aSecurityError, prototypes::ID aProtoId); + +// Returns true if the JSClass is used for DOM objects. +inline bool +IsDOMClass(const JSClass* clasp) +{ + return clasp->flags & JSCLASS_IS_DOMJSCLASS; +} + +inline bool +IsDOMClass(const js::Class* clasp) +{ + return IsDOMClass(Jsvalify(clasp)); +} + +// Return true if the JSClass is used for non-proxy DOM objects. +inline bool +IsNonProxyDOMClass(const js::Class* clasp) +{ + return IsDOMClass(clasp) && !clasp->isProxy(); +} + +inline bool +IsNonProxyDOMClass(const JSClass* clasp) +{ + return IsNonProxyDOMClass(js::Valueify(clasp)); +} + +// 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; +} + +inline bool +IsDOMIfaceAndProtoClass(const js::Class* clasp) +{ + return IsDOMIfaceAndProtoClass(Jsvalify(clasp)); +} + +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::GetObjectClass(obj)), + "Don't pass non-DOM objects to this function"); + + JS::Value val = js::GetReservedOrProxyPrivateSlot(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::GetObjectClass(obj)), + "Don't pass non-DOM objects to this function"); + + JS::Value val = js::GetReservedOrProxyPrivateSlot(obj, DOM_OBJECT_SLOT); + if (val.isUndefined()) { + return nullptr; + } + return static_cast<T*>(val.toPrivate()); +} + +inline const DOMJSClass* +GetDOMClass(const js::Class* clasp) +{ + return IsDOMClass(clasp) ? DOMJSClass::FromJSClass(clasp) : nullptr; +} + +inline const DOMJSClass* +GetDOMClass(JSObject* obj) +{ + return GetDOMClass(js::GetObjectClass(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::GetObjectClass(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*. +#define UNWRAP_OBJECT(Interface, obj, value) \ + mozilla::dom::UnwrapObject<mozilla::dom::prototypes::id::Interface, \ + mozilla::dom::Interface##Binding::NativeType>(obj, value) + +// 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. +namespace binding_detail { +template <class T, bool mayBeWrapper, typename U, typename V> +MOZ_ALWAYS_INLINE nsresult +UnwrapObjectInternal(V& obj, U& value, prototypes::ID protoID, + uint32_t protoDepth) +{ + /* 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)) { + /* Not a DOM object, not a wrapper, just bail */ + return NS_ERROR_XPC_BAD_CONVERT_JS; + } + + JSObject* unwrappedObj = + js::CheckedUnwrap(obj, /* stopAtWindowProxy = */ false); + if (!unwrappedObj) { + return NS_ERROR_XPC_SECURITY_MANAGER_VETO; + } + 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; + nsresult rv = UnwrapObjectInternal<T, false>(unwrappedObj, tempValue, + protoID, protoDepth); + if (NS_SUCCEEDED(rv)) { + // 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); + 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> +MOZ_ALWAYS_INLINE nsresult +UnwrapObject(JS::MutableHandle<JSObject*> obj, U& value) +{ + binding_detail::MutableObjectHandleWrapper wrapper(obj); + return binding_detail::UnwrapObjectInternal<T, true>( + wrapper, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth); +} + +template<prototypes::ID PrototypeID, class T, typename U> +MOZ_ALWAYS_INLINE nsresult +UnwrapObject(JS::MutableHandle<JS::Value> obj, U& value) +{ + MOZ_ASSERT(obj.isObject()); + binding_detail::MutableValueHandleWrapper wrapper(obj); + return binding_detail::UnwrapObjectInternal<T, true>( + wrapper, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth); +} + +// UnwrapObject overloads that ensure we have a strong ref to keep it alive. +template<prototypes::ID PrototypeID, class T, typename U> +MOZ_ALWAYS_INLINE nsresult +UnwrapObject(JSObject* obj, RefPtr<U>& value) +{ + return binding_detail::UnwrapObjectInternal<T, true>( + obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth); +} + +template<prototypes::ID PrototypeID, class T, typename U> +MOZ_ALWAYS_INLINE nsresult +UnwrapObject(JSObject* obj, nsCOMPtr<U>& value) +{ + return binding_detail::UnwrapObjectInternal<T, true>( + obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth); +} + +template<prototypes::ID PrototypeID, class T, typename U> +MOZ_ALWAYS_INLINE nsresult +UnwrapObject(JSObject* obj, OwningNonNull<U>& value) +{ + return binding_detail::UnwrapObjectInternal<T, true>( + obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth); +} + +// An UnwrapObject overload that just calls one of the JSObject* ones. +template<prototypes::ID PrototypeID, class T, typename U> +MOZ_ALWAYS_INLINE nsresult +UnwrapObject(JS::Handle<JS::Value> obj, U& value) +{ + MOZ_ASSERT(obj.isObject()); + return UnwrapObject<PrototypeID, T>(&obj.toObject(), value); +} + +template<prototypes::ID PrototypeID, class T> +MOZ_ALWAYS_INLINE bool +IsInstanceOf(JSObject* obj) +{ + void* ignored; + nsresult unwrapped = binding_detail::UnwrapObjectInternal<T, true>( + obj, ignored, PrototypeID, PrototypeTraits<PrototypeID>::Depth); + 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); +} + +inline bool +IsNotDateOrRegExp(JSContext* cx, JS::Handle<JSObject*> obj, + bool* notDateOrRegExp) +{ + MOZ_ASSERT(obj); + + js::ESClass cls; + if (!js::GetBuiltinClass(cx, obj, &cls)) { + return false; + } + + *notDateOrRegExp = cls != js::ESClass::Date && cls != js::ESClass::RegExp; + return true; +} + +MOZ_ALWAYS_INLINE bool +IsObjectValueConvertibleToDictionary(JSContext* cx, + JS::Handle<JS::Value> objVal, + bool* convertible) +{ + JS::Rooted<JSObject*> obj(cx, &objVal.toObject()); + return IsNotDateOrRegExp(cx, obj, convertible); +} + +MOZ_ALWAYS_INLINE bool +IsConvertibleToDictionary(JSContext* cx, JS::Handle<JS::Value> val, + bool* convertible) +{ + if (val.isNullOrUndefined()) { + *convertible = true; + return true; + } + if (!val.isObject()) { + *convertible = false; + return true; + } + return IsObjectValueConvertibleToDictionary(cx, val, convertible); +} + +MOZ_ALWAYS_INLINE bool +IsConvertibleToCallbackInterface(JSContext* cx, JS::Handle<JSObject*> obj, + bool* convertible) +{ + return IsNotDateOrRegExp(cx, obj, convertible); +} + +// 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: + JSObject* EntrySlotIfExists(size_t i) { + return (*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, 0, sizeof(mPages)); + } + + ~PageTableCache() { + for (size_t i = 0; i < ArrayLength(mPages); ++i) { + delete mPages[i]; + } + } + + JSObject* EntrySlotIfExists(size_t i) { + MOZ_ASSERT(i < kProtoAndIfaceCacheCount); + size_t pageIndex = i / kPageSize; + size_t leafIndex = i % kPageSize; + Page* p = mPages[pageIndex]; + if (!p) { + return nullptr; + } + return (*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 the JSObject stored in slot i, if that slot exists. If + // the slot does not exist, return null. + JSObject* EntrySlotIfExists(size_t i) { + FORWARD_OPERATION(EntrySlotIfExists, (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::GetObjectClass(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), ok(false) + {} + + void onChild(const JS::GCCellPtr&) override { + // We don't do anything here, we only want to verify that + // TraceProtoAndIfaceCache was called. + } + + TracerKind getTracerKind() const override { return TracerKind::VerifyTraceProtoAndIface; } +}; +#endif + +inline void +TraceProtoAndIfaceCache(JSTracer* trc, JSObject* obj) +{ + MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL); + +#ifdef DEBUG + if (trc->isCallbackTracer() && + (trc->asCallbackTracer()->getTracerKind() == + JS::CallbackTracer::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::GetObjectClass(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 NamedConstructor +{ + const char* mName; + const JSNativeHolder mHolder; + unsigned mNargs; +}; + +/* + * 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 + * 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. + * 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] or + * [PrimaryGlobal] interface, and hence shouldn't define properties on + * the prototype object. + * + * 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. + */ +void +CreateInterfaceObjects(JSContext* cx, JS::Handle<JSObject*> global, + JS::Handle<JSObject*> protoProto, + const js::Class* protoClass, JS::Heap<JSObject*>* protoCache, + JS::Handle<JSObject*> interfaceProto, + const js::Class* constructorClass, + unsigned ctorNargs, const NamedConstructor* namedConstructors, + JS::Heap<JSObject*>* constructorCache, + const NativeProperties* regularProperties, + const NativeProperties* chromeOnlyProperties, + const char* name, bool defineOnGlobal, + const char* const* unscopableNames, + bool isGlobal); + +/** + * Define the properties (regular and chrome-only) on obj. + * + * obj the object to instal 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 unforgeable methods on an object. + */ +bool +DefineUnforgeableMethods(JSContext* cx, JS::Handle<JSObject*> obj, + const Prefable<const JSFunctionSpec>* props); + +/* + * Define the unforgeable attributes on an object. + */ +bool +DefineUnforgeableAttributes(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 IsBaseOf 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(!IsBaseOf<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=IsBaseOf<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 + +MOZ_ALWAYS_INLINE bool +CouldBeDOMBinding(void*) +{ + return true; +} + +MOZ_ALWAYS_INLINE bool +CouldBeDOMBinding(nsWrapperCache* aCache) +{ + return aCache->IsDOMBinding(); +} + +inline bool +TryToOuterize(JS::MutableHandle<JS::Value> rval) +{ + if (js::IsWindow(&rval.toObject())) { + JSObject* obj = js::ToWindowProxyIfWindow(&rval.toObject()); + MOZ_ASSERT(obj); + rval.set(JS::ObjectValue(*obj)); + } + + 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.isObject()); + + // Cross-compartment always requires wrapping. + JSObject* obj = &rval.toObject(); + if (js::GetObjectCompartment(obj) != js::GetContextCompartment(cx)) { + return JS_WrapValue(cx, rval); + } + + // We're same-compartment, but even then we might need to wrap + // objects specially. Check for that. + if (IsDOMObject(obj)) { + return TryToOuterize(rval); + } + + // It's not a WebIDL object, so it's OK to just leave it as-is: only WebIDL + // objects (specifically only windows) require outerization. + return true; +} + +// 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 or XPConnect objects +MOZ_ALWAYS_INLINE +bool +MaybeWrapNonDOMObjectValue(JSContext* cx, JS::MutableHandle<JS::Value> rval) +{ + MOZ_ASSERT(rval.isObject()); + MOZ_ASSERT(!GetDOMClass(&rval.toObject())); + MOZ_ASSERT(!(js::GetObjectClass(&rval.toObject())->flags & + JSCLASS_PRIVATE_IS_NSISUPPORTS)); + + JSObject* obj = &rval.toObject(); + if (js::GetObjectCompartment(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.isString()) { + return MaybeWrapStringValue(cx, rval); + } + + if (!rval.isObject()) { + return true; + } + + return MaybeWrapObjectValue(cx, rval); +} + +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 = + IsBaseOf<nsGlobalWindow, T>::value || IsSame<EventTarget, T>::value; +}; + +#ifdef DEBUG +template<typename T, bool isISupports=IsBaseOf<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?"); + + bool wasPreservingWrapper = wrapperCacheFromQI->PreservingWrapper(); + wrapperCacheFromQI->SetPreservingWrapper(true); + wrapperCacheFromQI->CheckCCWrapperTraversal(ccISupports, participant); + wrapperCacheFromQI->SetPreservingWrapper(wasPreservingWrapper); + } +}; + +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); + // We can get rid of this when we remove support for hasXPConnectImpls. + bool couldBeDOMBinding = CouldBeDOMBinding(value); + 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 { + // Inline this here while we have non-dom objects in wrapper caches. + if (!couldBeDOMBinding) { + return false; + } + + 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 (IsBaseOf<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, (IsBaseOf<nsISupports, T>::value)); + MOZ_ASSERT(CheckWrapperCacheCast<T>::Check()); + } +#endif + + rval.set(JS::ObjectValue(*obj)); + + bool sameCompartment = + js::GetObjectCompartment(obj) == js::GetContextCompartment(cx); + if (sameCompartment && couldBeDOMBinding) { + return TypeNeedsOuterization<T>::value ? TryToOuterize(rval) : true; + } + + if (wrapBehavior == eDontWrapIntoContextCompartment) { + if (TypeNeedsOuterization<T>::value) { + JSAutoCompartment ac(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); +} + +// 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 scope. +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 compartment of the underlying object of "scope" + JS::Rooted<JSObject*> obj(cx); + { + // scope for the JSAutoCompartment so that we restore the compartment + // before we call JS_WrapValue. + Maybe<JSAutoCompartment> ac; + // 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)) { + scope = js::CheckedUnwrap(scope, /* stopAtWindowProxy = */ false); + if (!scope) + return false; + ac.emplace(cx, scope); + if (!JS_WrapObject(cx, &proto)) { + return false; + } + } + + MOZ_ASSERT(js::IsObjectInContextCompartment(scope, cx)); + if (!value->WrapObject(cx, proto, &obj)) { + return false; + } + } + + // We can end up here in all sorts of compartments, per above. 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 owned object using WebIDL bindings. "value" must implement a +// WrapObject() method taking a JSContext, a scope, and a boolean outparam that +// is true if the JSObject took ownership +template <class T> +inline bool +WrapNewBindingNonWrapperCachedObject(JSContext* cx, + JS::Handle<JSObject*> scopeArg, + nsAutoPtr<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) { + NS_RUNTIMEABORT("Don't try to wrap null objects"); + } + // We try to wrap in the compartment of the underlying object of "scope" + JS::Rooted<JSObject*> obj(cx); + { + // scope for the JSAutoCompartment so that we restore the compartment + // before we call JS_WrapValue. + Maybe<JSAutoCompartment> ac; + // 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)) { + scope = js::CheckedUnwrap(scope, /* stopAtWindowProxy = */ false); + if (!scope) + return false; + ac.emplace(cx, scope); + if (!JS_WrapObject(cx, &proto)) { + return false; + } + } + + MOZ_ASSERT(js::IsObjectInContextCompartment(scope, cx)); + if (!value->WrapObject(cx, proto, &obj)) { + return false; + } + + value.forget(); + } + + // We can end up here in all sorts of compartments, per above. Make + // sure to JS_WrapValue! + rval.set(JS::ObjectValue(*obj)); + return MaybeWrapObjectValue(cx, rval); +} + +// Helper for smart pointers (nsRefPtr/nsCOMPtr). +template <template <typename> class SmartPtr, typename T, + typename U=typename EnableIf<IsRefcounted<T>::value, T>::Type, + typename V=typename EnableIf<IsSmartPtr<SmartPtr<T>>::value, T>::Type> +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=typename EnableIf<!IsSmartPtr<T>::value, T>::Type> +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); +} + +// 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 +NativeInterface2JSObjectAndThrowIfFailed(JSContext* aCx, + JS::Handle<JSObject*> aScope, + JS::MutableHandle<JS::Value> aRetval, + xpcObjectHelper& aHelper, + const nsIID* aIID, + bool aAllowNativeWrapper); + +/** + * A method to handle new-binding wrap failure, by possibly falling back to + * wrapping as a non-new-binding object. + */ +template <class T> +MOZ_ALWAYS_INLINE bool +HandleNewBindingWrappingFailure(JSContext* cx, JS::Handle<JSObject*> scope, + T* value, JS::MutableHandle<JS::Value> rval) +{ + if (JS_IsExceptionPending(cx)) { + return false; + } + + qsObjectHelper helper(value, GetWrapperCache(value)); + return NativeInterface2JSObjectAndThrowIfFailed(cx, scope, rval, + helper, nullptr, true); +} + +// Helper for calling HandleNewBindingWrappingFailure with smart pointers +// (nsAutoPtr/nsRefPtr/nsCOMPtr) or references. + +template <class T, bool isSmartPtr=IsSmartPtr<T>::value> +struct HandleNewBindingWrappingFailureHelper +{ + static inline bool Wrap(JSContext* cx, JS::Handle<JSObject*> scope, + const T& value, JS::MutableHandle<JS::Value> rval) + { + return HandleNewBindingWrappingFailure(cx, scope, value.get(), rval); + } +}; + +template <class T> +struct HandleNewBindingWrappingFailureHelper<T, false> +{ + static inline bool Wrap(JSContext* cx, JS::Handle<JSObject*> scope, T& value, + JS::MutableHandle<JS::Value> rval) + { + return HandleNewBindingWrappingFailure(cx, scope, &value, rval); + } +}; + +template<class T> +inline bool +HandleNewBindingWrappingFailure(JSContext* cx, JS::Handle<JSObject*> scope, + T& value, JS::MutableHandle<JS::Value> rval) +{ + return HandleNewBindingWrappingFailureHelper<T>::Wrap(cx, scope, value, rval); +} + +template<bool Fatal> +inline bool +EnumValueNotFound(JSContext* cx, JS::HandleString str, const char* type, + const char* sourceDescription); + +template<> +inline bool +EnumValueNotFound<false>(JSContext* cx, JS::HandleString str, const char* type, + const char* sourceDescription) +{ + // TODO: Log a warning to the console. + return true; +} + +template<> +inline bool +EnumValueNotFound<true>(JSContext* cx, JS::HandleString str, const char* type, + const char* sourceDescription) +{ + JSAutoByteString deflated; + if (!deflated.encodeUtf8(cx, str)) { + return false; + } + return ThrowErrorMessage(cx, MSG_INVALID_ENUM_VALUE, sourceDescription, + deflated.ptr(), 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(JSContext* 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::RootedString 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 bool +GetUseXBLScope(T* aParentObject) +{ + return false; +} + +inline bool +GetUseXBLScope(const ParentObject& aParentObject) +{ + return aParentObject.mUseXBLScope; +} + +template<class T> +inline void +ClearWrapper(T* p, nsWrapperCache* cache) +{ + cache->ClearWrapper(); +} + +template<class T> +inline void +ClearWrapper(T* p, void*) +{ + nsWrapperCache* cache; + CallQueryInterface(p, &cache); + ClearWrapper(p, cache); +} + +template<class T> +inline void +UpdateWrapper(T* p, nsWrapperCache* cache, JSObject* obj, const JSObject* old) +{ + JS::AutoAssertGCCallback inCallback(obj); + cache->UpdateWrapper(obj, old); +} + +template<class T> +inline void +UpdateWrapper(T* p, void*, JSObject* obj, const JSObject* old) +{ + JS::AutoAssertGCCallback inCallback(obj); + 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(JSObject* obj); + +// Can only be called with a DOM JSClass. +bool +InstanceClassHasProtoAtDepth(const js::Class* 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; + qsObjectHelper helper(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; +} + +// 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) +{ + qsObjectHelper helper(ToSupports(p), cache); + JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx)); + JS::Rooted<JS::Value> v(cx); + return XPCOMObjectToJsval(cx, scope, helper, nullptr, false, &v) ? + v.toObjectOrNull() : + nullptr; +} + + +// Fallback for when our parent is not a WebIDL binding object. +template<typename T, bool isISupports=IsBaseOf<nsISupports, T>::value> +struct WrapNativeFallback +{ + static inline JSObject* Wrap(JSContext* cx, T* parent, nsWrapperCache* cache) + { + return nullptr; + } +}; + +// Fallback for when our parent is not a WebIDL binding object but _is_ an +// nsISupports object. +template<typename T > +struct WrapNativeFallback<T, true > +{ + static inline JSObject* Wrap(JSContext* cx, T* parent, nsWrapperCache* cache) + { + return WrapNativeISupports(cx, parent, cache); + } +}; + +// Wrapping of our native parent, for cases when it's a WebIDL object (though +// possibly preffed off). +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. + MOZ_ASSERT(!JS::ObjectIsMarkedGray(obj)); + return obj; + } + + // Inline this here while we have non-dom objects in wrapper caches. + if (!CouldBeDOMBinding(parent)) { + // WrapNativeFallback never returns a gray thing. + obj = WrapNativeFallback<T>::Wrap(cx, parent, cache); + MOZ_ASSERT_IF(obj, !JS::ObjectIsMarkedGray(obj)); + } else { + // WrapObject never returns a gray thing. + obj = parent->WrapObject(cx, nullptr); + MOZ_ASSERT_IF(obj, !JS::ObjectIsMarkedGray(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 + MOZ_ASSERT(!JS::ObjectIsMarkedGray(obj)); + return obj; + } + + obj = WrapNativeISupports(cx, parent, cache); + MOZ_ASSERT_IF(obj, !JS::ObjectIsMarkedGray(obj)); + return obj; + } +}; + +// Finding the associated global for an object. +template<typename T> +static inline JSObject* +FindAssociatedGlobal(JSContext* cx, T* p, nsWrapperCache* cache, + bool useXBLScope = false) +{ + if (!p) { + return JS::CurrentGlobalOrNull(cx); + } + + JSObject* obj = WrapNativeHelper<T>::Wrap(cx, p, cache); + if (!obj) { + return nullptr; + } + MOZ_ASSERT(!JS::ObjectIsMarkedGray(obj)); + + obj = js::GetGlobalForObjectCrossCompartment(obj); + + if (!useXBLScope) { + return obj; + } + + // If useXBLScope is true, it means that the canonical reflector for this + // native object should live in the content XBL scope. Note that we never put + // anonymous content inside an add-on scope. + if (xpc::IsInContentXBLScope(obj)) { + return obj; + } + JS::Rooted<JSObject*> rootedObj(cx, obj); + JSObject* xblScope = xpc::GetXBLScope(cx, rootedObj); + MOZ_ASSERT_IF(xblScope, JS_IsGlobalObject(xblScope)); + MOZ_ASSERT_IF(xblScope, !JS::ObjectIsMarkedGray(xblScope)); + return xblScope; +} + +// 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), GetUseXBLScope(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) { + return nullptr; + } + + MOZ_ASSERT(JS_IsGlobalObject(global)); + // This object could be gray if the nsIGlobalObject is the only thing keeping + // it alive. + JS::ExposeObjectToActiveJS(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 +// (nsAutoPtr/nsRefPtr/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 +// (nsAutoPtr/nsRefPtr/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(T* aObj) +{ + return aObj->Callback(); +} + +// 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(const T& aObj) + { + return GetCallbackFromCallbackObject(aObj.get()); + } +}; + +template <class T> +struct GetCallbackFromCallbackObjectHelper<T, false> +{ + static inline JSObject* Get(T& aObj) + { + return GetCallbackFromCallbackObject(&aObj); + } +}; + +template<class T> +inline JSObject* +GetCallbackFromCallbackObject(T& aObj) +{ + return GetCallbackFromCallbackObjectHelper<T>::Get(aObj); +} + +static inline bool +AtomizeAndPinJSString(JSContext* cx, jsid& id, const char* chars) +{ + if (JSString *str = ::JS_AtomizeAndPinString(cx, chars)) { + id = INTERNED_STRING_TO_JSID(cx, str); + return true; + } + return false; +} + +// Spec needs a name property +template <typename Spec> +static bool +InitIds(JSContext* cx, const Prefable<Spec>* prefableSpecs, jsid* ids) +{ + MOZ_ASSERT(prefableSpecs); + MOZ_ASSERT(prefableSpecs->specs); + 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. + Spec* spec = prefableSpecs->specs; + do { + if (!JS::PropertySpecNameToPermanentId(cx, spec->name, ids)) { + return false; + } + } while (++ids, (++spec)->name); + + // We ran out of ids for that pref. Put a JSID_VOID in on the id + // corresponding to the list terminator for the pref. + *ids = JSID_VOID; + ++ids; + } while ((++prefableSpecs)->specs); + + return true; +} + +bool +QueryInterface(JSContext* cx, unsigned argc, JS::Value* vp); + +template <class T> +struct +WantsQueryInterface +{ + static_assert(IsBaseOf<nsISupports, T>::value, + "QueryInterface can't work without an nsISupports."); + static bool Enabled(JSContext* aCx, JSObject* aGlobal) + { + return NS_IsMainThread() && IsChromeOrXBL(aCx, aGlobal); + } +}; + +void +GetInterfaceImpl(JSContext* aCx, nsIInterfaceRequestor* aRequestor, + nsWrapperCache* aCache, nsIJSID* aIID, + JS::MutableHandle<JS::Value> aRetval, ErrorResult& aError); + +template<class T> +void +GetInterface(JSContext* aCx, T* aThis, nsIJSID* aIID, + JS::MutableHandle<JS::Value> aRetval, ErrorResult& aError) +{ + GetInterfaceImpl(aCx, aThis, aThis, aIID, aRetval, aError); +} + +bool +UnforgeableValueOf(JSContext* cx, unsigned argc, JS::Value* vp); + +bool +ThrowingConstructor(JSContext* cx, unsigned argc, JS::Value* vp); + +bool +ThrowConstructorWithoutNew(JSContext* cx, const char* name); + +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::AutoIdVector& props); + +namespace binding_detail { + +class FastErrorResult : + public mozilla::binding_danger::TErrorResult< + mozilla::binding_danger::JustAssertCleanupPolicy> +{ +}; + +} // namespace binding_detail + +enum StringificationBehavior { + eStringify, + eEmpty, + eNull +}; + +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); +} + +void +NormalizeUSVString(JSContext* aCx, nsAString& aString); + +void +NormalizeUSVString(JSContext* aCx, binding_detail::FakeString& aString); + +template<typename T> +inline bool +ConvertIdToString(JSContext* cx, JS::HandleId id, T& result, bool& isSymbol) +{ + if (MOZ_LIKELY(JSID_IS_STRING(id))) { + if (!AssignJSString(cx, result, JSID_TO_STRING(id))) { + return false; + } + } else if (JSID_IS_SYMBOL(id)) { + isSymbol = true; + return true; + } else { + JS::RootedValue nameVal(cx, js::IdToValue(id)); + if (!ConvertJSValueToString(cx, nameVal, eStringify, eStringify, result)) { + return false; + } + } + isSymbol = false; + return true; +} + +bool +ConvertJSValueToByteString(JSContext* cx, JS::Handle<JS::Value> v, + bool nullable, nsACString& result); + +template<typename T> +void DoTraceSequence(JSTracer* trc, FallibleTArray<T>& seq); +template<typename T> +void DoTraceSequence(JSTracer* trc, InfallibleTArray<T>& seq); + +// Class used to trace sequences, with specializations for various +// sequence types. +template<typename T, + bool isDictionary=IsBaseOf<DictionaryBase, T>::value, + bool isTypedArray=IsBaseOf<AllTypedArraysBase, T>::value, + bool isOwningUnion=IsBaseOf<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::UnsafeTraceRoot(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::UnsafeTraceRoot(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 T> +static void +TraceMozMapValue(T* aValue, void* aClosure) +{ + JSTracer* trc = static_cast<JSTracer*>(aClosure); + // Act like it's a one-element sequence to leverage all that infrastructure. + SequenceTracer<T>::TraceSequence(trc, aValue, aValue + 1); +} + +template<typename T> +void TraceMozMap(JSTracer* trc, MozMap<T>& map) +{ + map.EnumerateValues(TraceMozMapValue<T>, trc); +} + +// sequence<MozMap> +template<typename T> +class SequenceTracer<MozMap<T>, false, false, false> +{ + explicit SequenceTracer() = delete; // Should never be instantiated + +public: + static void TraceSequence(JSTracer* trc, MozMap<T>* seqp, MozMap<T>* end) { + for (; seqp != end; ++seqp) { + seqp->EnumerateValues(TraceMozMapValue<T>, trc); + } + } +}; + +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, InfallibleTArray<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: + SequenceRooter(JSContext *aCx, FallibleTArray<T>* aSequence + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : JS::CustomAutoRooter(aCx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT), + mFallibleArray(aSequence), + mSequenceType(eFallibleArray) + { + } + + SequenceRooter(JSContext *aCx, InfallibleTArray<T>* aSequence + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : JS::CustomAutoRooter(aCx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT), + mInfallibleArray(aSequence), + mSequenceType(eInfallibleArray) + { + } + + SequenceRooter(JSContext *aCx, Nullable<nsTArray<T> >* aSequence + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : JS::CustomAutoRooter(aCx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT), + 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 { + InfallibleTArray<T>* mInfallibleArray; + FallibleTArray<T>* mFallibleArray; + Nullable<nsTArray<T> >* mNullableArray; + }; + + SequenceType mSequenceType; +}; + +// Rooter class for MozMap; this is what we mostly use in the codegen. +template<typename T> +class MOZ_RAII MozMapRooter final : private JS::CustomAutoRooter +{ +public: + MozMapRooter(JSContext *aCx, MozMap<T>* aMozMap + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : JS::CustomAutoRooter(aCx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT), + mMozMap(aMozMap), + mMozMapType(eMozMap) + { + } + + MozMapRooter(JSContext *aCx, Nullable<MozMap<T>>* aMozMap + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : JS::CustomAutoRooter(aCx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT), + mNullableMozMap(aMozMap), + mMozMapType(eNullableMozMap) + { + } + +private: + enum MozMapType { + eMozMap, + eNullableMozMap + }; + + virtual void trace(JSTracer *trc) override + { + if (mMozMapType == eMozMap) { + TraceMozMap(trc, *mMozMap); + } else { + MOZ_ASSERT(mMozMapType == eNullableMozMap); + if (!mNullableMozMap->IsNull()) { + TraceMozMap(trc, mNullableMozMap->Value()); + } + } + } + + union { + MozMap<T>* mMozMap; + Nullable<MozMap<T>>* mNullableMozMap; + }; + + MozMapType mMozMapType; +}; + +template<typename T> +class MOZ_RAII RootedUnion : public T, + private JS::CustomAutoRooter +{ +public: + explicit RootedUnion(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : + T(), + JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT) + { + } + + virtual void trace(JSTracer *trc) override + { + this->TraceUnion(trc); + } +}; + +template<typename T> +class MOZ_STACK_CLASS NullableRootedUnion : public Nullable<T>, + private JS::CustomAutoRooter +{ +public: + explicit NullableRootedUnion(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : + Nullable<T>(), + JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT) + { + } + + virtual void trace(JSTracer *trc) override + { + if (!this->IsNull()) { + this->Value().TraceUnion(trc); + } + } +}; + +inline bool +IdEquals(jsid id, const char* string) +{ + return JSID_IS_STRING(id) && + JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(id), string); +} + +inline bool +AddStringToIDVector(JSContext* cx, JS::AutoIdVector& 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<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 *defined to true). + * defined will be set to true if a property was set as a result of this call. + */ +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 *defined); + +/** + * 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::AutoIdVector& 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::GetGlobalForObjectCrossCompartment(obj)); + { + JSAutoCompartment ac(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(cx, obj)) { + MOZ_ASSERT(JS_IsNativeFunction(obj, Constructor)); + protop.set(JS::GetRealmFunctionPrototype(cx)); + } else { + const js::Class* clasp = js::GetObjectClass(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); + +/** + * 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 js::ClassOps sBoringInterfaceObjectClassClassOps; + +extern const js::ObjectOps sInterfaceObjectClassObjectOps; + +inline bool +UseDOMXray(JSObject* obj) +{ + const js::Class* clasp = js::GetObjectClass(obj); + return IsDOMClass(clasp) || + JS_IsNativeFunction(obj, Constructor) || + IsDOMIfaceAndProtoClass(clasp); +} + +#ifdef DEBUG +inline bool +HasConstructor(JSObject* obj) +{ + return JS_IsNativeFunction(obj, Constructor) || + js::GetObjectClass(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; +} + +inline +void NonNullHelper(NonNull<binding_detail::FakeString>& 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. +} + +inline +void NonNullHelper(const NonNull<binding_detail::FakeString>& 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. +} + +inline +void NonNullHelper(binding_detail::FakeString& 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. +} + +MOZ_ALWAYS_INLINE +const nsAString& NonNullHelper(const binding_detail::FakeString& aArg) +{ + return aArg; +} + +// Reparent the wrapper of aObj to whatever its native now thinks its +// parent should be. +nsresult +ReparentWrapper(JSContext* aCx, JS::Handle<JSObject*> aObj); + +/** + * 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); + +// 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(JSContext* cx, JS::Handle<JSObject*> obj, + nsIGlobalObject** global); + +void +ConstructJSImplementation(const char* aContractId, + nsIGlobalObject* aGlobal, + JS::MutableHandle<JSObject*> aObject, + ErrorResult& aRv); + +already_AddRefed<nsIGlobalObject> +ConstructJSImplementation(const char* aContractId, + const GlobalObject& aGlobal, + JS::MutableHandle<JSObject*> aObject, + ErrorResult& 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); +} + +template<class T, bool isISupports=IsBaseOf<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=IsBaseOf<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); +} + +// 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 ForgetObject(). +// The BindingJSObjectCreator object will root the JSObject until ForgetObject() +// 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 ForgetObject() is called. If +// the BindingJSObjectCreator object is destroyed and ForgetObject() 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::SetReservedOrProxyPrivateSlot(mReflector, DOM_OBJECT_SLOT, + JS::UndefinedValue()); + } + } + + void + CreateProxyObject(JSContext* aCx, const js::Class* aClass, + const DOMProxyHandler* aHandler, + JS::Handle<JSObject*> aProto, T* aNative, + JS::MutableHandle<JSObject*> aReflector) + { + js::ProxyOptions options; + options.setClass(aClass); + JS::Rooted<JS::Value> proxyPrivateVal(aCx, JS::PrivateValue(aNative)); + aReflector.set(js::NewProxyObject(aCx, aHandler, proxyPrivateVal, aProto, + options)); + if (aReflector) { + mNative = aNative; + mReflector = aReflector; + } + } + + 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; + } + } + + void + InitializationSucceeded() + { + void* dummy; + mNative.forget(&dummy); + mReflector = nullptr; + } + +private: + struct OwnedNative + { + // Make sure the native objects inherit from NonRefcountedDOMObject so + // that we log their ctor and dtor. + static_assert(IsBaseOf<NonRefcountedDOMObject, T>::value, + "Non-refcounted objects with DOM bindings should inherit " + "from NonRefcountedDOMObject."); + + OwnedNative& + operator=(T* 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(void**) + { + } + }; + + JS::Rooted<JSObject*> mReflector; + typename Conditional<IsRefcounted<T>::value, RefPtr<T>, OwnedNative>::Type mNative; +}; + +template<class T> +struct DeferredFinalizerImpl +{ + typedef typename Conditional<IsSame<T, nsISupports>::value, + nsCOMPtr<T>, + typename Conditional<IsRefcounted<T>::value, + RefPtr<T>, + nsAutoPtr<T>>::Type>::Type SmartPtr; + typedef SegmentedVector<SmartPtr> SmartPtrArray; + + static_assert(IsSame<T, nsISupports>::value || !IsBaseOf<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<nsAutoPtr<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=IsBaseOf<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 or null if it +// doesn't. This also returns null for classes that don't inherit from +// nsISupports (QI should be used to get the participant for those). +template<class T, bool isISupports=IsBaseOf<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 firt 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(JSFreeOp* aFop, 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); + +template <class T> +struct CreateGlobalOptions +{ + static constexpr ProtoAndIfaceCache::Kind ProtoAndIfaceCacheKind = + ProtoAndIfaceCache::NonWindowLike; + 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; + } +}; + +template <> +struct CreateGlobalOptions<nsGlobalWindow> +{ + static constexpr ProtoAndIfaceCache::Kind ProtoAndIfaceCacheKind = + ProtoAndIfaceCache::WindowLike; + static void TraceGlobal(JSTracer* aTrc, JSObject* aObj); + static bool PostCreateGlobal(JSContext* aCx, JS::Handle<JSObject*> aGlobal); +}; + +nsresult +RegisterDOMNames(); + +// 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::CompartmentOptions& aOptions, + JSPrincipals* aPrincipal, bool aInitStandardClasses, + JS::MutableHandle<JSObject*> aGlobal) +{ + aOptions.creationOptions().setTrace(CreateGlobalOptions<T>::TraceGlobal); + if (xpc::SharedMemoryEnabled()) { + aOptions.creationOptions().setSharedMemoryAndAtomicsEnabled(true); + } + + aGlobal.set(JS_NewGlobalObject(aCx, aClass, aPrincipal, + JS::DontFireOnNewGlobalHook, aOptions)); + if (!aGlobal) { + NS_WARNING("Failed to create global"); + return false; + } + + JSAutoCompartment ac(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_InitStandardClasses(aCx, aGlobal)) { + NS_WARNING("Failed to init standard classes"); + return false; + } + + JS::Handle<JSObject*> proto = GetProto(aCx); + if (!proto || !JS_SplicePrototype(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; +} + +/* + * 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: + PinnedStringId() : id(JSID_VOID) {} + + bool init(JSContext *cx, const char *string) { + JSString* str = JS_AtomizeAndPinString(cx, string); + if (!str) + return false; + id = INTERNED_STRING_TO_JSID(cx, str); + return true; + } + + operator const jsid& () { + return id; + } + + operator JS::Handle<jsid> () { + /* This is safe because we have pinned the string. */ + return JS::Handle<jsid>::fromMarkedLocation(&id); + } +}; + +bool +GenericBindingGetter(JSContext* cx, unsigned argc, JS::Value* vp); + +bool +GenericBindingSetter(JSContext* cx, unsigned argc, JS::Value* vp); + +bool +GenericBindingMethod(JSContext* cx, unsigned argc, JS::Value* vp); + +bool +GenericPromiseReturningBindingMethod(JSContext* cx, unsigned argc, JS::Value* vp); + +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. +// +// promiseScope should be the scope in which the Promise should be created. +bool +ConvertExceptionToPromise(JSContext* cx, + JSObject* promiseScope, + JS::MutableHandle<JS::Value> rval); + +#ifdef DEBUG +void +AssertReturnTypeMatchesJitinfo(const JSJitInfo* aJitinfo, + JS::Handle<JS::Value> aValue); +#endif + +// This function is called by the bindings layer for methods/getters/setters +// that are not safe to be called in prerendering mode. It checks to make sure +// that the |this| object is not running in a global that is in prerendering +// mode. Otherwise, it aborts execution of timers and event handlers, and +// returns false which gets converted to an uncatchable exception by the +// bindings layer. +bool +EnforceNotInPrerendering(JSContext* aCx, JSObject* aObj); + +// Handles the violation of a blacklisted action in prerendering mode by +// aborting the scripts, and preventing timers and event handlers from running +// in the window in the future. +void +HandlePrerenderingViolation(nsPIDOMWindowInner* aWindow); + +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> +inline bool +WrappedJSToDictionary(JSContext* aCx, nsISupports* aObject, T& aDictionary) +{ + nsCOMPtr<nsIXPConnectWrappedJS> wrappedObj = do_QueryInterface(aObject); + if (!wrappedObj) { + return false; + } + + JS::Rooted<JSObject*> obj(aCx, wrappedObj->GetJSObject()); + if (!obj) { + return false; + } + + JSAutoCompartment ac(aCx, obj); + JS::Rooted<JS::Value> v(aCx, JS::ObjectValue(*obj)); + return aDictionary.Init(aCx, v); +} + +template<class T> +inline bool +WrappedJSToDictionary(nsISupports* aObject, T& aDictionary) +{ + nsCOMPtr<nsIXPConnectWrappedJS> wrappedObj = do_QueryInterface(aObject); + NS_ENSURE_TRUE(wrappedObj, false); + JS::Rooted<JSObject*> obj(RootingCx(), wrappedObj->GetJSObject()); + NS_ENSURE_TRUE(obj, false); + + nsIGlobalObject* global = xpc::NativeGlobal(obj); + NS_ENSURE_TRUE(global, false); + + // we need this AutoEntryScript here because the spec requires us to execute + // getters when parsing a dictionary + AutoEntryScript aes(global, "WebIDL dictionary creation"); + + JS::Rooted<JS::Value> v(aes.cx(), JS::ObjectValue(*obj)); + return aDictionary.Init(aes.cx(), v); +} + + +template<class T, class S> +inline RefPtr<T> +StrongOrRawPtr(already_AddRefed<S>&& aPtr) +{ + return aPtr.template downcast<T>(); +} + +template<class T, + class ReturnType=typename Conditional<IsRefcounted<T>::value, T*, + nsAutoPtr<T>>::Type> +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> +struct StrongPtrForMember +{ + typedef typename Conditional<IsRefcounted<T>::value, + RefPtr<T>, nsAutoPtr<T>>::Type Type; +}; + +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); + +// Get the desired prototype object for an object construction from the given +// CallArgs. Null is returned if the default prototype should be used. +bool +GetDesiredProto(JSContext* aCx, const JS::CallArgs& aCallArgs, + JS::MutableHandle<JSObject*> aDesiredProto); + +void +SetDocumentAndPageUseCounter(JSContext* aCx, JSObject* aObject, + UseCounter aUseCounter); + +// Warnings +void +DeprecationWarning(JSContext* aCx, JSObject* aObject, + nsIDocument::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(); +} // 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 000000000..aa7f26ad6 --- /dev/null +++ b/dom/bindings/Bindings.conf @@ -0,0 +1,1701 @@ +# -*- 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 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. +# * binaryNames - Dict for mapping method and attribute names to different +# names when calling the native methods (defaults to an empty +# dict). The keys are the property names as they appear in the +# .webidl file and the values are the names as they should be +# in the WebIDL. +# * 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. +# +# The following fields are either a string, an array (defaults to an empty +# array) or a dictionary with three possible keys (all, getterOnly and +# setterOnly) each having such an array as the value +# +# * implicitJSContext - 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 = { + +'AbstractWorker': { + 'concrete': False +}, + +'AddonManagerPermissions': { + 'wrapperCache': False, + 'concrete': False +}, + +'AnimationEffectReadOnly': { + 'concrete': False +}, + +'AnimationTimeline': { + 'concrete': False +}, + +'AnonymousContent': { + 'wrapperCache': False +}, + +'ArchiveReader': { + 'nativeType': 'mozilla::dom::archivereader::ArchiveReader', +}, + +'ArchiveRequest': { + 'nativeType': 'mozilla::dom::archivereader::ArchiveRequest', +}, + +'AudioChannelManager': { + 'nativeType': 'mozilla::dom::system::AudioChannelManager', + 'headerFile': 'AudioChannelManager.h' +}, + +'AudioBuffer': { + 'implicitJSContext': [ 'copyToChannel' ], +}, + +'AudioBufferSourceNode': { + 'implicitJSContext': [ 'buffer' ], +}, + +'AudioNode' : { + 'concrete': False, + 'binaryNames': { + 'channelCountMode': 'channelCountModeValue', + 'channelInterpretation': 'channelInterpretationValue', + }, +}, + +'BarProp': { + 'headerFile': 'mozilla/dom/BarProps.h', +}, + +'Blob': { + 'headerFile': 'mozilla/dom/File.h', +}, + +'BatteryManager': { + 'nativeType': 'mozilla::dom::battery::BatteryManager', + 'headerFile': 'BatteryManager.h' +}, + +'BoxObject': { + 'resultNotAddRefed': ['element'], +}, + +'Cache': { + 'implicitJSContext': [ 'add', 'addAll' ], + 'nativeType': 'mozilla::dom::cache::Cache', +}, + +'CacheStorage': { + 'nativeType': 'mozilla::dom::cache::CacheStorage', +}, + +'CanvasRenderingContext2D': { + 'implicitJSContext': [ + 'createImageData', 'getImageData' + ], + 'binaryNames': { + 'mozImageSmoothingEnabled': 'imageSmoothingEnabled' + } +}, + +'CaretPosition' : { + 'nativeType': 'nsDOMCaretPosition', +}, + +'CharacterData': { + 'nativeType': 'nsGenericDOMDataNode', + 'concrete': False +}, + +'ChromeUtils': { + # 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, +}, + +'ChromeWindow': { + 'concrete': False, +}, + +'ChromeWorker': { + 'headerFile': 'mozilla/dom/WorkerPrivate.h', + 'nativeType': 'mozilla::dom::workers::ChromeWorkerPrivate', +}, + +'Client': { + 'nativeType': 'mozilla::dom::workers::ServiceWorkerClient', + 'headerFile': 'mozilla/dom/workers/bindings/ServiceWorkerClient.h', +}, + +'Clients': { + 'nativeType': 'mozilla::dom::workers::ServiceWorkerClients', + 'headerFile': 'mozilla/dom/workers/bindings/ServiceWorkerClients.h', +}, + +'console': { + 'nativeType': 'mozilla::dom::Console', +}, + +'ConvolverNode': { + 'implicitJSContext': [ 'buffer' ], +}, + +'Coordinates': { + 'headerFile': 'nsGeoPosition.h' +}, + +'Crypto' : { + 'headerFile': 'Crypto.h' +}, + +'CSS': { + 'concrete': False, +}, + +'CSS2Properties': { + 'nativeType': 'nsDOMCSSDeclaration' +}, + +'CSSLexer': { + 'wrapperCache': False +}, + +'CSSPrimitiveValue': { + 'nativeType': 'nsROCSSPrimitiveValue', +}, + +'CSSStyleDeclaration': { + 'nativeType': 'nsICSSDeclaration' +}, + +'CSSStyleSheet': { + 'nativeType': 'mozilla::StyleSheet', + 'binaryNames': { 'ownerRule': 'DOMOwnerRule' }, +}, + +'CSSValue': { + 'concrete': False +}, + +'CSSValueList': { + 'nativeType': 'nsDOMCSSValueList' +}, + +'DataChannel': { + 'nativeType': 'nsDOMDataChannel', +}, + +'DedicatedWorkerGlobalScope': { + 'headerFile': 'mozilla/dom/WorkerScope.h', +}, + +'DeviceAcceleration': { + 'headerFile': 'mozilla/dom/DeviceMotionEvent.h', +}, + +'DeviceRotationRate': { + 'headerFile': 'mozilla/dom/DeviceMotionEvent.h', +}, + +'Document': { + 'nativeType': 'nsIDocument', + 'binaryNames': { + 'documentURI': 'documentURIFromJS', + 'URL': 'documentURIFromJS' + } +}, + +'DominatorTree': { + 'nativeType': 'mozilla::devtools::DominatorTree' +}, + +'DOMException': { + 'binaryNames': { + 'message': 'messageMoz', + }, + 'implicitJSContext': [ 'filename', 'lineNumber', 'stack' ], +}, + +'DOMMatrixReadOnly': { + 'headerFile': 'mozilla/dom/DOMMatrix.h', + 'concrete': False, +}, + +'DOMPointReadOnly': { + 'headerFile': 'mozilla/dom/DOMPoint.h', + 'concrete': False, +}, + +'DOMRectList': { + 'headerFile': 'mozilla/dom/DOMRect.h', +}, + +'DOMRectReadOnly': { + 'headerFile': 'mozilla/dom/DOMRect.h', +}, + +'DOMRequest': { + 'implicitJSContext': [ 'then' ], +}, + +'DOMStringMap': { + 'nativeType': 'nsDOMStringMap' +}, + +'DOMTokenList': { + 'nativeType': 'nsDOMTokenList', +}, + +'DynamicsCompressorNode': { + 'binaryNames': { + 'release': 'getRelease' + }, +}, + +'Event': { + 'implicitJSContext': [ 'defaultPrevented', 'preventDefault' ], +}, + +'EventTarget': { + # When we get rid of hasXPConnectImpls, we can get rid of the + # couldBeDOMBinding stuff in GetOrCreateDOMReflector. + # + # We can also get rid of the UnwrapArg bits in + # the dom QueryInterface (in BindingUtils.cpp) at that point. + 'hasXPConnectImpls': True, + 'concrete': False, + 'jsImplParent': 'mozilla::DOMEventTargetHelper', + 'implicitJSContext': [ 'dispatchEvent' ] +}, + +'Exception': { + 'headerFile': 'mozilla/dom/DOMException.h', + 'binaryNames': { + 'message': 'messageMoz', + }, + 'implicitJSContext': [ '__stringifier', 'filename', 'lineNumber', 'stack' ], +}, + +'ExtendableEvent': { + 'headerFile': 'mozilla/dom/ServiceWorkerEvents.h', + 'nativeType': 'mozilla::dom::workers::ExtendableEvent', + 'implicitJSContext': [ 'waitUntil' ], +}, + +'ExtendableMessageEvent': { + 'headerFile': 'mozilla/dom/ServiceWorkerEvents.h', + 'nativeType': 'mozilla::dom::workers::ExtendableMessageEvent', +}, + +'FetchEvent': { + 'headerFile': 'ServiceWorkerEvents.h', + 'nativeType': 'mozilla::dom::workers::FetchEvent', + 'binaryNames': { + 'request': 'request_' + }, + 'implicitJSContext': [ 'respondWith' ], +}, + +'FileReader': { + 'implicitJSContext': [ 'readAsArrayBuffer' ], +}, + +'FileReaderSync': { + 'wrapperCache': False, +}, + +'FlyWebFetchEvent': { + 'headerFile': 'FlyWebServerEvents.h', +}, + +'FlyWebWebSocketEvent': { + 'headerFile': 'FlyWebServerEvents.h', +}, + +'FontFaceSet': { + 'implicitJSContext': [ 'load' ], +}, + +'FontFaceSetIterator': { + 'wrapperCache': False, +}, + +'Geolocation': { + 'headerFile': 'nsGeolocation.h' +}, + +'HeapSnapshot': { + 'nativeType': 'mozilla::devtools::HeapSnapshot' +}, + +'History': { + 'headerFile': 'nsHistory.h', + 'nativeType': 'nsHistory' +}, + +'HTMLAppletElement': { + 'nativeType': 'mozilla::dom::HTMLSharedObjectElement' +}, + +'HTMLBaseElement': { + 'nativeType': 'mozilla::dom::HTMLSharedElement' +}, + +'HTMLCollection': { + 'nativeType': 'nsIHTMLCollection', + # nsContentList.h pulls in nsIHTMLCollection.h + 'headerFile': 'nsContentList.h', +}, + +'HTMLDirectoryElement': { + 'nativeType': 'mozilla::dom::HTMLSharedElement' +}, + +'HTMLDListElement': { + 'nativeType' : 'mozilla::dom::HTMLSharedListElement' +}, + +'HTMLDocument': { + 'nativeType': 'nsHTMLDocument', + 'implicitJSContext': [ 'open', 'write', 'writeln' ] +}, + +'HTMLElement': { + 'nativeType': 'nsGenericHTMLElement', +}, + +'HTMLEmbedElement': { + 'nativeType': 'mozilla::dom::HTMLSharedObjectElement' +}, + +'HTMLHeadElement': { + 'nativeType': 'mozilla::dom::HTMLSharedElement' +}, + +'HTMLHtmlElement': { + 'nativeType': 'mozilla::dom::HTMLSharedElement' +}, + +'HTMLMediaElement': { + 'concrete': False +}, + +'HTMLOListElement': { + 'nativeType' : 'mozilla::dom::HTMLSharedListElement' +}, + +'HTMLParamElement': { + 'nativeType': 'mozilla::dom::HTMLSharedElement' +}, + +'HTMLQuoteElement': { + 'nativeType': 'mozilla::dom::HTMLSharedElement' +}, + +'HTMLTextAreaElement': { + 'binaryNames': { + 'textLength': 'getTextLength' + } +}, + +'HTMLUListElement': { + 'nativeType' : 'mozilla::dom::HTMLSharedListElement' +}, + +'IDBCursor': { + 'implicitJSContext': [ 'delete' ], + 'binaryNames': { + 'direction': 'getDirection' + } +}, + +'IDBCursorWithValue': { + 'nativeType': 'mozilla::dom::IDBCursor', +}, + +'IDBDatabase': { + 'implicitJSContext': [ 'transaction', 'createMutableFile', + 'mozCreateFileHandle' ], +}, + +'IDBFactory': { + 'implicitJSContext': [ 'open', 'deleteDatabase', 'openForPrincipal', + 'deleteForPrincipal' ], +}, + +'IDBIndex': { + 'binaryNames': { + 'mozGetAll': 'getAll', + 'mozGetAllKeys': 'getAllKeys', + } +}, + +'IDBKeyRange': { + 'wrapperCache': False, +}, + +'IDBLocaleAwareKeyRange': { + 'headerFile': 'IDBKeyRange.h', + 'wrapperCache': False, +}, + +'IDBObjectStore': { + 'binaryNames': { + 'mozGetAll': 'getAll' + }, + 'implicitJSContext': [ 'clear' ], +}, + +'IDBOpenDBRequest': { + 'headerFile': 'IDBRequest.h' +}, + +'IDBVersionChangeEvent': { + 'headerFile': 'IDBEvents.h', +}, + +'IID': { + 'nativeType': 'nsIJSID', + 'headerFile': 'xpcjsid.h', +}, + +'ImageBitmap': { + 'implicitJSContext': [ 'mapDataInto' ], +}, + +'ImageCapture': { + 'binaryNames': { 'videoStreamTrack': 'GetVideoStreamTrack' } +}, + +'ImageData': { + 'wrapperCache': False, +}, + +'InputStream': { + 'nativeType': 'nsIInputStream', + 'notflattened': True +}, + +'IntersectionObserver': { + 'nativeType': 'mozilla::dom::DOMIntersectionObserver', +}, + +'IntersectionObserverEntry': { + 'nativeType': 'mozilla::dom::DOMIntersectionObserverEntry', + 'headerFile': 'DOMIntersectionObserver.h', +}, + +'KeyEvent': { + 'concrete': False +}, + +'KeyframeEffect': { + 'implicitJSContext': { 'setterOnly': [ 'spacing' ] } +}, + +'LegacyMozTCPSocket': { + 'headerFile': 'TCPSocket.h', + 'wrapperCache': False, +}, + +'LocalMediaStream': { + 'headerFile': 'DOMMediaStream.h', + 'nativeType': 'mozilla::DOMLocalMediaStream' +}, + +'MediaList': { + 'nativeType': 'nsMediaList', + 'headerFile': 'nsIMediaList.h', +}, + +'MediaKeys' : { + 'implicitJSContext': [ 'createSession'] +}, + +'MediaStream': { + 'headerFile': 'DOMMediaStream.h', + 'nativeType': 'mozilla::DOMMediaStream' +}, + +'MediaStreamAudioDestinationNode': { + 'binaryNames': { 'stream': 'DOMStream' } +}, + +'MediaStreamList': { + 'headerFile': 'MediaStreamList.h', +}, + +'MediaStreamTrack': { + 'concrete': False +}, + +'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 +}, + +'MozSpeakerManager': { + 'nativeType': 'mozilla::dom::SpeakerManager', + 'headerFile': 'SpeakerManager.h' +}, + +'MozPowerManager': { + 'nativeType': 'mozilla::dom::PowerManager', +}, + +'MozWakeLock': { + 'nativeType': 'mozilla::dom::WakeLock', +}, + +'MozTimeManager': { + 'nativeType': 'mozilla::dom::time::TimeManager', +}, + +'MutationObserver': { + 'nativeType': 'nsDOMMutationObserver', +}, + +'MutationRecord': { + 'nativeType': 'nsDOMMutationRecord', + 'headerFile': 'nsDOMMutationObserver.h', +}, + +'NamedNodeMap': { + 'nativeType': 'nsDOMAttributeMap', +}, + +'NetworkInformation': { + 'nativeType': 'mozilla::dom::network::Connection', +}, + +'Node': { + 'nativeType': 'nsINode', + 'concrete': False, + 'binaryNames': { + 'baseURI': 'baseURIFromJS' + } +}, + +'NodeIterator': { + 'wrapperCache': False, +}, + +'NodeList': { + 'nativeType': 'nsINodeList', +}, + +'NotificationEvent': { + 'headerFile': 'mozilla/dom/NotificationEvent.h', + 'nativeType': 'mozilla::dom::workers::NotificationEvent', + 'binaryNames': { + 'notification': 'notification_' + } +}, + +'OfflineAudioContext': { + 'nativeType': 'mozilla::dom::AudioContext', +}, + +'OfflineResourceList': { + 'nativeType': 'nsDOMOfflineResourceList', +}, + +'PaintRequestList': { + 'headerFile': 'mozilla/dom/PaintRequest.h', +}, + +'Path2D': { + 'nativeType': 'mozilla::dom::CanvasPath', + 'headerFile': 'CanvasPath.h' +}, + +'PeerConnectionImpl': { + 'nativeType': 'mozilla::PeerConnectionImpl', + 'headerFile': 'PeerConnectionImpl.h', + 'wrapperCache': False +}, + +'Plugin': { + 'headerFile' : 'nsPluginArray.h', + 'nativeType': 'nsPluginElement', +}, + +'PluginArray': { + 'nativeType': 'nsPluginArray', +}, + +'PluginTag': { + 'nativeType': 'nsIPluginTag', +}, + +'PopupBoxObject': { + 'resultNotAddRefed': ['triggerNode', 'anchorNode'], +}, + +'Position': { + 'headerFile': 'nsGeoPosition.h' +}, + +'PositionError': { + 'headerFile': 'nsGeolocation.h' +}, + +'Promise': { + 'implicitJSContext': [ 'then', 'catch' ], +}, + +'PromiseDebugging': { + 'concrete': False, +}, + +'PromiseNativeHandler': { + 'wrapperCache': False, +}, + +'PushEvent': { + 'headerFile': 'ServiceWorkerEvents.h', + 'nativeType': 'mozilla::dom::workers::PushEvent', +}, + +'PushMessageData': { + 'headerFile': 'ServiceWorkerEvents.h', + 'nativeType': 'mozilla::dom::workers::PushMessageData', +}, + +'Range': { + 'nativeType': 'nsRange', + 'binaryNames': { + '__stringifier': 'ToString' + } +}, + +'Rect': { + 'nativeType': 'nsDOMCSSRect', +}, + +'Request': { + 'binaryNames': { + 'headers': 'headers_', + 'referrerPolicy': 'referrerPolicy_' + }, +}, + +'Response': { + 'binaryNames': { 'headers': 'headers_' }, +}, + +'RGBColor': { + 'nativeType': 'nsDOMCSSRGBColor', +}, + +'Screen': { + 'nativeType': 'nsScreen', +}, + +'ServiceWorker': { + 'nativeType': 'mozilla::dom::workers::ServiceWorker', + 'headerFile': 'mozilla/dom/workers/bindings/ServiceWorker.h', +}, + +'ServiceWorkerGlobalScope': { + 'headerFile': 'mozilla/dom/WorkerScope.h', +}, + +'ServiceWorkerRegistration': { + 'implicitJSContext': [ 'pushManager' ], +}, + +'SharedWorker': { + 'nativeType': 'mozilla::dom::workers::SharedWorker', + 'headerFile': 'mozilla/dom/workers/bindings/SharedWorker.h', + 'implicitJSContext': [ 'constructor' ], +}, + +'SharedWorkerGlobalScope': { + 'headerFile': 'mozilla/dom/WorkerScope.h', +}, + +'Storage': { + 'nativeType': 'mozilla::dom::DOMStorage', +}, + +'StyleSheet': { + 'nativeType': 'mozilla::StyleSheet', + 'headerFile': 'mozilla/StyleSheetInlines.h', +}, + +'SVGAnimatedLengthList': { + 'nativeType': 'mozilla::DOMSVGAnimatedLengthList', + 'headerFile': 'DOMSVGAnimatedLengthList.h', +}, + +'SVGAnimatedNumberList': { + 'nativeType': 'mozilla::DOMSVGAnimatedNumberList', + 'headerFile': 'DOMSVGAnimatedNumberList.h' +}, + +'SVGAnimatedPreserveAspectRatio': { + 'nativeType': 'mozilla::dom::DOMSVGAnimatedPreserveAspectRatio', + 'headerFile': 'SVGAnimatedPreserveAspectRatio.h' +}, + +'SVGAnimationElement': { + 'concrete': False +}, + +'SVGComponentTransferFunctionElement': { + 'concrete': False, +}, + +'SVGElement': { + 'nativeType': 'nsSVGElement', +}, + +'SVGFEFuncAElement': { + 'headerFile': 'mozilla/dom/SVGComponentTransferFunctionElement.h', +}, + +'SVGFEFuncBElement': { + 'headerFile': 'mozilla/dom/SVGComponentTransferFunctionElement.h', +}, + +'SVGFEFuncGElement': { + 'headerFile': 'mozilla/dom/SVGComponentTransferFunctionElement.h', +}, + +'SVGFEFuncRElement': { + 'headerFile': 'mozilla/dom/SVGComponentTransferFunctionElement.h', +}, + +'SVGGraphicsElement': { + 'concrete': False, +}, + +'SVGGradientElement': { + 'concrete': False, +}, + +'SVGLength': { + 'nativeType': 'mozilla::DOMSVGLength', + 'headerFile': 'DOMSVGLength.h' +}, + +'SVGLengthList': { + 'nativeType': 'mozilla::DOMSVGLengthList', + 'headerFile': 'DOMSVGLengthList.h' +}, + +'SVGLinearGradientElement': { + 'headerFile': 'mozilla/dom/SVGGradientElement.h', +}, + +'SVGNumber': { + 'nativeType': 'mozilla::DOMSVGNumber', + 'headerFile': 'DOMSVGNumber.h', +}, + +'SVGNumberList': { + 'nativeType': 'mozilla::DOMSVGNumberList', + 'headerFile': 'DOMSVGNumberList.h' +}, + +'SVGPathSeg': { + 'nativeType': 'mozilla::DOMSVGPathSeg', + 'headerFile': 'DOMSVGPathSeg.h', + 'concrete': False, +}, + +'SVGPathSegClosePath': { + 'nativeType': 'mozilla::DOMSVGPathSegClosePath', + 'headerFile': 'DOMSVGPathSeg.h' +}, + +'SVGPathSegMovetoAbs': { + 'nativeType': 'mozilla::DOMSVGPathSegMovetoAbs', + 'headerFile': 'DOMSVGPathSeg.h' +}, + +'SVGPathSegMovetoRel': { + 'nativeType': 'mozilla::DOMSVGPathSegMovetoRel', + 'headerFile': 'DOMSVGPathSeg.h' +}, + +'SVGPathSegLinetoAbs': { + 'nativeType': 'mozilla::DOMSVGPathSegLinetoAbs', + 'headerFile': 'DOMSVGPathSeg.h' +}, + +'SVGPathSegLinetoRel': { + 'nativeType': 'mozilla::DOMSVGPathSegLinetoRel', + 'headerFile': 'DOMSVGPathSeg.h' +}, + +'SVGPathSegCurvetoCubicAbs': { + 'nativeType': 'mozilla::DOMSVGPathSegCurvetoCubicAbs', + 'headerFile': 'DOMSVGPathSeg.h' +}, + +'SVGPathSegCurvetoCubicRel': { + 'nativeType': 'mozilla::DOMSVGPathSegCurvetoCubicRel', + 'headerFile': 'DOMSVGPathSeg.h' +}, + +'SVGPathSegCurvetoQuadraticAbs': { + 'nativeType': 'mozilla::DOMSVGPathSegCurvetoQuadraticAbs', + 'headerFile': 'DOMSVGPathSeg.h' +}, + +'SVGPathSegCurvetoQuadraticRel': { + 'nativeType': 'mozilla::DOMSVGPathSegCurvetoQuadraticRel', + 'headerFile': 'DOMSVGPathSeg.h' +}, + +'SVGPathSegArcAbs': { + 'nativeType': 'mozilla::DOMSVGPathSegArcAbs', + 'headerFile': 'DOMSVGPathSeg.h' +}, + +'SVGPathSegArcRel': { + 'nativeType': 'mozilla::DOMSVGPathSegArcRel', + 'headerFile': 'DOMSVGPathSeg.h' +}, + +'SVGPathSegLinetoHorizontalAbs': { + 'nativeType': 'mozilla::DOMSVGPathSegLinetoHorizontalAbs', + 'headerFile': 'DOMSVGPathSeg.h' +}, + +'SVGPathSegLinetoHorizontalRel': { + 'nativeType': 'mozilla::DOMSVGPathSegLinetoHorizontalRel', + 'headerFile': 'DOMSVGPathSeg.h' +}, + +'SVGPathSegLinetoVerticalAbs': { + 'nativeType': 'mozilla::DOMSVGPathSegLinetoVerticalAbs', + 'headerFile': 'DOMSVGPathSeg.h' +}, + +'SVGPathSegLinetoVerticalRel': { + 'nativeType': 'mozilla::DOMSVGPathSegLinetoVerticalRel', + 'headerFile': 'DOMSVGPathSeg.h' +}, + +'SVGPathSegCurvetoCubicSmoothAbs': { + 'nativeType': 'mozilla::DOMSVGPathSegCurvetoCubicSmoothAbs', + 'headerFile': 'DOMSVGPathSeg.h' +}, + +'SVGPathSegCurvetoCubicSmoothRel': { + 'nativeType': 'mozilla::DOMSVGPathSegCurvetoCubicSmoothRel', + 'headerFile': 'DOMSVGPathSeg.h' +}, + +'SVGPathSegCurvetoQuadraticSmoothAbs': { + 'nativeType': 'mozilla::DOMSVGPathSegCurvetoQuadraticSmoothAbs', + 'headerFile': 'DOMSVGPathSeg.h' +}, + +'SVGPathSegCurvetoQuadraticSmoothRel': { + 'nativeType': 'mozilla::DOMSVGPathSegCurvetoQuadraticSmoothRel', + 'headerFile': 'DOMSVGPathSeg.h' +}, + +'SVGPathSegList': { + 'nativeType': 'mozilla::DOMSVGPathSegList', + 'headerFile': 'DOMSVGPathSegList.h' +}, + +'SVGPoint': { + 'nativeType': 'mozilla::nsISVGPoint', + 'headerFile': 'nsISVGPoint.h' +}, + +'SVGPointList': { + 'nativeType': 'mozilla::DOMSVGPointList', + 'headerFile': 'DOMSVGPointList.h' +}, + +'SVGPreserveAspectRatio': { + 'nativeType': 'mozilla::dom::DOMSVGPreserveAspectRatio', + 'headerFile': 'SVGPreserveAspectRatio.h' +}, + +'SVGRadialGradientElement': { + 'headerFile': 'mozilla/dom/SVGGradientElement.h', +}, + +'SVGRect': { + 'nativeType': 'mozilla::dom::SVGIRect' +}, + +'SVGTextContentElement': { + 'concrete': False +}, + +'SVGTextPositioningElement': { + 'concrete': False +}, + +'SVGTransform': { + 'binaryNames': { + "matrix": "GetMatrix" + } +}, + +'SVGTransformList': { + 'nativeType': 'mozilla::DOMSVGTransformList', + 'headerFile': 'DOMSVGTransformList.h' +}, + +'SVGStringList': { + 'nativeType': 'mozilla::DOMSVGStringList', + 'headerFile': 'DOMSVGStringList.h', +}, + +'SVGUnitTypes' : { + 'concrete': False, +}, + +'SVGZoomAndPan' : { + 'concrete': False, +}, + +'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 +}, + +'TCPSocket': { + 'implicitJSContext': ['send'] +}, + +'ThreadSafeChromeUtils': { + # 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/ChromeUtils.h', +}, + +'TouchList': { + 'headerFile': 'mozilla/dom/TouchEvent.h', +}, + +'TreeColumn': { + 'nativeType': 'nsTreeColumn', + 'headerFile': 'nsTreeColumns.h', +}, + +'TreeColumns': { + 'nativeType': 'nsTreeColumns', +}, + +'TreeWalker': { + 'wrapperCache': False, +}, + +'VTTCue': { + 'nativeType': 'mozilla::dom::TextTrackCue' +}, + +'VTTRegion': { + 'nativeType': 'mozilla::dom::TextTrackRegion', +}, + +'WindowClient': { + 'nativeType': 'mozilla::dom::workers::ServiceWorkerWindowClient', + 'headerFile': 'mozilla/dom/workers/bindings/ServiceWorkerWindowClient.h', +}, + +'WebGLActiveInfo': { + 'nativeType': 'mozilla::WebGLActiveInfo', + 'headerFile': 'WebGLActiveInfo.h' +}, + +'WebGLBuffer': { + 'nativeType': 'mozilla::WebGLBuffer', + 'headerFile': 'WebGLBuffer.h' +}, + +'WEBGL_compressed_texture_atc': { + 'nativeType': 'mozilla::WebGLExtensionCompressedTextureATC', + 'headerFile': 'WebGLExtensions.h' +}, + +'WEBGL_compressed_texture_etc': { + 'nativeType': 'mozilla::WebGLExtensionCompressedTextureES3', + 'headerFile': 'WebGLExtensions.h' +}, + +'WEBGL_compressed_texture_etc1': { + 'nativeType': 'mozilla::WebGLExtensionCompressedTextureETC1', + 'headerFile': 'WebGLExtensions.h' +}, + +'WEBGL_compressed_texture_pvrtc': { + 'nativeType': 'mozilla::WebGLExtensionCompressedTexturePVRTC', + 'headerFile': 'WebGLExtensions.h' +}, + +'WEBGL_compressed_texture_s3tc': { + 'nativeType': 'mozilla::WebGLExtensionCompressedTextureS3TC', + 'headerFile': 'WebGLExtensions.h' +}, + +'WEBGL_depth_texture': { + 'nativeType': 'mozilla::WebGLExtensionDepthTexture', + 'headerFile': 'WebGLExtensions.h' +}, + +'WEBGL_debug_renderer_info': { + 'nativeType': 'mozilla::WebGLExtensionDebugRendererInfo', + 'headerFile': 'WebGLExtensions.h' +}, + +'WEBGL_debug_shaders': { + 'nativeType': 'mozilla::WebGLExtensionDebugShaders', + 'headerFile': 'WebGLExtensions.h' +}, + +'OES_element_index_uint': { + 'nativeType': 'mozilla::WebGLExtensionElementIndexUint', + 'headerFile': 'WebGLExtensions.h' +}, + +'EXT_frag_depth': { + 'nativeType': 'mozilla::WebGLExtensionFragDepth', + 'headerFile': 'WebGLExtensions.h' +}, + +'WEBGL_lose_context': { + 'nativeType': 'mozilla::WebGLExtensionLoseContext', + 'headerFile': 'WebGLExtensions.h' +}, + +'EXT_sRGB': { + 'nativeType': 'mozilla::WebGLExtensionSRGB', + 'headerFile': 'WebGLExtensions.h' +}, + +'OES_standard_derivatives': { + 'nativeType': 'mozilla::WebGLExtensionStandardDerivatives', + 'headerFile': 'WebGLExtensions.h' +}, + +'EXT_shader_texture_lod': { + 'nativeType': 'mozilla::WebGLExtensionShaderTextureLod', + 'headerFile': 'WebGLExtensions.h' +}, + +'EXT_texture_filter_anisotropic': { + 'nativeType': 'mozilla::WebGLExtensionTextureFilterAnisotropic', + 'headerFile': 'WebGLExtensions.h' +}, + +'OES_texture_float': { + 'nativeType': 'mozilla::WebGLExtensionTextureFloat', + 'headerFile': 'WebGLExtensions.h' +}, + +'OES_texture_float_linear': { + 'nativeType': 'mozilla::WebGLExtensionTextureFloatLinear', + 'headerFile': 'WebGLExtensions.h' +}, + +'OES_texture_half_float': { + 'nativeType': 'mozilla::WebGLExtensionTextureHalfFloat', + 'headerFile': 'WebGLExtensions.h' +}, + +'OES_texture_half_float_linear': { + 'nativeType': 'mozilla::WebGLExtensionTextureHalfFloatLinear', + 'headerFile': 'WebGLExtensions.h' +}, + +'WEBGL_color_buffer_float': { + 'nativeType': 'mozilla::WebGLExtensionColorBufferFloat', + 'headerFile': 'WebGLExtensions.h' +}, + +'EXT_color_buffer_half_float': { + 'nativeType': 'mozilla::WebGLExtensionColorBufferHalfFloat', + 'headerFile': 'WebGLExtensions.h' +}, + +'EXT_color_buffer_float': { + 'nativeType': 'mozilla::WebGLExtensionEXTColorBufferFloat', + 'headerFile': 'WebGLExtensions.h' +}, + +'WEBGL_draw_buffers': { + 'nativeType': 'mozilla::WebGLExtensionDrawBuffers', + 'headerFile': 'WebGLExtensions.h' +}, + +'OES_vertex_array_object': { + 'nativeType': 'mozilla::WebGLExtensionVertexArray', + 'headerFile': 'WebGLExtensions.h' +}, + +'ANGLE_instanced_arrays': { + 'nativeType': 'mozilla::WebGLExtensionInstancedArrays', + 'headerFile': 'WebGLExtensions.h' +}, + +'EXT_blend_minmax': { + 'nativeType': 'mozilla::WebGLExtensionBlendMinMax', + 'headerFile': 'WebGLExtensions.h' +}, + +'EXT_disjoint_timer_query': { + 'nativeType': 'mozilla::WebGLExtensionDisjointTimerQuery', + 'headerFile': 'WebGLExtensions.h' +}, + +'WebGLFramebuffer': { + 'nativeType': 'mozilla::WebGLFramebuffer', + 'headerFile': 'WebGLFramebuffer.h' +}, + +'WebGLProgram': { + 'nativeType': 'mozilla::WebGLProgram', + 'headerFile': 'WebGLProgram.h' +}, + +'WebGLQuery': { + 'nativeType': 'mozilla::WebGLQuery', + 'headerFile': 'WebGLQuery.h' +}, + +'WebGLRenderbuffer': { + 'nativeType': 'mozilla::WebGLRenderbuffer', + 'headerFile': 'WebGLRenderbuffer.h' +}, + +'WebGLRenderingContext': { + 'nativeType': 'mozilla::WebGLContext', + 'headerFile': 'WebGLContext.h', +}, + +'WebGL2RenderingContext': { + 'nativeType': 'mozilla::WebGL2Context', + 'headerFile': 'WebGL2Context.h', +}, + +'WebGLSampler': { + 'nativeType': 'mozilla::WebGLSampler', + 'headerFile': 'WebGLSampler.h' +}, + +'WebGLShader': { + 'nativeType': 'mozilla::WebGLShader', + 'headerFile': 'WebGLShader.h' +}, + +'WebGLShaderPrecisionFormat': { + 'nativeType': 'mozilla::WebGLShaderPrecisionFormat', + 'headerFile': 'WebGLShaderPrecisionFormat.h', + 'wrapperCache': False +}, + +'WebGLSync': { + 'nativeType': 'mozilla::WebGLSync', + 'headerFile': 'WebGLSync.h' +}, + +'WebGLTexture': { + 'nativeType': 'mozilla::WebGLTexture', + 'headerFile': 'WebGLTexture.h' +}, + +'WebGLTransformFeedback': { + 'nativeType': 'mozilla::WebGLTransformFeedback', + 'headerFile': 'WebGLTransformFeedback.h' +}, + +'WebGLUniformLocation': { + 'nativeType': 'mozilla::WebGLUniformLocation', + 'headerFile': 'WebGLUniformLocation.h' +}, + +'WebGLVertexArrayObject': { + 'nativeType': 'mozilla::WebGLVertexArray', + 'headerFile': 'WebGLVertexArray.h' +}, + +'WebrtcGlobalInformation': { + 'nativeType': 'mozilla::dom::WebrtcGlobalInformation', + 'headerFile': 'WebrtcGlobalInformation.h', + 'wrapperCache': False, + 'concrete': False, +}, + +'Window': { + 'nativeType': 'nsGlobalWindow', + 'binaryNames': { + 'postMessage': 'postMessageMoz', + }, + 'implicitJSContext': [ + 'requestIdleCallback' + ], +}, + +'WindowProxy': { + 'nativeType': 'nsPIDOMWindowOuter', + 'headerFile': 'nsPIDOMWindow.h', + 'concrete': False +}, + +'WindowRoot': { + 'nativeType': 'nsWindowRoot' +}, + +'Worker': { + 'headerFile': 'mozilla/dom/WorkerPrivate.h', + 'nativeType': 'mozilla::dom::workers::WorkerPrivate', +}, + +'WorkerDebuggerGlobalScope': { + 'headerFile': 'mozilla/dom/WorkerScope.h', + 'implicitJSContext': [ + 'dump', 'global', 'reportError', 'setConsoleEventHandler', + ], +}, + +'WorkerGlobalScope': { + 'headerFile': 'mozilla/dom/WorkerScope.h', + 'concrete': False, + 'implicitJSContext': [ + 'close', + ], + # Rename a few things so we don't have both classes and methods + # with the same name + 'binaryNames': { + 'performance': 'getPerformance', + }, +}, + +'XMLHttpRequest': { + 'implicitJSContext': [ 'send'], +}, + +'XMLHttpRequestEventTarget': { + 'concrete': False +}, + +'XMLSerializer': { + 'nativeType': 'nsDOMSerializer', +}, + +'XPathEvaluator': { + 'wrapperCache': False +}, + +'XPathExpression': { + 'wrapperCache': False, +}, + +'XSLTProcessor': { + 'nativeType': 'txMozillaXSLTProcessor', +}, + +'XULDocument': { + 'headerFile': 'XULDocument.h' +}, + +'XULElement': { + 'nativeType': 'nsXULElement', +}, + +#################################### +# Test Interfaces of various sorts # +#################################### + +'TestInterface' : { + # Keep this in sync with TestExampleInterface + 'headerFile': 'TestBindingHeader.h', + 'register': False, + 'binaryNames': { 'methodRenamedFrom': 'methodRenamedTo', + 'attributeGetterRenamedFrom': 'attributeGetterRenamedTo', + 'attributeRenamedFrom': 'attributeRenamedTo' } + }, + +'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, + 'binaryNames': { 'methodRenamedFrom': 'methodRenamedTo', + 'attributeGetterRenamedFrom': 'attributeGetterRenamedTo', + 'attributeRenamedFrom': 'attributeRenamedTo' } + }, + +'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, + 'concrete': False + }, + +'OnlyForUseInConstructor' : { + 'headerFile': 'TestBindingHeader.h', + 'register': False + }, + +'ImplementedInterface' : { + 'headerFile': 'TestBindingHeader.h', + 'concrete': False, + 'register': False, + }, + +'ImplementedInterfaceParent' : { + 'headerFile': 'TestBindingHeader.h', + 'concrete': False, + 'register': False + }, + +'DiamondImplements' : { + 'headerFile': 'TestBindingHeader.h', + 'concrete': False, + 'register': False + }, + +'DiamondBranch1A' : { + 'headerFile': 'TestBindingHeader.h', + 'concrete': False, + 'register': False + }, + +'DiamondBranch1B' : { + 'headerFile': 'TestBindingHeader.h', + 'concrete': False, + 'register': False + }, + +'DiamondBranch2A' : { + 'headerFile': 'TestBindingHeader.h', + 'concrete': False, + 'register': False + }, + +'DiamondBranch2B' : { + 'headerFile': 'TestBindingHeader.h', + 'concrete': False, + '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, + 'binaryNames': { 'methodRenamedFrom': 'methodRenamedTo', + 'attributeGetterRenamedFrom': 'attributeGetterRenamedTo', + 'attributeRenamedFrom': 'attributeRenamedTo' } + }, + +'TestExampleWorkerInterface' : { + 'headerFile': 'TestExampleWorkerInterface-example.h', + 'register': False, + }, + +'TestExampleProxyInterface' : { + 'headerFile': 'TestExampleProxyInterface-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, + }, + +} + +# 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('ApplicationCache', nativeType='nsIDOMOfflineResourceList') +addExternalIface('Counter') +addExternalIface('CSSRule') +addExternalIface('RTCDataChannel', nativeType='nsIDOMDataChannel') +addExternalIface('HitRegionOptions', nativeType='nsISupports') +addExternalIface('imgINotificationObserver', nativeType='imgINotificationObserver') +addExternalIface('imgIRequest', nativeType='imgIRequest', notflattened=True) +addExternalIface('MenuBuilder', nativeType='nsIMenuBuilder', notflattened=True) +addExternalIface('MozControllers', nativeType='nsIControllers') +addExternalIface('MozFrameLoader', nativeType='nsIFrameLoader', notflattened=True) +addExternalIface('MozObserver', nativeType='nsIObserver', notflattened=True) +addExternalIface('MozRDFCompositeDataSource', nativeType='nsIRDFCompositeDataSource', + notflattened=True) +addExternalIface('MozRDFResource', nativeType='nsIRDFResource', notflattened=True) +addExternalIface('MozTreeView', nativeType='nsITreeView', + headerFile='nsITreeView.h', notflattened=True) +addExternalIface('MozWakeLockListener', headerFile='nsIDOMWakeLockListener.h') +addExternalIface('MozXULTemplateBuilder', nativeType='nsIXULTemplateBuilder') +addExternalIface('nsIBrowserDOMWindow', nativeType='nsIBrowserDOMWindow', + notflattened=True) +addExternalIface('nsIControllers', nativeType='nsIControllers') +addExternalIface('nsIDOMCrypto', nativeType='nsIDOMCrypto', + headerFile='Crypto.h') +addExternalIface('nsIFile', nativeType='nsIFile', notflattened=True) +addExternalIface('nsILoadGroup', nativeType='nsILoadGroup', + headerFile='nsILoadGroup.h', notflattened=True) +addExternalIface('nsIMessageBroadcaster', nativeType='nsIMessageBroadcaster', + headerFile='nsIMessageManager.h', notflattened=True) +addExternalIface('nsISelectionListener', nativeType='nsISelectionListener') +addExternalIface('nsIStreamListener', nativeType='nsIStreamListener', notflattened=True) +addExternalIface('nsITransportProvider', nativeType='nsITransportProvider') +addExternalIface('nsISupports', nativeType='nsISupports') +addExternalIface('nsIDocShell', nativeType='nsIDocShell', notflattened=True) +addExternalIface('nsIEditor', nativeType='nsIEditor', notflattened=True) +addExternalIface('nsIVariant', nativeType='nsIVariant', notflattened=True) +addExternalIface('nsIScriptableRegion', nativeType='nsIScriptableRegion', 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('URI', nativeType='nsIURI', headerFile='nsIURI.h', + notflattened=True) +addExternalIface('XULCommandDispatcher') diff --git a/dom/bindings/CallbackFunction.h b/dom/bindings/CallbackFunction.h new file mode 100644 index 000000000..ac2b3e8b3 --- /dev/null +++ b/dom/bindings/CallbackFunction.h @@ -0,0 +1,78 @@ +/* -*- 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 { +namespace dom { + +class CallbackFunction : public CallbackObject +{ +public: + // See CallbackObject for an explanation of the arguments. + explicit CallbackFunction(JSContext* aCx, JS::Handle<JSObject*> aCallable, + nsIGlobalObject* aIncumbentGlobal) + : CallbackObject(aCx, aCallable, aIncumbentGlobal) + { + } + + // See CallbackObject for an explanation of the arguments. + explicit CallbackFunction(JS::Handle<JSObject*> aCallable, + JS::Handle<JSObject*> aAsyncStack, + nsIGlobalObject* aIncumbentGlobal) + : CallbackObject(aCallable, aAsyncStack, aIncumbentGlobal) + { + } + + JS::Handle<JSObject*> Callable() const + { + return Callback(); + } + + JS::Handle<JSObject*> CallablePreserveColor() const + { + return CallbackPreserveColor(); + } + + bool HasGrayCallable() const + { + // Play it safe in case this gets called after unlink. + return mCallback && JS::ObjectIsMarkedGray(mCallback); + } + +protected: + explicit CallbackFunction(CallbackFunction* aCallbackFunction) + : CallbackObject(aCallbackFunction) + { + } + + // See CallbackObject for an explanation of the arguments. + CallbackFunction(JSContext* aCx, JS::Handle<JSObject*> aCallable, + nsIGlobalObject* aIncumbentGlobal, + const FastCallbackConstructor&) + : CallbackObject(aCx, aCallable, aIncumbentGlobal, + FastCallbackConstructor()) + { + } +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_CallbackFunction_h diff --git a/dom/bindings/CallbackInterface.cpp b/dom/bindings/CallbackInterface.cpp new file mode 100644 index 000000000..55c1d4d7a --- /dev/null +++ b/dom/bindings/CallbackInterface.cpp @@ -0,0 +1,36 @@ +/* -*- 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 "mozilla/dom/BindingUtils.h" +#include "nsPrintfCString.h" + +namespace mozilla { +namespace dom { + +bool +CallbackInterface::GetCallableProperty(JSContext* cx, JS::Handle<jsid> aPropId, + JS::MutableHandle<JS::Value> aCallable) +{ + if (!JS_GetPropertyById(cx, CallbackKnownNotGray(), aPropId, aCallable)) { + return false; + } + if (!aCallable.isObject() || + !JS::IsCallable(&aCallable.toObject())) { + char* propName = + JS_EncodeString(cx, JS_FORGET_STRING_FLATNESS(JSID_TO_FLAT_STRING(aPropId))); + nsPrintfCString description("Property '%s'", propName); + JS_free(cx, propName); + ThrowErrorMessage(cx, MSG_NOT_CALLABLE, description.get()); + return false; + } + + return true; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/bindings/CallbackInterface.h b/dom/bindings/CallbackInterface.h new file mode 100644 index 000000000..3ba5182e7 --- /dev/null +++ b/dom/bindings/CallbackInterface.h @@ -0,0 +1,59 @@ +/* -*- 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 { +namespace dom { + +class CallbackInterface : public CallbackObject +{ +public: + // See CallbackObject for an explanation of the arguments. + explicit CallbackInterface(JSContext* aCx, JS::Handle<JSObject*> aCallback, + nsIGlobalObject* aIncumbentGlobal) + : CallbackObject(aCx, aCallback, aIncumbentGlobal) + { + } + + // See CallbackObject for an explanation of the arguments. + explicit CallbackInterface(JS::Handle<JSObject*> aCallback, + JS::Handle<JSObject*> aAsyncStack, + nsIGlobalObject* aIncumbentGlobal) + : CallbackObject(aCallback, aAsyncStack, aIncumbentGlobal) + { + } + +protected: + bool GetCallableProperty(JSContext* cx, JS::Handle<jsid> aPropId, + JS::MutableHandle<JS::Value> aCallable); + + // See CallbackObject for an explanation of the arguments. + CallbackInterface(JSContext* aCx, JS::Handle<JSObject*> aCallable, + nsIGlobalObject* aIncumbentGlobal, + const FastCallbackConstructor&) + : CallbackObject(aCx, aCallable, aIncumbentGlobal, + FastCallbackConstructor()) + { + } +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_CallbackFunction_h diff --git a/dom/bindings/CallbackObject.cpp b/dom/bindings/CallbackObject.cpp new file mode 100644 index 000000000..7c7d2c6b4 --- /dev/null +++ b/dom/bindings/CallbackObject.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/CallbackObject.h" +#include "mozilla/dom/BindingUtils.h" +#include "jsfriendapi.h" +#include "nsIScriptGlobalObject.h" +#include "nsIXPConnect.h" +#include "nsIScriptContext.h" +#include "nsPIDOMWindow.h" +#include "nsJSUtils.h" +#include "xpcprivate.h" +#include "WorkerPrivate.h" +#include "nsGlobalWindow.h" +#include "WorkerScope.h" +#include "jsapi.h" +#include "nsJSPrincipals.h" + +namespace mozilla { +namespace 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->DropJSObjects(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncumbentGlobal) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CallbackObject) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncumbentGlobal) +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(mCreationStack) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mIncumbentJSGlobal) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +void +CallbackObject::Trace(JSTracer* aTracer) +{ + JS::TraceEdge(aTracer, &mCallback, "CallbackObject.mCallback"); + JS::TraceEdge(aTracer, &mCreationStack, "CallbackObject.mCreationStack"); + JS::TraceEdge(aTracer, &mIncumbentJSGlobal, + "CallbackObject.mIncumbentJSGlobal"); +} + +void +CallbackObject::HoldJSObjectsIfMoreThanOneOwner() +{ + MOZ_ASSERT(mRefCnt.get() > 0); + if (mRefCnt.get() > 1) { + mozilla::HoldJSObjects(this); + } else { + // We can just forget all our stuff. + ClearJSReferences(); + } +} + +CallbackObject::CallSetup::CallSetup(CallbackObject* aCallback, + ErrorResult& aRv, + const char* aExecutionReason, + ExceptionHandling aExceptionHandling, + JSCompartment* aCompartment, + bool aIsJSImplementedWebIDL) + : mCx(nullptr) + , mCompartment(aCompartment) + , mErrorResult(aRv) + , mExceptionHandling(aExceptionHandling) + , mIsMainThread(NS_IsMainThread()) +{ + if (mIsMainThread) { + nsContentUtils::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(); + } + + // First, find the real underlying callback. + JSObject* realCallback = js::UncheckedUnwrap(aCallback->CallbackPreserveColor()); + nsIGlobalObject* globalObject = nullptr; + + JSContext* cx; + { + // Bug 955660: we cannot do "proper" rooting here because we need the + // global to get a context. Everything here is simple getters that cannot + // GC, so just paper over the necessary dataflow inversion. + JS::AutoSuppressGCAnalysis nogc; + + // Now get the global for this callback. Note that for the case of + // JS-implemented WebIDL we never have a window here. + nsGlobalWindow* win = mIsMainThread && !aIsJSImplementedWebIDL + ? xpc::WindowGlobalOrNull(realCallback) + : nullptr; + if (win) { + MOZ_ASSERT(win->IsInnerWindow()); + // We don't want to run script in windows that have been navigated away + // from. + if (!win->AsInner()->HasActiveDocument()) { + aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR, + NS_LITERAL_CSTRING("Refusing to execute function from window " + "whose document is no longer active.")); + return; + } + globalObject = win; + } else { + // No DOM Window. Store the global. + JSObject* global = js::GetGlobalForObjectCrossCompartment(realCallback); + globalObject = xpc::NativeGlobal(global); + MOZ_ASSERT(globalObject); + } + + // Bail out if there's no useful global. This seems to happen intermittently + // on gaia-ui tests, probably because nsInProcessTabChildGlobal is returning + // null in some kind of teardown state. + if (!globalObject->GetGlobalJSObject()) { + aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR, + NS_LITERAL_CSTRING("Refusing to execute function from global which is " + "being torn down.")); + return; + } + + 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->GetGlobalJSObject()) { + aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR, + NS_LITERAL_CSTRING("Refusing to execute function because our " + "incumbent global is being torn down.")); + return; + } + mAutoIncumbentScript.emplace(incumbent); + } + + cx = mAutoEntryScript->cx(); + + // Unmark the callable (by invoking Callback() 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->Callback()); + } + + // JS-implemented WebIDL is always OK to run, since it runs with Chrome + // privileges anyway. + if (mIsMainThread && !aIsJSImplementedWebIDL) { + // Check that it's ok to run this callback at all. + // Make sure to use realCallback to get the global of the callback object, + // not the wrapper. + bool allowed = xpc::Scriptability::Get(realCallback).Allowed(); + + if (!allowed) { + aRv.ThrowDOMException(NS_ERROR_DOM_NOT_SUPPORTED_ERR, + NS_LITERAL_CSTRING("Refusing to execute function from global in which " + "script is disabled.")); + return; + } + } + + mAsyncStack.emplace(cx, aCallback->GetCreationStack()); + if (*mAsyncStack) { + mAsyncStackSetter.emplace(cx, *mAsyncStack, aExecutionReason); + } + + // Enter the compartment of our callback, so we can actually work with it. + // + // Note that if the callback is a wrapper, this will not be the same + // compartment that we ended up in with mAutoEntryScript above, because the + // entry point is based off of the unwrapped callback (realCallback). + mAc.emplace(cx, *mRootedCallable); + + // And now we're ready to go. + mCx = cx; +} + +bool +CallbackObject::CallSetup::ShouldRethrowException(JS::Handle<JS::Value> aException) +{ + if (mExceptionHandling == eRethrowExceptions) { + if (!mCompartment) { + // Caller didn't ask us to filter for only exceptions we subsume. + return true; + } + + // On workers, we don't have nsIPrincipals to work with. But we also only + // have one compartment, so check whether mCompartment is the same as the + // current compartment of mCx. + if (mCompartment == js::GetContextCompartment(mCx)) { + return true; + } + + MOZ_ASSERT(NS_IsMainThread()); + + // At this point mCx is in the compartment of our unwrapped callback, so + // just check whether the principal of mCompartment subsumes that of the + // current compartment/global of mCx. + nsIPrincipal* callerPrincipal = + nsJSPrincipals::get(JS_GetCompartmentPrincipals(mCompartment)); + nsIPrincipal* calleePrincipal = nsContentUtils::SubjectPrincipal(); + if (callerPrincipal->SubsumesConsideringDomain(calleePrincipal)) { + return true; + } + } + + MOZ_ASSERT(mCompartment); + + // Now we only want to throw an exception to the caller if the object that was + // thrown is in the caller compartment (which we stored in mCompartment). + + if (!aException.isObject()) { + return false; + } + + JS::Rooted<JSObject*> obj(mCx, &aException.toObject()); + obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false); + return js::GetObjectCompartment(obj) == mCompartment; +} + +CallbackObject::CallSetup::~CallSetup() +{ + // To get our nesting right we have to destroy our JSAutoCompartment first. + // In particular, we want to do this before we try reporting any exceptions, + // so we end up reporting them while in the compartment of our entry point, + // not whatever cross-compartment wrappper mCallback might be. + // Be careful: the JSAutoCompartment might not have been constructed at all! + mAc.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 ((mCompartment && 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 ~mAc, 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 + // compartment and undoing all our entry/incumbent script changes + if (mIsMainThread) { + nsContentUtils::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->Callback()); + + JSAutoCompartment ac(cx, callback); + RefPtr<nsXPCWrappedJS> wrappedJS; + nsresult rv = + nsXPCWrappedJS::GetNewOrUsed(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 dom +} // namespace mozilla diff --git a/dom/bindings/CallbackObject.h b/dom/bindings/CallbackObject.h new file mode 100644 index 000000000..8a3d45dfc --- /dev/null +++ b/dom/bindings/CallbackObject.h @@ -0,0 +1,607 @@ +/* -*- 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 "nsISupports.h" +#include "nsISupportsImpl.h" +#include "nsCycleCollectionParticipant.h" +#include "jswrapper.h" +#include "mozilla/Assertions.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/HoldDropJSObjects.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/OwningNonNull.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsWrapperCache.h" +#include "nsJSEnvironment.h" +#include "xpcpublic.h" +#include "jsapi.h" +#include "js/TracingAPI.h" + +namespace mozilla { +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_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, + nsIGlobalObject* aIncumbentGlobal) + { + if (aCx && JS::ContextOptionsRef(aCx).asyncStack()) { + JS::RootedObject stack(aCx); + if (!JS::CaptureCurrentStack(aCx, &stack)) { + JS_ClearPendingException(aCx); + } + Init(aCallback, stack, aIncumbentGlobal); + } else { + Init(aCallback, 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(JS::Handle<JSObject*> aCallback, + JS::Handle<JSObject*> aAsyncStack, + nsIGlobalObject* aIncumbentGlobal) + { + Init(aCallback, aAsyncStack, aIncumbentGlobal); + } + + JS::Handle<JSObject*> Callback() const + { + mCallback.exposeToActiveJS(); + return CallbackPreserveColor(); + } + + JSObject* GetCreationStack() const + { + return mCreationStack; + } + + void MarkForCC() + { + mCallback.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. + * + * This should only be called if you are certain that the return value won't + * be passed into a JS API function and that it won't be stored without being + * rooted (or otherwise signaling the stored value to the CC). + */ + JS::Handle<JSObject*> CallbackPreserveColor() const + { + // Calling fromMarkedLocation() is safe because we trace our mCallback, and + // because the value of mCallback cannot change after if has been set. + return JS::Handle<JSObject*>::fromMarkedLocation(mCallback.address()); + } + + /* + * If the callback is known to be non-gray, then this method can be + * used instead of Callback() to avoid the overhead of + * ExposeObjectToActiveJS(). + */ + JS::Handle<JSObject*> CallbackKnownNotGray() const + { + MOZ_ASSERT(!JS::ObjectIsMarkedGray(mCallback)); + return CallbackPreserveColor(); + } + + nsIGlobalObject* IncumbentGlobalOrNull() const + { + return mIncumbentGlobal; + } + + enum ExceptionHandling { + // Report any exception and don't throw it to the caller code. + eReportExceptions, + // Throw an exception to the caller code if the thrown exception is a + // binding object for a DOMError or DOMException from the caller's scope, + // otherwise report it. + eRethrowContentExceptions, + // Throw exceptions to the caller code, unless the caller compartment is + // provided, the exception is not a DOMError or DOMException from the + // caller compartment, and the caller compartment does not subsume our + // unwrapped callback. + eRethrowExceptions + }; + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const + { + return aMallocSizeOf(this); + } + +protected: + virtual ~CallbackObject() + { + DropJSObjects(); + } + + explicit CallbackObject(CallbackObject* aCallbackObject) + { + Init(aCallbackObject->mCallback, aCallbackObject->mCreationStack, + aCallbackObject->mIncumbentGlobal); + } + + bool operator==(const CallbackObject& aOther) const + { + JSObject* thisObj = + js::UncheckedUnwrap(CallbackPreserveColor()); + JSObject* otherObj = + js::UncheckedUnwrap(aOther.CallbackPreserveColor()); + return thisObj == otherObj; + } + +private: + inline void InitNoHold(JSObject* aCallback, JSObject* aCreationStack, + nsIGlobalObject* aIncumbentGlobal) + { + MOZ_ASSERT(aCallback && !mCallback); + // Set script objects before we hold, on the off chance that a GC could + // somehow happen in there... (which would be pretty odd, granted). + mCallback = aCallback; + mCreationStack = aCreationStack; + if (aIncumbentGlobal) { + mIncumbentGlobal = aIncumbentGlobal; + mIncumbentJSGlobal = aIncumbentGlobal->GetGlobalJSObject(); + } + } + + inline void Init(JSObject* aCallback, JSObject* aCreationStack, + nsIGlobalObject* aIncumbentGlobal) + { + InitNoHold(aCallback, aCreationStack, aIncumbentGlobal); + mozilla::HoldJSObjects(this); + } + + inline void ClearJSReferences() + { + mCallback = nullptr; + mCreationStack = nullptr; + mIncumbentJSGlobal = nullptr; + } + + CallbackObject(const CallbackObject&) = delete; + CallbackObject& operator =(const CallbackObject&) = delete; + +protected: + void DropJSObjects() + { + MOZ_ASSERT_IF(mIncumbentJSGlobal, mCallback); + if (mCallback) { + ClearJSReferences(); + mozilla::DropJSObjects(this); + } + } + + // 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. If we have more than one owner, this will + // HoldJSObjects; otherwise it will just forget all our JS references. + void HoldJSObjectsIfMoreThanOneOwner(); + + // 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. If you use this, you MUST ensure + // that the object is traced until the HoldJSObjects happens! + CallbackObject(JSContext* aCx, JS::Handle<JSObject*> aCallback, + nsIGlobalObject* aIncumbentGlobal, + const FastCallbackConstructor&) + { + if (aCx && JS::ContextOptionsRef(aCx).asyncStack()) { + JS::RootedObject stack(aCx); + if (!JS::CaptureCurrentStack(aCx, &stack)) { + JS_ClearPendingException(aCx); + } + InitNoHold(aCallback, stack, aIncumbentGlobal); + } else { + InitNoHold(aCallback, nullptr, aIncumbentGlobal); + } + } + + // 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; + 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 aCompartment + // needs to be set to the compartment in which exceptions will be rethrown. + // + // If aExceptionHandling == eRethrowExceptions then aCompartment may be set + // to the compartment in which exceptions will be rethrown. In that case + // they will only be rethrown if that compartment's principal subsumes the + // principal of our (unwrapped) callback. + CallSetup(CallbackObject* aCallback, ErrorResult& aRv, + const char* aExecutionReason, + ExceptionHandling aExceptionHandling, + JSCompartment* aCompartment = nullptr, + bool aIsJSImplementedWebIDL = false); + ~CallSetup(); + + JSContext* GetContext() const + { + return mCx; + } + + 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 compartment. This will only have a sensible value if + // mExceptionHandling == eRethrowContentExceptions or eRethrowExceptions. + JSCompartment* mCompartment; + + // And now members whose construction/destruction order we need to control. + Maybe<AutoEntryScript> mAutoEntryScript; + Maybe<AutoIncumbentScript> mAutoIncumbentScript; + + Maybe<JS::Rooted<JSObject*> > mRootedCallable; + + // Members which are used to set the async stack. + Maybe<JS::Rooted<JSObject*>> mAsyncStack; + Maybe<JS::AutoSetAsyncStackForNewCalls> mAsyncStackSetter; + + // Can't construct a JSAutoCompartment without a JSContext either. Also, + // Put mAc after mAutoEntryScript so that we exit the compartment before + // we pop the JSContext. Though in practice we'll often manually order + // those two things. + Maybe<JSAutoCompartment> mAc; + + // 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; + + nsISupports* GetISupports() const + { + return reinterpret_cast<nsISupports*>(mPtrBits & ~XPCOMCallbackFlag); + } + + // 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)); + // ToXPCOMCallback already did the right QI for us. + return supp.forget().downcast<XPCOMCallbackT>(); + } + + // 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(); + NS_IF_RELEASE(ptr); + mPtrBits = 0; + } + + 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) +{ + 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. +template<typename T> +class RootedCallback : public JS::Rooted<T> +{ +public: + explicit RootedCallback(JSContext* cx) + : JS::Rooted<T>(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 Callback() on us. + JS::Handle<JSObject*> Callback() const + { + return this->get()->Callback(); + } + + ~RootedCallback() + { + // Ensure that our callback starts holding on to its own JS objects as + // needed. Having to null-check here when T is OwningNonNull is a bit + // silly, but it's simpler than creating two separate RootedCallback + // instantiations for OwningNonNull and RefPtr. + if (IsInitialized(this->get())) { + this->get()->HoldJSObjectsIfMoreThanOneOwner(); + } + } + +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(); + } +}; + +} // 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 000000000..3174c37dd --- /dev/null +++ b/dom/bindings/Codegen.py @@ -0,0 +1,17364 @@ +# 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 os +import re +import string +import math +import textwrap +import functools + +from WebIDL import BuiltinTypes, IDLBuiltinType, IDLNullValue, IDLSequenceType, IDLType, IDLAttribute, IDLInterfaceMember, IDLUndefinedValue, IDLEmptySequenceValue, IDLDictionary +from Configuration import NoSuchDescriptorError, getTypesFromDescriptor, getTypesFromDictionary, getTypesFromCallback, getAllTypes, Descriptor, MemberIsUnforgeable, iteratorNativeType + +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' +FINALIZE_HOOK_NAME = '_finalize' +OBJECT_MOVED_HOOK_NAME = '_objectMoved' +CONSTRUCT_HOOK_NAME = '_constructor' +LEGACYCALLER_HOOK_NAME = '_legacycaller' +HASINSTANCE_HOOK_NAME = '_hasInstance' +RESOLVE_HOOK_NAME = '_resolve' +MAY_RESOLVE_HOOK_NAME = '_mayResolve' +ENUMERATE_HOOK_NAME = '_enumerate' +ENUM_ENTRY_VARIABLE_NAME = 'strings' +INSTANCE_RESERVED_SLOTS = 1 + + +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): + 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.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())) + + +def idlTypeNeedsCycleCollection(type): + type = type.unroll() # Takes care of sequences and nullables + if ((type.isPrimitive() and type.tag() in builtinNames) or + type.isEnum() or + type.isString() or + type.isAny() or + type.isObject() or + type.isSpiderMonkeyInterface()): + return False + elif type.isCallback() or type.isGeckoInterface(): + return True + elif type.isUnion(): + return any(idlTypeNeedsCycleCollection(t) for t in type.flatMemberTypes) + elif type.isMozMap(): + if idlTypeNeedsCycleCollection(type.inner): + raise TypeError("Cycle collection for type %s is not supported" % type) + return False + elif type.isDictionary(): + if any(idlTypeNeedsCycleCollection(m.type) for m in type.inner.members): + raise TypeError("Cycle collection for type %s is not supported" % type) + return False + else: + raise TypeError("Don't know whether to cycle-collect type %s" % type) + + +def wantsAddProperty(desc): + return (desc.concrete and desc.wrapperCache and not desc.isGlobal()) + + +# 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 string table for the given strings with a function accessor: + + const char *accessorName(unsigned int index) { + static const char table[] = "..."; + static const uint16_t indices = { ... }; + return &table[indices[index]]; + } + + This is more efficient than the more natural: + + const char *table[] = { + ... + }; + + The uint16_t indices are smaller than the pointer equivalents, and the + string table requires no runtime relocations. + """ + def __init__(self, accessorName, strings): + CGThing.__init__(self) + self.accessorName = accessorName + self.strings = strings + + def declare(self): + return "extern const char *%s(unsigned int aIndex);\n" % self.accessorName + + def define(self): + table = ' "\\0" '.join('"%s"' % s for s in self.strings) + indices = [] + currentIndex = 0 + for s in self.strings: + indices.append(currentIndex) + currentIndex += len(s) + 1 # for the null terminator + return fill( + """ + const char *${name}(unsigned int aIndex) + { + static const char table[] = ${table}; + static const uint16_t indices[] = { ${indices} }; + static_assert(${currentIndex} <= UINT16_MAX, "string table overflow!"); + return &table[indices[aIndex]]; + } + """, + name=self.accessorName, + table=table, + indices=", ".join("%d" % index for index in indices), + currentIndex=currentIndex) + + +class CGNativePropertyHooks(CGThing): + """ + Generate a NativePropertyHooks for a given descriptor + """ + def __init__(self, descriptor, properties): + CGThing.__init__(self) + self.descriptor = descriptor + self.properties = properties + + def declare(self): + if not self.descriptor.wantsXrays: + return "" + return dedent(""" + // We declare this as an array so that retrieving a pointer to this + // binding's property hooks only requires compile/link-time resolvable + // address arithmetic. Declaring it as a pointer instead would require + // doing a run-time load to fetch a pointer to this binding's property + // hooks. And then structures which embedded a pointer to this structure + // would require a run-time load for proper initialization, which would + // then induce static constructors. Lots of static constructors. + extern const NativePropertyHooks sNativePropertyHooks[]; + """) + + def define(self): + if not self.descriptor.wantsXrays: + return "" + deleteNamedProperty = "nullptr" + if self.descriptor.concrete and self.descriptor.proxy: + resolveOwnProperty = "ResolveOwnProperty" + enumerateOwnProperties = "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" + parentProtoName = self.descriptor.parentPrototypeName + parentHooks = (toBindingNamespace(parentProtoName) + "::sNativePropertyHooks" + if parentProtoName else 'nullptr') + + if self.descriptor.wantsXrayExpandoClass: + expandoClass = "&sXrayExpandoObjectClass" + else: + expandoClass = "&DefaultXrayExpandoObjectClass" + + return fill( + """ + const NativePropertyHooks sNativePropertyHooks[] = { { + ${resolveOwnProperty}, + ${enumerateOwnProperties}, + ${deleteNamedProperty}, + { ${regular}, ${chrome} }, + ${prototypeID}, + ${constructorID}, + ${parentHooks}, + ${expandoClass} + } }; + """, + resolveOwnProperty=resolveOwnProperty, + enumerateOwnProperties=enumerateOwnProperties, + deleteNamedProperty=deleteNamedProperty, + regular=regular, + chrome=chrome, + prototypeID=prototypeID, + constructorID=constructorID, + parentHooks=parentHooks, + 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))) + + return fill( + """ + { ${protoChain} }, + IsBaseOf<nsISupports, ${nativeType} >::value, + ${hooks}, + FindAssociatedGlobalForNative<${nativeType}>::Get, + GetProtoObjectHandle, + GetCCParticipant<${nativeType}>::Get() + """, + protoChain=', '.join(protoList), + nativeType=descriptor.nativeType, + hooks=NativePropertyHooks(descriptor)) + + +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 = INSTANCE_RESERVED_SLOTS + self.descriptor.interface.totalMembersInSlots + 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.isProbablyShortLivingObject(): + classFlags += " | JSCLASS_SKIP_NURSERY_FINALIZE" + if self.descriptor.interface.getExtendedAttribute("NeedResolve"): + resolveHook = RESOLVE_HOOK_NAME + mayResolveHook = MAY_RESOLVE_HOOK_NAME + enumerateHook = ENUMERATE_HOOK_NAME + elif self.descriptor.isGlobal(): + resolveHook = "mozilla::dom::ResolveGlobal" + mayResolveHook = "mozilla::dom::MayResolveGlobal" + enumerateHook = "mozilla::dom::EnumerateGlobal" + else: + resolveHook = "nullptr" + mayResolveHook = "nullptr" + enumerateHook = "nullptr" + + return fill( + """ + static const js::ClassOps sClassOps = { + ${addProperty}, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + ${enumerate}, /* enumerate */ + ${resolve}, /* resolve */ + ${mayResolve}, /* mayResolve */ + ${finalize}, /* finalize */ + ${call}, /* call */ + nullptr, /* hasInstance */ + nullptr, /* construct */ + ${trace}, /* trace */ + }; + + static const js::ClassExtension sClassExtension = { + nullptr, /* weakmapKeyDelegateOp */ + ${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.identifier.name, + flags=classFlags, + addProperty=ADDPROPERTY_HOOK_NAME if wantsAddProperty(self.descriptor) else 'nullptr', + enumerate=enumerateHook, + 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): + flags = ["JSCLASS_IS_DOMJSCLASS"] + # 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") + objectMovedHook = OBJECT_MOVED_HOOK_NAME if self.descriptor.wrapperCache else 'nullptr' + return fill( + """ + static const js::ClassExtension sClassExtension = PROXY_MAKE_EXT( + ${objectMoved} + ); + + static const DOMJSClass sClass = { + PROXY_CLASS_WITH_EXT("${name}", + ${flags}, + &sClassExtension), + $*{descriptor} + }; + """, + name=self.descriptor.interface.identifier.name, + flags=" | ".join(flags), + objectMoved=objectMovedHook, + 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("ArrayClass"): + protoGetter = "JS::GetRealmArrayPrototype" + elif descriptor.interface.getExtendedAttribute("ExceptionClass"): + protoGetter = "JS::GetRealmErrorPrototype" + elif descriptor.interface.isIteratorInterface(): + protoGetter = "JS::GetRealmIteratorPrototype" + 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.hasUnforgeableMembers 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}, + "[object ${name}Prototype]", + ${protoGetter} + }; + """, + name=self.descriptor.interface.identifier.name, + slotCount=slotCount, + type=type, + hooks=NativePropertyHooks(self.descriptor), + prototypeID=prototypeID, + depth=depth, + protoGetter=protoGetter) + + +def NeedsGeneratedHasInstance(descriptor): + assert descriptor.interface.hasInterfaceObject() + return descriptor.hasXPConnectImpls or descriptor.interface.isConsequential() + + +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 = "binding_detail::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 = ( + not NeedsGeneratedHasInstance(self.descriptor) and + self.descriptor.interface.hasInterfacePrototypeObject()) + + prototypeID, depth = PrototypeIDAndDepth(self.descriptor) + slotCount = "DOM_INTERFACE_SLOTS_BASE" + if len(self.descriptor.interface.namedConstructors) > 0: + slotCount += (" + %i /* slots for the named constructors */" % + len(self.descriptor.interface.namedConstructors)) + (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 js::ClassOps sInterfaceObjectClassOps = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* enumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + nullptr, /* finalize */ + ${ctorname}, /* call */ + nullptr, /* hasInstance */ + ${ctorname}, /* construct */ + nullptr, /* trace */ + }; + + """, + ctorname=ctorname) + classOpsPtr = "&sInterfaceObjectClassOps" + + if self.descriptor.interface.isNamespace(): + classString = self.descriptor.interface.getExtendedAttribute("ClassString") + if classString is None: + classString = "Object" + else: + classString = classString[0] + toStringResult = "[object %s]" % classString + objectOps = "JS_NULL_OBJECT_OPS" + else: + classString = "Function" + toStringResult = ("function %s() {\\n [native code]\\n}" % + self.descriptor.interface.identifier.name) + # We need non-default ObjectOps so we can actually make + # use of our toStringResult. + 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} + }, + eInterface, + ${needsHasInstance}, + ${prototypeID}, + ${depth}, + ${hooks}, + "${toStringResult}", + ${protoGetter} + }; + """, + classString=classString, + slotCount=slotCount, + classOpsPtr=classOpsPtr, + hooks=NativePropertyHooks(self.descriptor), + objectOps=objectOps, + needsHasInstance=toStringBool(needsHasInstance), + prototypeID=prototypeID, + depth=depth, + toStringResult=toStringResult, + 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): + 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(CGWrapper): + def __init__(self, namespace, child, declareOnly=False): + pre = "namespace %s {\n" % namespace + post = "} // namespace %s\n" % namespace + CGWrapper.__init__(self, child, pre=pre, post=post, + declareOnly=declareOnly) + + @staticmethod + def build(namespaces, child, declareOnly=False): + """ + Static helper method to build multiple wrapped namespaces. + """ + if not namespaces: + return CGWrapper(child, declareOnly=declareOnly) + inner = CGNamespace.build(namespaces[1:], child, declareOnly=declareOnly) + return CGNamespace(namespaces[0], inner, declareOnly=declareOnly) + + +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) + 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()) + + # Grab the includes for checking hasInstance + interfacesImplementingSelf = set() + for d in descriptors: + interfacesImplementingSelf |= d.interface.interfacesImplementingSelf + implementationIncludes |= set(self.getDeclarationFilename(i) for i in + interfacesImplementingSelf) + + # Grab the includes for the things that involve XPCOM interfaces + hasInstanceIncludes = set("nsIDOM" + d.interface.identifier.name + ".h" for d + in descriptors if + d.interface.hasInterfaceObject() and + NeedsGeneratedHasInstance(d) and + d.interface.hasInterfacePrototypeObject()) + if len(hasInstanceIncludes) > 0: + hasInstanceIncludes.add("nsContentUtils.h") + + # 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((t, dictionary)): + """ + Add the relevant headers for this type. We use dictionary, if + passed, to decide what to do with interface types. + """ + # Dictionaries have members that need to be actually + # declared, not just forward-declared. + if dictionary: + headerSet = declareIncludes + else: + headerSet = bindingHeaders + if t.nullable(): + # Need to make sure that Nullable as a dictionary + # member works. + headerSet.add("mozilla/dom/Nullable.h") + unrolled = t.unroll() + if unrolled.isUnion(): + headerSet.add(self.getUnionDeclarationFilename(config, unrolled)) + bindingHeaders.add("mozilla/dom/UnionConversions.h") + elif unrolled.isDate(): + if dictionary or jsImplementedDescriptors: + declareIncludes.add("mozilla/dom/Date.h") + else: + bindingHeaders.add("mozilla/dom/Date.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)) + 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.isMozMap(): + if dictionary or jsImplementedDescriptors: + declareIncludes.add("mozilla/dom/MozMap.h") + else: + bindingHeaders.add("mozilla/dom/MozMap.h") + # Also add headers for the type the MozMap is + # parametrized over, if needed. + addHeadersForType((t.inner, dictionary)) + + map(addHeadersForType, + getAllTypes(descriptors + callbackDescriptors, dictionaries, + callbacks)) + + # Now make sure we're not trying to include the header from inside itself + declareIncludes.discard(prefix + ".h") + + 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 interface generated for a seperate + # iterable interface, skip generating type includes, as we have + # what we need in IterableIterator.h + if desc.interface.isExternal() or desc.interface.isIteratorInterface(): + 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) + + # Let the machinery do its thing. + def _includeString(includes): + return ''.join(['#include "%s"\n' % i for i in includes]) + '\n' + CGWrapper.__init__(self, child, + declarePre=_includeString(sorted(declareIncludes)), + definePre=_includeString(sorted(set(defineIncludes) | + bindingIncludes | + bindingHeaders | + hasInstanceIncludes | + 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() + f = f.unroll() + if f.isInterface(): + if f.isSpiderMonkeyInterface(): + headers.add("jsfriendapi.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) + 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.isEnum(): + # Need to see the actual definition of the enum, + # unfortunately. + headers.add(CGHeaders.getDeclarationFilename(f.inner)) + 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.isMozMap(): + headers.add("mozilla/dom/MozMap.h") + # And add headers for the type we're parametrized over + addHeadersForType(f.inner) + + 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)) + + +def UnionConversions(unionTypes, config): + """ + The unionTypes argument should be a list of tuples, each containing two + elements: a union type and a descriptor. This is typically the list + generated by UnionsForFile. + + Returns a tuple containing a list of headers and a CGThing to declare all + union argument conversion helper structs. + """ + headers = set() + unionConversions = dict() + + for t in unionTypes: + name = str(t) + if name not in unionConversions: + unionConversions[name] = CGUnionConversionStruct(t, config) + + def addHeadersForType(f): + f = f.unroll() + if f.isInterface(): + if f.isSpiderMonkeyInterface(): + headers.add("jsfriendapi.h") + headers.add("mozilla/dom/TypedArray.h") + elif f.inner.isExternal(): + try: + typeDesc = config.getDescriptor(f.inner.identifier.name) + except NoSuchDescriptorError: + return + headers.add(typeDesc.headerFile) + else: + headers.add(CGHeaders.getDeclarationFilename(f.inner)) + elif f.isDictionary(): + headers.add(CGHeaders.getDeclarationFilename(f.inner)) + elif f.isPrimitive(): + headers.add("mozilla/dom/PrimitiveConversions.h") + elif f.isMozMap(): + headers.add("mozilla/dom/MozMap.h") + # And the internal type of the MozMap + addHeadersForType(f.inner) + + # We plan to include UnionTypes.h no matter what, so it's + # OK if we throw it into the set here. + headers.add(CGHeaders.getUnionDeclarationFilename(config, t)) + + for f in t.flatMemberTypes: + addHeadersForType(f) + + return (headers, + CGWrapper(CGList(SortedDictValues(unionConversions), "\n"), + post="\n\n")) + + +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. + """ + def __init__(self, descriptor, name, returnType, args, inline=False, alwaysInline=False, static=False, templateArgs=None): + 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 + + 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.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 declare(self): + if self.inline: + return self._define(True) + return "%s%s%s(%s);\n" % (self._template(), self._decorators(), self.name, self._argstring(True)) + + 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): + return "" if self.inline else self._define() + + def definition_prologue(self, fromDeclare): + return "%s%s%s(%s)\n{\n" % (self._template(), self._decorators(), + self.name, self._argstring(fromDeclare)) + + def definition_epilogue(self): + return "}\n" + + def definition_body(self): + assert False # Override me! + + +class CGAbstractStaticMethod(CGAbstractMethod): + """ + Abstract base class for codegen of implementation-only (no + declaration) static methods. + """ + def __init__(self, descriptor, name, returnType, args): + CGAbstractMethod.__init__(self, descriptor, name, returnType, args, + inline=False, static=True) + + def declare(self): + # We only have implementation + return "" + + +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 CGGetJSClassMethod(CGAbstractMethod): + def __init__(self, descriptor): + CGAbstractMethod.__init__(self, descriptor, 'GetJSClass', 'const JSClass*', + []) + + def definition_body(self): + return "return sClass.ToJSClass();\n" + + +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 + 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; + """) + + +def finalizeHook(descriptor, hookName, freeOp): + finalize = "" + if descriptor.wrapperCache: + finalize += "ClearWrapper(self, self);\n" + if descriptor.interface.getExtendedAttribute('OverrideBuiltins'): + finalize += "self->mExpandoAndGeneration.expando = JS::UndefinedValue();\n" + if descriptor.isGlobal(): + finalize += "mozilla::dom::FinalizeGlobal(CastToJSFreeOp(%s), obj);\n" % freeOp + 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::FreeOp*', 'fop'), 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).define() + + +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('const JSObject*', 'old')] + CGAbstractClassHook.__init__(self, descriptor, OBJECT_MOVED_HOOK_NAME, + 'void', args) + + def generate_code(self): + assert self.descriptor.wrapperCache + return CGIfWrapper(CGGeneric("UpdateWrapper(self, self, obj, old);\n"), + "self").define() + + +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): + # [ChromeOnly] interfaces may only be constructed by chrome. + chromeOnlyCheck = "" + if isChromeOnly(self._ctor): + chromeOnlyCheck = dedent(""" + if (!nsContentUtils::ThreadsafeIsCallerChrome()) { + return ThrowingConstructor(cx, argc, vp); + } + + """) + + # Additionally, we want to throw if a caller does a bareword invocation + # of a constructor without |new|. We don't enforce this for chrome in + # realease builds to avoid the addon compat fallout of making that + # change. See bug 916644. + # + # Figure out the name of our constructor for error reporting purposes. + # For unnamed webidl constructors, identifier.name is "constructor" but + # the name JS sees is the interface name; for named constructors + # identifier.name is the actual name. + name = self._ctor.identifier.name + if name != "constructor": + ctorName = name + else: + ctorName = self.descriptor.interface.identifier.name + + preamble = fill( + """ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::Rooted<JSObject*> obj(cx, &args.callee()); + $*{chromeOnlyCheck} + if (!args.isConstructing()) { + // XXXbz wish I could get the name from the callee instead of + // Adding more relocations + return ThrowConstructorWithoutNew(cx, "${ctorName}"); + } + JS::Rooted<JSObject*> desiredProto(cx); + if (!GetDesiredProto(cx, args, &desiredProto)) { + return false; + } + """, + chromeOnlyCheck=chromeOnlyCheck, + ctorName=ctorName) + + name = self._ctor.identifier.name + nativeName = MakeNativeName(self.descriptor.binaryNameFor(name)) + callGenerator = CGMethodCall(nativeName, True, self.descriptor, + self._ctor, isConstructor=True, + constructorName=ctorName) + return preamble + "\n" + callGenerator.define() + + +# Encapsulate the constructor in a helper method to share genConstructorBody with CGJSImplMethod. +class CGConstructNavigatorObject(CGAbstractMethod): + """ + Construct a new JS-implemented WebIDL DOM object, for use on navigator. + """ + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle<JSObject*>', 'obj'), + Argument('ErrorResult&', 'aRv')] + rtype = 'already_AddRefed<%s>' % descriptor.name + CGAbstractMethod.__init__(self, descriptor, "ConstructNavigatorObject", + rtype, args) + + def definition_body(self): + if not self.descriptor.interface.isJSImplemented(): + raise TypeError("Only JS-implemented classes are currently supported " + "on navigator. See bug 856820.") + + return dedent( + """ + GlobalObject global(cx, obj); + if (global.Failed()) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + """) + genConstructorBody(self.descriptor) + + +def NamedConstructorName(m): + return '_' + m.identifier.name + + +class CGNamedConstructors(CGThing): + def __init__(self, descriptor): + self.descriptor = descriptor + CGThing.__init__(self) + + def declare(self): + return "" + + def define(self): + if len(self.descriptor.interface.namedConstructors) == 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.namedConstructors: + namedConstructors += ( + "{ \"%s\", { %s, &sNamedConstructorNativePropertyHooks }, %i },\n" % + (n.identifier.name, NamedConstructorName(n), methodLength(n))) + + return fill( + """ + const NativePropertyHooks sNamedConstructorNativePropertyHooks = { + nullptr, + nullptr, + nullptr, + { nullptr, nullptr }, + prototypes::id::${name}, + ${constructorID}, + nullptr + }; + + static const NamedConstructor namedConstructors[] = { + $*{namedConstructors} + { nullptr, { nullptr, nullptr }, 0 } + }; + """, + name=self.descriptor.name, + constructorID=constructorID, + namedConstructors=namedConstructors) + + +class CGHasInstanceHook(CGAbstractStaticMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), + Argument('unsigned', 'argc'), + Argument('JS::Value*', 'vp')] + assert descriptor.interface.hasInterfaceObject() + assert NeedsGeneratedHasInstance(descriptor) + CGAbstractStaticMethod.__init__(self, descriptor, HASINSTANCE_HOOK_NAME, + 'bool', args) + + def define(self): + return CGAbstractStaticMethod.define(self) + + def definition_body(self): + return self.generate_code() + + def generate_code(self): + header = dedent(""" + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + if (!args.get(0).isObject()) { + args.rval().setBoolean(false); + return true; + } + + JS::Rooted<JSObject*> instance(cx, &args[0].toObject()); + """) + if self.descriptor.interface.hasInterfacePrototypeObject(): + return ( + header + + fill( + """ + + static_assert(IsBaseOf<nsISupports, ${nativeType}>::value, + "HasInstance only works for nsISupports-based classes."); + + bool ok = InterfaceHasInstance(cx, argc, vp); + if (!ok || args.rval().toBoolean()) { + return ok; + } + + // FIXME Limit this to chrome by checking xpc::AccessCheck::isChrome(obj). + nsCOMPtr<nsISupports> native = + xpc::UnwrapReflectorToISupports(js::UncheckedUnwrap(instance, /* stopAtWindowProxy = */ false)); + nsCOMPtr<nsIDOM${name}> qiResult = do_QueryInterface(native); + args.rval().setBoolean(!!qiResult); + return true; + """, + nativeType=self.descriptor.nativeType, + name=self.descriptor.interface.identifier.name)) + + hasInstanceCode = dedent(""" + + const DOMJSClass* domClass = GetDOMClass(js::UncheckedUnwrap(instance, /* stopAtWindowProxy = */ false)); + if (!domClass) { + // Not a DOM object, so certainly not an instance of this interface + args.rval().setBoolean(false); + return true; + } + """) + if self.descriptor.interface.identifier.name == "ChromeWindow": + setRval = "args.rval().setBoolean(UnwrapDOMObject<nsGlobalWindow>(js::UncheckedUnwrap(instance, /* stopAtWindowProxy = */ false))->IsChromeWindow())" + else: + setRval = "args.rval().setBoolean(true)" + # Sort interaces implementing self by name so we get stable output. + for iface in sorted(self.descriptor.interface.interfacesImplementingSelf, + key=lambda iface: iface.identifier.name): + hasInstanceCode += fill( + """ + + if (domClass->mInterfaceChain[PrototypeTraits<prototypes::id::${name}>::Depth] == prototypes::id::${name}) { + ${setRval}; + return true; + } + """, + name=iface.identifier.name, + setRval=setRval) + hasInstanceCode += ("args.rval().setBoolean(false);\n" + "return true;\n") + return header + hasInstanceCode + + +def isChromeOnly(m): + return m.getExtendedAttribute("ChromeOnly") + + +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. + """ + def __init__(self, pref=None, func=None, secureContext=False, + nonExposedGlobals=None): + assert pref is None or isinstance(pref, str) + assert func is None or isinstance(func, str) + assert isinstance(secureContext, bool) + assert nonExposedGlobals is None or isinstance(nonExposedGlobals, set) + self.pref = pref + 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" + + def __eq__(self, other): + return (self.pref == other.pref and self.func == other.func and + self.secureContext == other.secureContext and + self.nonExposedGlobals == other.nonExposedGlobals) + + 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") + + +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 + # self.prefCacheData will store an array of (prefname, bool*) + # pairs for our bool var caches. generateArray will fill it + # in as needed. + self.prefCacheData = [] + + 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 __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), + self.usedForXrays()) + if self.hasChromeOnly(): + str += self.generateArray(self.chrome, self.variableName(True), + self.usedForXrays()) + 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 + + return MemberCondition( + PropertyDefiner.getStringAttr(interfaceMember, + "Pref"), + PropertyDefiner.getStringAttr(interfaceMember, + "Func"), + interfaceMember.getExtendedAttribute("SecureContext") is not None, + nonExposureSet) + + def generatePrefableArray(self, array, name, specFormatter, specTerminator, + specType, getCondition, getDataTuple, doIdArrays): + """ + 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 + # So we won't put a specTerminator at the very front of the list: + lastCondition = getCondition(array[0], self.descriptor) + + specs = [] + disablers = [] + prefableSpecs = [] + + disablersTemplate = dedent( + """ + static PrefableDisablers %s_disablers%d = { + true, %s, %s, %s + }; + """) + prefableWithDisablersTemplate = ' { &%s_disablers%d, &%s_specs[%d] }' + prefableWithoutDisablersTemplate = ' { nullptr, &%s_specs[%d] }' + prefCacheTemplate = '&%s[%d].disablers->enabled' + + def switchToCondition(props, condition): + # Remember the info about where our pref-controlled + # booleans live. + if condition.pref is not None: + props.prefCacheData.append( + (condition.pref, + prefCacheTemplate % (name, len(prefableSpecs)))) + # 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), + toStringBool(condition.secureContext), + condition.nonExposedGlobals, + condition.func)) + else: + prefableSpecs.append(prefableWithoutDisablersTemplate % + (name, len(specs))) + + switchToCondition(self, lastCondition) + + for member in array: + curCondition = getCondition(member, self.descriptor) + if lastCondition != curCondition: + # Terminate previous list + specs.append(specTerminator) + # And switch to our new condition + switchToCondition(self, curCondition) + lastCondition = curCondition + # And the actual spec + specs.append(specFormatter(getDataTuple(member))) + specs.append(specTerminator) + prefableSpecs.append(" { nullptr, nullptr }") + + specType = "const " + specType + arrays = fill( + """ + static ${specType} ${name}_specs[] = { + ${specs} + }; + + ${disablers} + // Can't be const because the pref-enabled boolean needs to be writable + static Prefable<${specType}> ${name}[] = { + ${prefableSpecs} + }; + + """, + specType=specType, + name=name, + disablers='\n'.join(disablers), + specs=',\n'.join(specs), + prefableSpecs=',\n'.join(prefableSpecs)) + if doIdArrays: + arrays += "static jsid %s_ids[%i];\n\n" % (name, len(specs)) + 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 MakeJSImplClearCachedValueNativeName(member): + return "_" + MakeClearCachedValueNativeName(member) + + +def IDLToCIdentifier(name): + return name.replace("-", "_") + + +class MethodDefiner(PropertyDefiner): + """ + A class for defining methods on a prototype object. + """ + def __init__(self, descriptor, name, 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 + MemberIsUnforgeable(m, descriptor) == unforgeable and + not m.isIdentifierLess()] + else: + methods = [] + self.chrome = [] + self.regular = [] + for m in methods: + if m.identifier.name == 'queryInterface': + if m.isStatic(): + raise TypeError("Legacy queryInterface member shouldn't be static") + signatures = m.signatures() + + def argTypeIsIID(arg): + return arg.type.inner.isExternal() and arg.type.inner.identifier.name == 'IID' + if len(signatures) > 1 or len(signatures[0][1]) > 1 or not argTypeIsIID(signatures[0][1][0]): + raise TypeError("There should be only one queryInterface method with 1 argument of type IID") + + # Make sure to not stick QueryInterface on abstract interfaces that + # have hasXPConnectImpls (like EventTarget). So only put it on + # interfaces that are concrete and all of whose ancestors are abstract. + def allAncestorsAbstract(iface): + if not iface.parent: + return True + desc = self.descriptor.getDescriptor(iface.parent.identifier.name) + if desc.concrete: + return False + return allAncestorsAbstract(iface.parent) + if (not self.descriptor.interface.hasInterfacePrototypeObject() or + not self.descriptor.concrete or + not allAncestorsAbstract(self.descriptor.interface)): + raise TypeError("QueryInterface is only supported on " + "interfaces that are concrete and all " + "of whose ancestors are abstract: " + + self.descriptor.name) + condition = "WantsQueryInterface<%s>::Enabled" % descriptor.nativeType + self.regular.append({ + "name": 'QueryInterface', + "methodInfo": False, + "length": 1, + "flags": "0", + "condition": MemberCondition(func=condition) + }) + continue + + # Iterable methods should be enumerable, maplike/setlike methods + # should not. + isMaplikeOrSetlikeMethod = (m.isMaplikeOrSetlikeOrIterableMethod() and + (m.maplikeOrSetlikeOrIterable.isMaplike() or + m.maplikeOrSetlikeOrIterable.isSetlike())) + method = { + "name": m.identifier.name, + "methodInfo": not m.isStatic(), + "length": methodLength(m), + # Methods generated for a maplike/setlike declaration are not + # enumerable. + "flags": "JSPROP_ENUMERATE" if not isMaplikeOrSetlikeMethod else "0", + "condition": PropertyDefiner.getControllingCondition(m, descriptor), + "allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable"), + "returnsPromise": m.returnsPromise(), + "hasIteratorAlias": "@@iterator" in m.aliases + } + + 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": "JSPROP_ENUMERATE", + "condition": MemberCondition() + }) + + if (static and + not unforgeable and + descriptor.interface.hasInterfaceObject() and + NeedsGeneratedHasInstance(descriptor)): + self.regular.append({ + "name": "@@hasInstance", + "methodInfo": False, + "nativeName": HASINSTANCE_HOOK_NAME, + "length": 1, + # Flags match those of Function[Symbol.hasInstance] + "flags": "JSPROP_READONLY | JSPROP_PERMANENT", + "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(m, + descriptor) + }) + self.regular.append({ + "name": "values", + "methodInfo": False, + "selfHostedName": "ArrayValues", + "length": 0, + "flags": "JSPROP_ENUMERATE", + "condition": PropertyDefiner.getControllingCondition(m, + descriptor) + }) + self.regular.append({ + "name": "entries", + "methodInfo": False, + "selfHostedName": "ArrayEntries", + "length": 0, + "flags": "JSPROP_ENUMERATE", + "condition": PropertyDefiner.getControllingCondition(m, + descriptor) + }) + self.regular.append({ + "name": "forEach", + "methodInfo": False, + "selfHostedName": "ArrayForEach", + "length": 1, + "flags": "JSPROP_ENUMERATE", + "condition": PropertyDefiner.getControllingCondition(m, + descriptor) + }) + + if not static: + stringifier = descriptor.operations['Stringifier'] + if (stringifier and + unforgeable == MemberIsUnforgeable(stringifier, descriptor)): + toStringDesc = { + "name": "toString", + "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) + jsonifier = descriptor.operations['Jsonifier'] + if (jsonifier and + unforgeable == MemberIsUnforgeable(jsonifier, descriptor)): + toJSONDesc = { + "name": "toJSON", + "nativeName": jsonifier.identifier.name, + "length": 0, + "flags": "JSPROP_ENUMERATE", + "condition": PropertyDefiner.getControllingCondition(jsonifier, descriptor) + } + if isChromeOnly(jsonifier): + self.chrome.append(toJSONDesc) + else: + self.regular.append(toJSONDesc) + if (unforgeable and + descriptor.interface.getExtendedAttribute("Unforgeable")): + # Synthesize our valueOf method + self.regular.append({ + "name": 'valueOf', + "nativeName": "UnforgeableValueOf", + "methodInfo": False, + "length": 0, + "flags": "JSPROP_ENUMERATE", # 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() + }) + else: + for m in clearableCachedAttrs(descriptor): + attrName = MakeNativeName(m.identifier.name) + self.chrome.append({ + "name": "_clearCached%sValue" % attrName, + "nativeName": MakeJSImplClearCachedValueNativeName(m), + "methodInfo": False, + "length": "0", + "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() + + def generateArray(self, array, name, doIdArrays): + if len(array) == 0: + return "" + + def condition(m, d): + return m["condition"] + + def flags(m): + unforgeable = " | JSPROP_PERMANENT | JSPROP_READONLY" if self.unforgeable else "" + return m["flags"] + unforgeable + + def specData(m): + 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): + # Cast this in case the methodInfo is a + # JSTypedMethodJitInfo. + jitinfo = ("reinterpret_cast<const JSJitInfo*>(&%s_methodinfo)" % accessor) + if m.get("allowCrossOriginThis", False): + if m.get("returnsPromise", False): + raise TypeError("%s returns a Promise but should " + "be allowed cross-origin?" % + accessor) + accessor = "genericCrossOriginMethod" + elif self.descriptor.needsSpecialGenericOps(): + if m.get("returnsPromise", False): + accessor = "genericPromiseReturningMethod" + else: + accessor = "genericMethod" + elif m.get("returnsPromise", False): + accessor = "GenericPromiseReturningBindingMethod" + else: + accessor = "GenericBindingMethod" + else: + if m.get("returnsPromise", False): + jitinfo = "&%s_methodinfo" % accessor + accessor = "StaticMethodPromiseWrapper" + else: + jitinfo = "nullptr" + + return (m["name"], accessor, jitinfo, m["length"], flags(m), selfHostedName) + + 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 + + return self.generatePrefableArray( + array, name, + formatSpec, + ' JS_FS_END', + 'JSFunctionSpec', + condition, specData, doIdArrays) + + +def IsCrossOriginWritable(attr, descriptor): + """ + Return whether the IDLAttribute in question is cross-origin writable on the + interface represented by descriptor. This is needed to handle the fact that + some, but not all, interfaces implementing URLUtils want a cross-origin + writable .href. + """ + crossOriginWritable = attr.getExtendedAttribute("CrossOriginWritable") + if not crossOriginWritable: + return False + if crossOriginWritable is True: + return True + assert (isinstance(crossOriginWritable, list) and + len(crossOriginWritable) == 1) + return crossOriginWritable[0] == descriptor.interface.identifier.name + +def isNonExposedNavigatorObjectGetter(attr, descriptor): + return (attr.navigatorObjectGetter and + not descriptor.getDescriptor(attr.type.inner.identifier.name).register) + +class AttrDefiner(PropertyDefiner): + def __init__(self, descriptor, name, 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: + attributes = [m for m in descriptor.interface.members if + m.isAttr() and m.isStatic() == static and + MemberIsUnforgeable(m, descriptor) == unforgeable and + not isNonExposedNavigatorObjectGetter(m, descriptor)] + else: + attributes = [] + self.chrome = [m for m in attributes if isChromeOnly(m)] + self.regular = [m for m in attributes if not isChromeOnly(m)] + self.static = static + self.unforgeable = unforgeable + + 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() + + def generateArray(self, array, name, doIdArrays): + if len(array) == 0: + return "" + + def flags(attr): + unforgeable = " | JSPROP_PERMANENT" if self.unforgeable else "" + # Attributes generated as part of a maplike/setlike declaration are + # not enumerable. + enumerable = " | JSPROP_ENUMERATE" if not attr.isMaplikeOrSetlikeAttr() else "" + return ("JSPROP_SHARED" + enumerable + unforgeable) + + def getter(attr): + if self.static: + accessor = 'get_' + IDLToCIdentifier(attr.identifier.name) + jitinfo = "nullptr" + else: + if attr.hasLenientThis(): + accessor = "genericLenientGetter" + elif attr.getExtendedAttribute("CrossOriginReadable"): + accessor = "genericCrossOriginGetter" + elif self.descriptor.needsSpecialGenericOps(): + accessor = "genericGetter" + else: + accessor = "GenericBindingGetter" + 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("LenientSetter") is None): + return "JSNATIVE_WRAPPER(nullptr)" + if self.static: + accessor = 'set_' + IDLToCIdentifier(attr.identifier.name) + jitinfo = "nullptr" + else: + if attr.hasLenientThis(): + accessor = "genericLenientSetter" + elif IsCrossOriginWritable(attr, self.descriptor): + accessor = "genericCrossOriginSetter" + elif self.descriptor.needsSpecialGenericOps(): + accessor = "genericSetter" + else: + accessor = "GenericBindingSetter" + jitinfo = "&%s_setterinfo" % IDLToCIdentifier(attr.identifier.name) + return "{ { %s, %s } }" % \ + (accessor, jitinfo) + + def specData(attr): + return (attr.identifier.name, flags(attr), getter(attr), + setter(attr)) + + return self.generatePrefableArray( + array, name, + lambda fields: ' { "%s", %s, { { %s, %s } } }' % fields, + ' JS_PS_END', + 'JSPropertySpec', + PropertyDefiner.getControllingCondition, specData, doIdArrays) + + +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, doIdArrays): + if len(array) == 0: + return "" + + def specData(const): + return (const.identifier.name, + convertConstIDLValueToJSVal(const.value)) + + return self.generatePrefableArray( + array, name, + lambda fields: ' { "%s", %s }' % fields, + ' { 0, JS::UndefinedValue() }', + 'ConstantSpec', + PropertyDefiner.getControllingCondition, specData, doIdArrays) + + +class PropertyArrays(): + def __init__(self, descriptor): + self.staticMethods = MethodDefiner(descriptor, "StaticMethods", + static=True) + self.staticAttrs = AttrDefiner(descriptor, "StaticAttributes", + static=True) + self.methods = MethodDefiner(descriptor, "Methods", static=False) + self.attrs = AttrDefiner(descriptor, "Attributes", static=False) + self.unforgeableMethods = MethodDefiner(descriptor, "UnforgeableMethods", + static=False, unforgeable=True) + self.unforgeableAttrs = AttrDefiner(descriptor, "UnforgeableAttributes", + 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 CGNativeProperties(CGList): + def __init__(self, descriptor, properties): + def generateNativeProperties(name, chrome): + def check(p): + return p.hasChromeOnly() if chrome else p.hasNonChromeOnly() + + nativePropsInts = [] + nativePropsTrios = [] + + iteratorAliasIndex = -1 + for index, item in enumerate(properties.methods.regular): + if item.get("hasIteratorAlias"): + iteratorAliasIndex = index + break + nativePropsInts.append(CGGeneric(str(iteratorAliasIndex))) + + offset = 0 + for array in properties.arrayNames(): + propertyArray = getattr(properties, array) + if check(propertyArray): + varName = propertyArray.variableName(chrome) + bitfields = "true, %d /* %s */" % (offset, varName) + offset += 1 + nativePropsInts.append(CGGeneric(bitfields)) + + if propertyArray.usedForXrays(): + ids = "%(name)s_ids" + else: + ids = "nullptr" + trio = "{ %(name)s, " + ids + ", %(name)s_specs }" + trio = trio % {'name': varName} + nativePropsTrios.append(CGGeneric(trio)) + else: + bitfields = "false, 0" + nativePropsInts.append(CGGeneric(bitfields)) + + nativePropsTrios = \ + [CGWrapper(CGIndenter(CGList(nativePropsTrios, ",\n")), + pre='{\n', post='\n}')] + nativeProps = nativePropsInts + nativePropsTrios + pre = ("static const NativePropertiesN<%d> %s = {\n" % + (offset, name)) + return CGWrapper(CGIndenter(CGList(nativeProps, ",\n")), + pre=pre, post="\n};\n") + + 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 CGJsonifyAttributesMethod(CGAbstractMethod): + """ + Generate the JsonifyAttributes method for an interface descriptor + """ + def __init__(self, descriptor): + args = [Argument('JSContext*', 'aCx'), + Argument('JS::Handle<JSObject*>', 'obj'), + Argument('%s*' % descriptor.nativeType, 'self'), + Argument('JS::Rooted<JSObject*>&', 'aResult')] + CGAbstractMethod.__init__(self, descriptor, 'JsonifyAttributes', 'bool', args) + + def definition_body(self): + ret = '' + interface = self.descriptor.interface + for m in interface.members: + if m.isAttr() and not m.isStatic() and m.type.isSerializable(): + ret += fill( + """ + { // scope for "temp" + JS::Rooted<JS::Value> temp(aCx); + if (!get_${name}(aCx, obj, self, JSJitGetterCallArgs(&temp))) { + return false; + } + if (!JS_DefineProperty(aCx, aResult, "${name}", temp, JSPROP_ENUMERATE)) { + return false; + } + } + """, + name=IDLToCIdentifier(m.identifier.name)) + ret += 'return true;\n' + 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): + args = [Argument('JSContext*', 'aCx'), + Argument('JS::Handle<JSObject*>', 'aGlobal'), + Argument('ProtoAndIfaceCache&', 'aProtoAndIfaceCache'), + Argument('bool', 'aDefineOnGlobal')] + CGAbstractMethod.__init__(self, descriptor, 'CreateInterfaceObjects', 'void', args) + self.properties = properties + self.haveUnscopables = haveUnscopables + + 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) + + idsToInit = [] + # There is no need to init any IDs in bindings that don't want Xrays. + if self.descriptor.wantsXrays: + for var in self.properties.arrayNames(): + props = getattr(self.properties, var) + # We only have non-chrome ids to init if we have no chrome ids. + if props.hasChromeOnly(): + idsToInit.append(props.variableName(True)) + if props.hasNonChromeOnly(): + idsToInit.append(props.variableName(False)) + if len(idsToInit) > 0: + initIdCalls = ["!InitIds(aCx, %s, %s_ids)" % (varname, varname) + for varname in idsToInit] + idsInitedFlag = CGGeneric("static bool sIdsInited = false;\n") + setFlag = CGGeneric("sIdsInited = true;\n") + initIdConditionals = [CGIfWrapper(CGGeneric("return;\n"), call) + for call in initIdCalls] + initIds = CGList([idsInitedFlag, + CGIfWrapper(CGList(initIdConditionals + [setFlag]), + "!sIdsInited && NS_IsMainThread()")]) + else: + initIds = None + + prefCacheData = [] + for var in self.properties.arrayNames(): + props = getattr(self.properties, var) + prefCacheData.extend(props.prefCacheData) + if len(prefCacheData) != 0: + prefCacheData = [ + CGGeneric('Preferences::AddBoolVarCache(%s, "%s");\n' % (ptr, pref)) + for pref, ptr in prefCacheData] + prefCache = CGWrapper(CGIndenter(CGList(prefCacheData)), + pre=("static bool sPrefCachesInited = false;\n" + "if (!sPrefCachesInited && NS_IsMainThread()) {\n" + " sPrefCachesInited = true;\n"), + post="}\n") + else: + prefCache = None + + if self.descriptor.interface.ctor(): + constructArgs = methodLength(self.descriptor.interface.ctor()) + else: + constructArgs = 0 + if len(self.descriptor.interface.namedConstructors) > 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 named constructors. + assert len(self.descriptor.interface.namedConstructors) == 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 = "nsContentUtils::ThreadsafeIsCallerChrome() ? sChromeOnlyNativeProperties.Upcast() : nullptr" + else: + chromeProperties = "nullptr" + + call = fill( + """ + JS::Heap<JSObject*>* protoCache = ${protoCache}; + JS::Heap<JSObject*>* interfaceCache = ${interfaceCache}; + dom::CreateInterfaceObjects(aCx, aGlobal, ${parentProto}, + ${protoClass}, protoCache, + ${constructorProto}, ${interfaceClass}, ${constructArgs}, ${namedConstructors}, + interfaceCache, + ${properties}, + ${chromeProperties}, + ${name}, aDefineOnGlobal, + ${unscopableNames}, + ${isGlobal}); + """, + protoClass=protoClass, + parentProto=parentProto, + protoCache=protoCache, + constructorProto=constructorProto, + interfaceClass=interfaceClass, + constructArgs=constructArgs, + namedConstructors=namedConstructors, + interfaceCache=interfaceCache, + properties=properties, + chromeProperties=chromeProperties, + name='"' + self.descriptor.interface.identifier.name + '"' if needInterfaceObject else "nullptr", + unscopableNames="unscopableNames" if self.haveUnscopables else "nullptr", + isGlobal=toStringBool(isGlobal)) + + # 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; + """) + + 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": + symbolJSID = "SYMBOL_TO_JSID(JS::GetWellKnownSymbol(aCx, JS::SymbolCode::iterator))" + getSymbolJSID = CGGeneric(fill("JS::Rooted<jsid> iteratorId(aCx, ${symbolJSID});", + symbolJSID=symbolJSID)) + defineFn = "JS_DefinePropertyById" + prop = "iteratorId" + elif alias.startswith("@@"): + raise TypeError("Can't handle any well-known Symbol other than @@iterator") + else: + getSymbolJSID = None + defineFn = "JS_DefineProperty" + prop = '"%s"' % alias + return CGList([ + getSymbolJSID, + # 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. + CGGeneric(fill( + """ + if (!${defineFn}(aCx, proto, ${prop}, aliasedVal, JSPROP_ENUMERATE)) { + $*{failureCode} + } + """, + defineFn=defineFn, + prop=prop, + 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(fill(""" + // Set up aliases on the interface prototype object we just created. + JS::Handle<JSObject*> proto = GetProtoObjectHandle(aCx); + if (!proto) { + $*{failureCode} + } + + """, + failureCode=failureCode)), + CGGeneric("JS::Rooted<JS::Value> aliasedVal(aCx);\n\n") + ] + [defineAliasesFor(m) for m in sorted(aliasedMembers)]) + else: + defineAliases = None + + # Globals handle unforgeables directly in Wrap() instead of + # via a holder. + if self.descriptor.hasUnforgeableMembers 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 = "*protoCache" + createUnforgeableHolder = CGGeneric(fill( + """ + JS::Rooted<JSObject*> unforgeableHolder(aCx); + { + JS::Rooted<JSObject*> holderProto(aCx, ${holderProto}); + unforgeableHolder = 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 + + if self.descriptor.name == "Promise": + speciesSetup = CGGeneric(fill( + """ + #ifndef SPIDERMONKEY_PROMISE + JS::Rooted<JSObject*> promiseConstructor(aCx, *interfaceCache); + JS::Rooted<jsid> species(aCx, + SYMBOL_TO_JSID(JS::GetWellKnownSymbol(aCx, JS::SymbolCode::species))); + if (!JS_DefinePropertyById(aCx, promiseConstructor, species, JS::UndefinedHandleValue, + JSPROP_SHARED, Promise::PromiseSpecies, nullptr)) { + $*{failureCode} + } + #endif // SPIDERMONKEY_PROMISE + """, + failureCode=failureCode)) + else: + speciesSetup = None + + if (self.descriptor.interface.isOnGlobalProtoChain() and + needInterfacePrototypeObject): + makeProtoPrototypeImmutable = CGGeneric(fill( + """ + if (*${protoCache}) { + bool succeeded; + JS::Handle<JSObject*> prot = GetProtoObjectHandle(aCx); + if (!JS_SetImmutablePrototype(aCx, prot, &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)) + else: + makeProtoPrototypeImmutable = None + + return CGList( + [getParentProto, getConstructorProto, initIds, + prefCache, CGGeneric(call), defineAliases, unforgeableHolderSetup, + speciesSetup, makeProtoPrototypeImmutable], + "\n").define() + + +class CGGetPerInterfaceObject(CGAbstractMethod): + """ + A method for getting a per-interface object (a prototype object or interface + constructor object). + """ + def __init__(self, descriptor, name, idPrefix="", extraArgs=[]): + args = [Argument('JSContext*', 'aCx')] + extraArgs + CGAbstractMethod.__init__(self, descriptor, name, + 'JS::Handle<JSObject*>', args) + self.id = idPrefix + "id::" + self.descriptor.name + + def definition_body(self): + return fill( + """ + /* Make sure our global is sane. Hopefully we can remove this sometime */ + JSObject* global = JS::CurrentGlobalOrNull(aCx); + if (!(js::GetObjectClass(global)->flags & JSCLASS_DOM_GLOBAL)) { + return nullptr; + } + + /* Check to see whether the interface objects are already installed */ + ProtoAndIfaceCache& protoAndIfaceCache = *GetProtoAndIfaceCache(global); + if (!protoAndIfaceCache.EntrySlotIfExists(${id})) { + JS::Rooted<JSObject*> rootedGlobal(aCx, global); + CreateInterfaceObjects(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 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(${id}); + MOZ_ASSERT_IF(entrySlot, !JS::ObjectIsMarkedGray(entrySlot)); + return JS::Handle<JSObject*>::fromMarkedLocation(entrySlot.address()); + """, + id=self.id) + + +class CGGetProtoObjectHandleMethod(CGGetPerInterfaceObject): + """ + A method for getting the interface prototype object. + """ + def __init__(self, descriptor): + CGGetPerInterfaceObject.__init__(self, descriptor, "GetProtoObjectHandle", + "prototypes::") + + def definition_body(self): + return dedent(""" + /* Get the interface prototype object for this class. This will create the + object as needed. */ + bool aDefineOnGlobal = true; + + """) + CGGetPerInterfaceObject.definition_body(self) + + +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(CGGetPerInterfaceObject): + """ + A method for getting the interface constructor object. + """ + def __init__(self, descriptor): + CGGetPerInterfaceObject.__init__( + self, descriptor, "GetConstructorObjectHandle", + "constructors::", + extraArgs=[Argument("bool", "aDefineOnGlobal", "true")]) + + def definition_body(self): + return dedent(""" + /* Get the interface object for this class. This will create the object as + needed. */ + + """) + CGGetPerInterfaceObject.definition_body(self) + + +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::GetObjectClass(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::GetObjectClass(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, + "Don't know how to resolve the properties of the named properties object for ${nativeType}."); + MOZ_ASSERT(clasp->mNativeHooks->mEnumerateOwnProperties, + "Don't know how to enumerate the properties of the named properties object for ${nativeType}."); + } + return namedPropertiesObject.get(); + """, + getParentProto=getParentProto, + ifaceName=self.descriptor.name, + parentProto=parentProto, + nativeType=self.descriptor.nativeType) + + +class CGDefineDOMInterfaceMethod(CGAbstractMethod): + """ + A method for resolve hooks to try to lazily define the interface object for + a given interface. + """ + def __init__(self, descriptor): + args = [Argument('JSContext*', 'aCx'), + Argument('JS::Handle<JSObject*>', 'aGlobal'), + Argument('JS::Handle<jsid>', 'id'), + Argument('bool', 'aDefineOnGlobal')] + CGAbstractMethod.__init__(self, descriptor, 'DefineDOMInterface', 'JSObject*', args) + + def definition_body(self): + if len(self.descriptor.interface.namedConstructors) > 0: + getConstructor = dedent(""" + JSObject* interfaceObject = GetConstructorObjectHandle(aCx, aDefineOnGlobal); + if (!interfaceObject) { + return nullptr; + } + for (unsigned slot = DOM_INTERFACE_SLOTS_BASE; slot < JSCLASS_RESERVED_SLOTS(&sInterfaceObjectClass.mBase); ++slot) { + JSObject* constructor = &js::GetReservedSlot(interfaceObject, slot).toObject(); + if (JS_GetFunctionId(JS_GetObjectFunction(constructor)) == JSID_TO_STRING(id)) { + return constructor; + } + } + return interfaceObject; + """) + else: + getConstructor = "return GetConstructorObjectHandle(aCx, aDefineOnGlobal);\n" + return getConstructor + + +def getConditionList(idlobj, cxName, objName): + """ + 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. + """ + conditions = [] + pref = idlobj.getExtendedAttribute("Pref") + if pref: + assert isinstance(pref, list) and len(pref) == 1 + conditions.append('Preferences::GetBool("%s")' % pref[0]) + if idlobj.getExtendedAttribute("ChromeOnly"): + conditions.append("nsContentUtils::ThreadsafeIsCallerChrome()") + func = idlobj.getExtendedAttribute("Func") + if func: + assert isinstance(func, list) and len(func) == 1 + conditions.append("%s(%s, %s)" % (func[0], cxName, objName)) + if idlobj.getExtendedAttribute("SecureContext"): + conditions.append("mozilla::dom::IsSecureContextOrObjectIsFromSecureContext(%s, %s)" % (cxName, objName)) + + return CGList((CGGeneric(cond) for cond in conditions), " &&\n") + + +class CGConstructorEnabled(CGAbstractMethod): + """ + A method for testing whether we should be exposing this interface + object or navigator property. 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.isExposedOnMainThread(): + 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::GetObjectClass(aObj)->name; + if (${workerCondition}) { + return false; + } + """, workerCondition=workerCondition.define()) + exposedInWorkerCheck = CGGeneric(exposedInWorkerCheck) + if iface.isExposedOnMainThread(): + 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 CreateBindingJSObject(descriptor, properties): + 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: + create = dedent( + """ + creator.CreateProxyObject(aCx, &sClass.mBase, DOMProxyHandler::getInstance(), + proto, aObject, aReflector); + if (!aReflector) { + return false; + } + + """) + if descriptor.interface.getExtendedAttribute('OverrideBuiltins'): + create += dedent(""" + js::SetProxyExtra(aReflector, JSPROXYSLOT_EXPANDO, + JS::PrivateValue(&aObject->mExpandoAndGeneration)); + + """) + else: + create = dedent( + """ + creator.CreateObject(aCx, sClass.ToJSClass(), proto, aObject, aReflector); + if (!aReflector) { + return false; + } + """) + return objDecl + create + + +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 (!DefineUnforgeableAttributes(aCx, ${holderName}, %s)) { + $*{failureCode} + } + """, + failureCode=failureCode, + holderName=holderName) + defineUnforgeableMethods = fill( + """ + if (!DefineUnforgeableMethods(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::ThreadsafeIsCallerChrome()")) + + if descriptor.interface.getExtendedAttribute("Unforgeable"): + # We do our undefined toJSON and 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::RootedId toPrimitive(aCx, + SYMBOL_TO_JSID(JS::GetWellKnownSymbol(aCx, JS::SymbolCode::toPrimitive))); + if (!JS_DefinePropertyById(aCx, ${holderName}, toPrimitive, + JS::UndefinedHandleValue, + JSPROP_READONLY | JSPROP_PERMANENT) || + !JS_DefineProperty(aCx, ${holderName}, "toJSON", + JS::UndefinedHandleValue, + JSPROP_READONLY | JSPROP_ENUMERATE | 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.hasUnforgeableMembers: + 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): + asserts = "" + iface = descriptor.interface + while iface: + 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)) + iface = iface.parent + 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 SetImmutablePrototype(descriptor, failureCode): + if not descriptor.hasNonOrdinaryGetPrototypeOf(): + return "" + + return fill( + """ + bool succeeded; + if (!JS_SetImmutablePrototype(aCx, aReflector, &succeeded)) { + ${failureCode} + } + MOZ_ASSERT(succeeded, + "Making a fresh reflector instance have an immutable " + "prototype can internally fail, but it should never be " + "unsuccessful"); + """, + failureCode=failureCode) + + +def DeclareProto(): + """ + Declare the canonicalProto and proto we have for our wrapping operation. + """ + return dedent( + """ + JS::Handle<JSObject*> canonicalProto = GetProtoObjectHandle(aCx); + if (!canonicalProto) { + return false; + } + JS::Rooted<JSObject*> proto(aCx); + 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::GetObjectCompartment(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. + + 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::Handle<JSObject*>', 'aGivenProto'), + Argument('JS::MutableHandle<JSObject*>', 'aReflector')] + CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'bool', args) + self.properties = properties + + def definition_body(self): + if self.descriptor.proxy: + preserveWrapper = dedent( + """ + // For DOM proxies, the only reliable way to preserve the wrapper + // is to force creation of the expando object. + JS::Rooted<JSObject*> unused(aCx, + DOMProxyHandler::EnsureExpandoObject(aCx, aReflector)); + """) + else: + preserveWrapper = "PreserveWrapper(aObject);\n" + + failureCode = dedent( + """ + aCache->ReleaseWrapper(aObject); + aCache->ClearWrapper(); + return false; + """) + + return fill( + """ + $*{assertInheritance} + 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"); + + JS::Rooted<JSObject*> global(aCx, FindAssociatedGlobal(aCx, aObject->GetParentObject())); + if (!global) { + return false; + } + MOZ_ASSERT(JS_IsGlobalObject(global)); + MOZ_ASSERT(!JS::ObjectIsMarkedGray(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 + binding_detail::AssertReflectorHasGivenProto(aCx, aReflector, aGivenProto); + #endif // DEBUG + return true; + } + + JSAutoCompartment ac(aCx, global); + $*{declareProto} + + $*{createObject} + + aCache->SetWrapper(aReflector); + $*{unforgeable} + $*{slots} + $*{setImmutablePrototype} + 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} + } + + return true; + """, + assertInheritance=AssertInheritanceChain(self.descriptor), + declareProto=DeclareProto(), + createObject=CreateBindingJSObject(self.descriptor, self.properties), + unforgeable=CopyUnforgeablePropertiesToInstance(self.descriptor, + failureCode), + slots=InitMemberSlots(self.descriptor, failureCode), + setImmutablePrototype=SetImmutablePrototype(self.descriptor, + failureCode), + preserveWrapper=preserveWrapper) + + +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. + + properties should be a PropertyArrays instance. + """ + def __init__(self, descriptor, properties): + # XXX can we wrap if we don't have an interface prototype object? + assert descriptor.interface.hasInterfacePrototypeObject() + args = [Argument('JSContext*', 'aCx'), + Argument(descriptor.nativeType + '*', 'aObject'), + Argument('JS::Handle<JSObject*>', 'aGivenProto'), + Argument('JS::MutableHandle<JSObject*>', 'aReflector')] + CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'bool', args) + self.properties = properties + + def definition_body(self): + failureCode = "return false;\n" + + return fill( + """ + $*{assertions} + + JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx)); + $*{declareProto} + + $*{createObject} + + $*{unforgeable} + + $*{slots} + + $*{setImmutablePrototype} + creator.InitializationSucceeded(); + return true; + """, + assertions=AssertInheritanceChain(self.descriptor), + declareProto=DeclareProto(), + createObject=CreateBindingJSObject(self.descriptor, self.properties), + unforgeable=CopyUnforgeablePropertiesToInstance(self.descriptor, + failureCode), + slots=InitMemberSlots(self.descriptor, failureCode), + setImmutablePrototype=SetImmutablePrototype(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::CompartmentOptions&', '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::ThreadsafeIsCallerChrome() ? sChromeOnlyNativeProperties.Upcast() : nullptr" + else: + chromeProperties = "nullptr" + + failureCode = dedent( + """ + aCache->ReleaseWrapper(aObject); + aCache->ClearWrapper(); + return false; + """); + + if self.descriptor.hasUnforgeableMembers: + unforgeable = InitUnforgeablePropertiesOnHolder( + self.descriptor, self.properties, failureCode, + "aReflector").define(); + else: + unforgeable = "" + + return fill( + """ + $*{assertions} + MOZ_ASSERT(ToSupportsIsOnPrimaryInheritanceChain(aObject, aCache), + "nsISupports must be on our primary inheritance chain"); + + if (!CreateGlobal<${nativeType}, GetProtoObjectHandle>(aCx, + aObject, + aCache, + sClass.ToJSClass(), + aOptions, + aPrincipal, + aInitStandardClasses, + aReflector)) { + $*{failureCode} + } + + // aReflector is a new global, so has a new compartment. Enter it + // before doing anything with it. + JSAutoCompartment ac(aCx, aReflector); + + if (!DefineProperties(aCx, aReflector, ${properties}, ${chromeProperties})) { + $*{failureCode} + } + $*{unforgeable} + + $*{slots} + + return true; + """, + assertions=AssertInheritanceChain(self.descriptor), + nativeType=self.descriptor.nativeType, + 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( + """ + + if (!get_${member}(aCx, aWrapper, aObject, args)) { + return false; + } + // Getter handled setting our reserved slots + """, + 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); + JSAutoCompartment ac(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 CGIsPermittedMethod(CGAbstractMethod): + """ + crossOriginGetters/Setters/Methods are sets of names of the relevant members. + """ + def __init__(self, descriptor, crossOriginGetters, crossOriginSetters, + crossOriginMethods): + self.crossOriginGetters = crossOriginGetters + self.crossOriginSetters = crossOriginSetters + self.crossOriginMethods = crossOriginMethods + args = [Argument("JSFlatString*", "prop"), + Argument("char16_t", "propFirstChar"), + Argument("bool", "set")] + CGAbstractMethod.__init__(self, descriptor, "IsPermitted", "bool", args, + inline=True) + + def definition_body(self): + allNames = self.crossOriginGetters | self.crossOriginSetters | self.crossOriginMethods + readwrite = self.crossOriginGetters & self.crossOriginSetters + readonly = (self.crossOriginGetters - self.crossOriginSetters) | self.crossOriginMethods + writeonly = self.crossOriginSetters - self.crossOriginGetters + cases = {} + for name in sorted(allNames): + cond = 'JS_FlatStringEqualsAscii(prop, "%s")' % name + if name in readonly: + cond = "!set && %s" % cond + elif name in writeonly: + cond = "set && %s" % cond + else: + assert name in readwrite + firstLetter = name[0] + case = cases.get(firstLetter, CGList([])) + case.append(CGGeneric("if (%s) {\n" + " return true;\n" + "}\n" % cond)) + cases[firstLetter] = case + caseList = [] + for firstLetter in sorted(cases.keys()): + caseList.append(CGCase("'%s'" % firstLetter, cases[firstLetter])) + switch = CGSwitch("propFirstChar", caseList) + return switch.define() + "\nreturn false;\n" + + +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. + + If allowCrossOriginObj is True, then we'll first do an + UncheckedUnwrap and then operate on the result. + """ + def __init__(self, descriptor, source, mutableSource, target, codeOnFailure, + exceptionCode=None, isCallbackReturnValue=False, + allowCrossOriginObj=False): + self.substitution = { + "type": descriptor.nativeType, + "protoID": "prototypes::id::" + descriptor.name, + "target": target, + "codeOnFailure": codeOnFailure, + } + # Supporting both the "cross origin object" case and the "has + # XPConnect impls" case at the same time is a pain, so let's + # not do that. That allows us to assume that our source is + # always a Handle or MutableHandle. + if allowCrossOriginObj and descriptor.hasXPConnectImpls: + raise TypeError("Interface %s both allows a cross-origin 'this' " + "and has XPConnect impls. We don't support that" % + descriptor.name) + if allowCrossOriginObj: + self.substitution["uncheckedObjDecl"] = fill( + """ + JS::Rooted<JSObject*> maybeUncheckedObj(cx, &${source}.toObject()); + """, + source=source) + self.substitution["uncheckedObjGet"] = fill( + """ + if (xpc::WrapperFactory::IsXrayWrapper(maybeUncheckedObj)) { + maybeUncheckedObj = js::UncheckedUnwrap(maybeUncheckedObj); + } else { + maybeUncheckedObj = js::CheckedUnwrap(maybeUncheckedObj); + if (!maybeUncheckedObj) { + $*{codeOnFailure} + } + } + """, + codeOnFailure=(codeOnFailure % { 'securityError': 'true'})) + self.substitution["source"] = "maybeUncheckedObj" + self.substitution["mutableSource"] = "&maybeUncheckedObj" + # No need to set up xpconnectUnwrap, since it won't be + # used in the allowCrossOriginObj case. + else: + self.substitution["uncheckedObjDecl"] = "" + self.substitution["uncheckedObjGet"] = "" + self.substitution["source"] = source + self.substitution["mutableSource"] = mutableSource + xpconnectUnwrap = ( + "nsresult rv = UnwrapXPConnect<${type}>(cx, ${mutableSource}, getter_AddRefs(objPtr));\n") + + if descriptor.hasXPConnectImpls: + self.substitution["codeOnFailure"] = string.Template( + "RefPtr<${type}> objPtr;\n" + + xpconnectUnwrap + + "if (NS_FAILED(rv)) {\n" + "${indentedCodeOnFailure}" + "}\n" + "// We should have an object\n" + "MOZ_ASSERT(objPtr);\n" + "${target} = objPtr;\n" + ).substitute(self.substitution, + indentedCodeOnFailure=indent(codeOnFailure)) + elif (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; + if (!GetContentGlobalForJSImplementedObject(cx, Callback(), getter_AddRefs(contentGlobal))) { + $*{exceptionCode} + } + JS::Rooted<JSObject*> jsImplSourceObj(cx, &${source}.toObject()); + ${target} = new ${type}(jsImplSourceObj, 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( + """ + $*{uncheckedObjDecl} + { + $*{uncheckedObjGet} + nsresult rv = UnwrapObject<${protoID}, ${type}>(${mutableSource}, ${target}); + 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, + 'ThrowErrorMessage(cx, MSG_DOES_NOT_IMPLEMENT_INTERFACE, "%s", "%s");\n' + '%s' % (sourceDescription, descriptor.interface.identifier.name, + exceptionCode), + exceptionCode, + isCallbackReturnValue) + + +class CGCallbackTempRoot(CGGeneric): + def __init__(self, name): + define = dedent(""" + { // Scope for tempRoot + JS::Rooted<JSObject*> tempRoot(cx, &${val}.toObject()); + ${declName} = new %s(cx, tempRoot, mozilla::dom::GetIncumbentGlobal()); + } + """) % name + CGGeneric.__init__(self, define=define) + + +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 and not isCallbackReturnValue and + not isOptional) + if useFastCallback: + name = "binding_detail::Fast%s" % name + + 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 = indent(CGCallbackTempRoot(name).define()) + 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. + ${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; in particular it does not + end with a ';' + """ + assert defaultValue.type.isDOMString() or defaultValue.type.isByteString() + return ("static const %(char_t)s data[] = { %(data)s };\n" + "%(method)s(data, ArrayLength(data) - 1)") % { + 'char_t': "char" if defaultValue.type.isByteString() else "char16_t", + 'method': method, + 'data': ", ".join(["'" + char + "'" for char in + defaultValue.value] + ["0"]) + } + + +# 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, + treatNullAs="Default", + isEnforceRange=False, + isClamp=False, + isNullOrUndefined=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 know the value + isObject() and we have no need to recheck that. + + 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", or "OwningUnion" to indicate that the conversion is + for something that is a dictionary member, a variadic argument, a sequence, + or an owning union respectively. + + 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 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 + [TreatNonObjectAsNull] 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 + + # 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 + ('ThrowErrorMessage(cx, MSG_NOT_OBJECT, "%s");\n' + '%s' % (firstCap(sourceDescription), exceptionCode))) + + def onFailureBadType(failureCode, typeName): + return CGGeneric( + failureCode or + ('ThrowErrorMessage(cx, MSG_DOES_NOT_IMPLEMENT_INTERFACE, "%s", "%s");\n' + '%s' % (firstCap(sourceDescription), typeName, exceptionCode))) + + def onFailureNotCallable(failureCode): + return CGGeneric( + failureCode or + ('ThrowErrorMessage(cx, 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 + return ( + "if (${haveValue}) {\n" + + indent(template) + + "} else {\n" + + indent(setDefault) + + "}\n") + + # 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: + 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", "MozMap")) + # 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`. + if not isinstance(descriptorProvider, Descriptor) or descriptorProvider.interface.isJSImplemented(): + templateBody = fill( + """ + if (($${passedToJSImpl}) && !CallerSubsumes($${val})) { + ThrowErrorMessage(cx, MSG_PERMISSION_DENIED_TO_PASS_ARG, "${sourceDescription}"); + $*{exceptionCode} + } + """, + 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 is "": + return 1 + return nestingLevel + 1 + + assert not (isEnforceRange and isClamp) # These are mutually exclusive + + if type.isSequence(): + assert not isEnforceRange and not isClamp + + if failureCode is None: + notSequence = ('ThrowErrorMessage(cx, MSG_NOT_SEQUENCE, "%s");\n' + "%s" % (firstCap(sourceDescription), 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 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 nullable: + typeName = CGTemplatedType("Nullable", typeName) + 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}" + }) + + # 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(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, + nestingLevel=str(nestingLevel)) + + templateBody = wrapObjectTemplate(templateBody, type, + "${declName}.SetNull();\n", notSequence) + if isinstance(defaultValue, IDLEmptySequenceValue): + if type.nullable(): + codeToSetEmpty = "${declName}.SetValue();\n" + else: + codeToSetEmpty = "/* Array is already empty; nothing to do */\n" + templateBody = handleDefault(templateBody, codeToSetEmpty) + + # Sequence arguments that might contain traceable things need + # to get traced + if not isMember and typeNeedsRooting(elementType): + 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 + else: + holderType = None + holderArgs = None + + return JSToNativeConversionInfo(templateBody, declType=typeName, + holderType=holderType, + dealWithOptional=isOptional, + holderArgs=holderArgs) + + if type.isMozMap(): + assert not isEnforceRange and not isClamp + if failureCode is None: + notMozMap = ('ThrowErrorMessage(cx, MSG_NOT_OBJECT, "%s");\n' + "%s" % (firstCap(sourceDescription), exceptionCode)) + else: + notMozMap = failureCode + + nullable = type.nullable() + # Be very careful not to change "type": we need it later + if nullable: + valueType = type.inner.inner + else: + valueType = type.inner + + valueInfo = getJSToNativeConversionInfo( + valueType, descriptorProvider, isMember="MozMap", + exceptionCode=exceptionCode, lenientFloatCode=lenientFloatCode, + isCallbackReturnValue=isCallbackReturnValue, + sourceDescription="value in %s" % sourceDescription, + nestingLevel=incrementNestingLevel()) + if valueInfo.dealWithOptional: + raise TypeError("Shouldn't have optional things in MozMap") + if valueInfo.holderType is not None: + raise TypeError("Shouldn't need holders for MozMap") + + typeName = CGTemplatedType("MozMap", valueInfo.declType) + mozMapType = typeName.define() + if nullable: + typeName = CGTemplatedType("Nullable", typeName) + mozMapRef = "${declName}.SetValue()" + else: + mozMapRef = "${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}" + }) + + templateBody = fill( + """ + ${mozMapType} &mozMap = ${mozMapRef}; + + JS::Rooted<JSObject*> mozMapObj(cx, &$${val}.toObject()); + JS::Rooted<JS::IdVector> ids(cx, JS::IdVector(cx)); + if (!JS_Enumerate(cx, mozMapObj, &ids)) { + $*{exceptionCode} + } + JS::Rooted<JS::Value> propNameValue(cx); + JS::Rooted<JS::Value> temp(cx); + JS::Rooted<jsid> curId(cx); + for (size_t i = 0; i < ids.length(); ++i) { + // Make sure we get the value before converting the name, since + // getting the value can trigger GC but our name is a dependent + // string. + curId = ids[i]; + binding_detail::FakeString propName; + bool isSymbol; + if (!ConvertIdToString(cx, curId, propName, isSymbol) || + (!isSymbol && !JS_GetPropertyById(cx, mozMapObj, curId, &temp))) { + $*{exceptionCode} + } + if (isSymbol) { + continue; + } + + ${valueType}* slotPtr = mozMap.AddEntry(propName); + if (!slotPtr) { + JS_ReportOutOfMemory(cx); + $*{exceptionCode} + } + ${valueType}& slot = *slotPtr; + $*{valueConversion} + } + """, + exceptionCode=exceptionCode, + mozMapType=mozMapType, + mozMapRef=mozMapRef, + valueType=valueInfo.declType.define(), + valueConversion=valueConversion) + + templateBody = wrapObjectTemplate(templateBody, type, + "${declName}.SetNull();\n", + notMozMap) + + declType = typeName + declArgs = None + holderType = None + holderArgs = None + # MozMap 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 not isMember and typeNeedsRooting(valueType): + holderType = CGTemplatedType("MozMapRooter", valueInfo.declType) + # If our MozMap 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" % mozMapRef + + 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 or isCallbackReturnValue + unionArgumentObj = "${declName}" if isOwningUnion else "${holderName}" + if nullable: + # 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()" if isOwningUnion else ".ref()" + + memberTypes = type.flatMemberTypes + names = [] + + interfaceMemberTypes = filter(lambda t: t.isNonCallbackInterface(), memberTypes) + if len(interfaceMemberTypes) > 0: + interfaceObject = [] + for memberType in interfaceMemberTypes: + name = getUnionMemberName(memberType) + interfaceObject.append( + CGGeneric("(failed = !%s.TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext" % + (unionArgumentObj, name))) + names.append(name) + interfaceObject = CGWrapper(CGList(interfaceObject, " ||\n"), + pre="done = ", post=";\n\n", reindent=True) + else: + interfaceObject = None + + sequenceObjectMemberTypes = filter(lambda t: t.isSequence(), memberTypes) + if len(sequenceObjectMemberTypes) > 0: + assert len(sequenceObjectMemberTypes) == 1 + name = getUnionMemberName(sequenceObjectMemberTypes[0]) + sequenceObject = CGGeneric( + "done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n" % + (unionArgumentObj, name)) + names.append(name) + else: + sequenceObject = None + + dateObjectMemberTypes = filter(lambda t: t.isDate(), memberTypes) + if len(dateObjectMemberTypes) > 0: + assert len(dateObjectMemberTypes) == 1 + memberType = dateObjectMemberTypes[0] + name = getUnionMemberName(memberType) + dateObject = CGGeneric("%s.SetTo%s(cx, ${val});\n" + "done = true;\n" % (unionArgumentObj, name)) + dateObject = CGIfWrapper(dateObject, "JS_ObjectIsDate(cx, argObj)") + names.append(name) + else: + dateObject = None + + callbackMemberTypes = filter(lambda t: t.isCallback() or t.isCallbackInterface(), memberTypes) + if len(callbackMemberTypes) > 0: + assert len(callbackMemberTypes) == 1 + memberType = callbackMemberTypes[0] + name = getUnionMemberName(memberType) + callbackObject = CGGeneric( + "done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n" % + (unionArgumentObj, name)) + names.append(name) + else: + callbackObject = None + + dictionaryMemberTypes = filter(lambda t: t.isDictionary(), memberTypes) + if len(dictionaryMemberTypes) > 0: + assert len(dictionaryMemberTypes) == 1 + name = getUnionMemberName(dictionaryMemberTypes[0]) + setDictionary = CGGeneric( + "done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n" % + (unionArgumentObj, name)) + names.append(name) + else: + setDictionary = None + + mozMapMemberTypes = filter(lambda t: t.isMozMap(), memberTypes) + if len(mozMapMemberTypes) > 0: + assert len(mozMapMemberTypes) == 1 + name = getUnionMemberName(mozMapMemberTypes[0]) + mozMapObject = CGGeneric( + "done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n" % + (unionArgumentObj, name)) + names.append(name) + else: + mozMapObject = None + + objectMemberTypes = filter(lambda t: t.isObject(), memberTypes) + 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 (!%s.SetToObject(cx, &${val}.toObject(), ${passedToJSImpl})) {\n" + "%s" + "}\n" + "done = true;\n" % (unionArgumentObj, indent(exceptionCode))) + names.append(objectMemberTypes[0].name) + else: + object = None + + hasObjectTypes = interfaceObject or sequenceObject or dateObject or callbackObject or object or mozMapObject + if hasObjectTypes: + # "object" is not distinguishable from other types + assert not object or not (interfaceObject or sequenceObject or dateObject or callbackObject or mozMapObject) + if sequenceObject or dateObject 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, dateObject, 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 dateObject: + templateBody.prepend(CGGeneric("JS::Rooted<JSObject*> argObj(cx, &${val}.toObject());\n")) + + if mozMapObject: + templateBody = CGList([templateBody, + CGIfWrapper(mozMapObject, "!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 = !%s.TrySetTo%s(cx, ${val}, tryNext)) || !tryNext;\n" + "break;\n" % (unionArgumentObj, name)) + other = CGList([]) + stringConversion = map(getStringOrPrimitiveConversion, stringTypes) + numericConversion = map(getStringOrPrimitiveConversion, numericTypes) + booleanConversion = map(getStringOrPrimitiveConversion, 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 (0);\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) { + $*{exceptionCode} + } + if (!done) { + ThrowErrorMessage(cx, MSG_NOT_IN_UNION, "${desc}", "${names}"); + $*{exceptionCode} + } + """, + exceptionCode=exceptionCode, + desc=firstCap(sourceDescription), + names=", ".join(names))) + + templateBody = CGWrapper(CGIndenter(CGList([templateBody, throw])), pre="{\n", post="}\n") + + typeName = CGUnionStruct.unionTypeDecl(type, isOwningUnion) + argumentTypeName = typeName + "Argument" + if nullable: + typeName = "Nullable<" + typeName + " >" + + def handleNull(templateBody, setToNullVar, extraConditionForNull=""): + nullTest = "%s${val}.isNullOrUndefined()" % extraConditionForNull + return CGIfElseWrapper(nullTest, + CGGeneric("%s.SetNull();\n" % setToNullVar), + templateBody) + + 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 + extraConditionForNull = "!(${haveValue}) || " + else: + extraConditionForNull = "" + templateBody = handleNull(templateBody, unionArgumentObj, + extraConditionForNull=extraConditionForNull) + + 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: + holderArgs = "${declName}.Value().SetValue()" + declType = CGTemplatedType("Optional", declType) + constructDecl = CGGeneric("${declName}.Construct();\n") + declLoc = "${declName}.Value()" + else: + holderArgs = "${declName}.SetValue()" + if holderType is not None: + constructHolder = CGGeneric("${holderName}.emplace(%s);\n" % holderArgs) + else: + constructHolder = None + # Don't need to pass those args when the holder is being constructed + holderArgs = None + else: + holderArgs = "${declName}" + constructHolder = None + + if not isMember and isCallbackReturnValue: + declType = CGWrapper(declType, post="&") + declArgs = "aRetVal" + else: + declArgs = None + + if defaultValue and not isinstance(defaultValue, IDLNullValue): + 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 "") + # 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();\n" % + (value, name)) + 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.SetStringData" % unionArgumentObj) + + ";\n") + + templateBody = CGIfElseWrapper("!(${haveValue})", default, templateBody) + + templateBody = CGList([constructHolder, templateBody]) + + if nullable: + if defaultValue: + if isinstance(defaultValue, IDLNullValue): + extraConditionForNull = "!(${haveValue}) || " + else: + extraConditionForNull = "${haveValue} && " + else: + extraConditionForNull = "" + templateBody = handleNull(templateBody, declLoc, + extraConditionForNull=extraConditionForNull) + elif (not type.hasNullableType and defaultValue and + isinstance(defaultValue, IDLNullValue)): + 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))) + templateBody = CGIfElseWrapper("!(${haveValue})", + initDictionaryWithNull, + templateBody) + + templateBody = CGList([constructDecl, templateBody]) + + return JSToNativeConversionInfo(templateBody.define(), + declType=declType, + declArgs=declArgs, + holderType=holderType, + holderArgs=holderArgs, + dealWithOptional=isOptional and (not nullable or isOwningUnion)) + + if type.isGeckoInterface(): + assert not isEnforceRange and not isClamp + + 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) + + # 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. + # + # Finally, promises need to hold a strong ref because that's what + # Promise.resolve returns. + assert not descriptor.interface.isCallback() + isPromise = descriptor.interface.identifier.name == "Promise" + forceOwningType = isMember or isCallbackReturnValue or isPromise + + 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 += 'static_assert(IsRefcounted<%s>::value, "We can only store refcounted classes.");' % typeName + + if isPromise: + # 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. + # This is in theory a case the spec covers but in practice it + # really doesn't define behavior here because it doesn't define + # what Realm we're in after the callback returns, which is when + # the argument conversion happens. We will use the current + # compartment, which is the compartment of the callable (which + # may itself be a cross-compartment wrapper itself), which makes + # as much sense as anything else. In practice, such an API would + # once again be providing a Promise to signal completion of an + # operation, which would then not be exposed to anyone other than + # our own implementation code. + # 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. CheckedUnwrap 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>(NS_LITERAL_STRING("${sourceDescription}")); + return nullptr; + } + JSObject* unwrappedVal = js::CheckedUnwrap(&$${val}.toObject()); + if (!unwrappedVal) { + // A slight lie, but not much of one, for a dead object wrapper. + aRv.ThrowTypeError<MSG_NOT_OBJECT>(NS_LITERAL_STRING("${sourceDescription}")); + return nullptr; + } + globalObj = js::GetGlobalForObjectCrossCompartment(unwrappedVal); + """, + sourceDescription=sourceDescription) + else: + getPromiseGlobal = "" + + templateBody = fill( + """ + { // Scope for our GlobalObject, FastErrorResult, JSAutoCompartment, + // etc. + + JS::Rooted<JSObject*> globalObj(cx, JS::CurrentGlobalOrNull(cx)); + $*{getPromiseGlobal} + JSAutoCompartment ac(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; + #ifdef SPIDERMONKEY_PROMISE + nsCOMPtr<nsIGlobalObject> global = + do_QueryInterface(promiseGlobal.GetAsSupports()); + if (!global) { + promiseRv.Throw(NS_ERROR_UNEXPECTED); + promiseRv.MaybeSetPendingException(cx); + $*{exceptionCode} + } + $${declName} = Promise::Resolve(global, cx, valueToResolve, + promiseRv); + if (promiseRv.MaybeSetPendingException(cx)) { + $*{exceptionCode} + } + #else + JS::Handle<JSObject*> promiseCtor = + PromiseBinding::GetConstructorObjectHandle(cx); + if (!promiseCtor) { + $*{exceptionCode} + } + JS::Rooted<JS::Value> resolveThisv(cx, JS::ObjectValue(*promiseCtor)); + JS::Rooted<JS::Value> resolveResult(cx); + Promise::Resolve(promiseGlobal, resolveThisv, valueToResolve, + &resolveResult, promiseRv); + if (promiseRv.MaybeSetPendingException(cx)) { + $*{exceptionCode} + } + nsresult unwrapRv = UNWRAP_OBJECT(Promise, &resolveResult.toObject(), $${declName}); + if (NS_FAILED(unwrapRv)) { // Quite odd + promiseRv.Throw(unwrapRv); + promiseRv.MaybeSetPendingException(cx); + $*{exceptionCode} + } + #endif // SPIDERMONKEY_PROMISE + } + """, + getPromiseGlobal=getPromiseGlobal, + exceptionCode=exceptionCode) + elif not descriptor.interface.isConsequential() and 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: + # Either external, or new-binding non-castable. 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 + ">(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" + + if isPromise: + if type.nullable(): + codeToSetNull = "${declName} = nullptr;\n" + templateBody = CGIfElseWrapper( + "${val}.isNullOrUndefined()", + CGGeneric(codeToSetNull), + CGGeneric(templateBody)).define() + if isinstance(defaultValue, IDLNullValue): + templateBody = handleDefault(templateBody, codeToSetNull) + else: + assert defaultValue is None + else: + # 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 + arrayType = CGGeneric(name) + declType = arrayType + 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()) + template = wrapObjectTemplate(template, type, "${declName}.SetNull();\n", + failureCode) + if not isMember: + # 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<RootedTypedArray<ArrayType> >. So do a holder if we're + # optional and use a RootedTypedArray otherwise. + if isOptional: + holderType = CGTemplatedType("TypedArrayRooter", arrayType) + # If our typed array 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("RootedTypedArray", 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.isDOMString() or type.isUSVString(): + assert not isEnforceRange and not isClamp + + 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" + nullBehavior = treatAs[treatNullAs] + + def getConversionCode(varName): + normalizeCode = "" + if type.isUSVString(): + normalizeCode = "NormalizeUSVString(cx, %s);\n" % varName + + 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)" % varName + else: + defaultCode = handleDefaultStringValue(defaultValue, + "%s.Rebind" % varName) + return handleDefault(conversionCode, defaultCode + ";\n") + + if isMember: + # Convert directly into the nsString member we have. + declType = CGGeneric("nsString") + return JSToNativeConversionInfo( + getConversionCode("${declName}"), + declType=declType, + dealWithOptional=isOptional) + + if isOptional: + declType = "Optional<nsAString>" + holderType = CGGeneric("binding_detail::FakeString") + conversionCode = ("%s" + "${declName} = &${holderName};\n" % + getConversionCode("${holderName}")) + else: + declType = "binding_detail::FakeString" + 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 + + nullable = toStringBool(type.nullable()) + + conversionCode = fill(""" + if (!ConvertJSValueToByteString(cx, $${val}, ${nullable}, $${declName})) { + $*{exceptionCode} + } + """, + nullable=nullable, + exceptionCode=exceptionCode) + + if defaultValue is not None: + if isinstance(defaultValue, IDLNullValue): + assert(type.nullable()) + defaultCode = "${declName}.SetIsVoid(true)" + else: + defaultCode = handleDefaultStringValue(defaultValue, + "${declName}.Rebind") + conversionCode = handleDefault(conversionCode, defaultCode + ";\n") + + return JSToNativeConversionInfo( + conversionCode, + declType=CGGeneric("nsCString"), + dealWithOptional=isOptional) + + if type.isEnum(): + assert not isEnforceRange and not isClamp + + 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=firstCap(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 + 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 + + declArgs = None + if isMember in ("Variadic", "Sequence", "Dictionary", "MozMap"): + # 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`. + if not isinstance(descriptorProvider, Descriptor) or descriptorProvider.interface.isJSImplemented(): + templateBody = fill( + """ + if (($${passedToJSImpl}) && !CallerSubsumes($${val})) { + ThrowErrorMessage(cx, MSG_PERMISSION_DENIED_TO_PASS_ARG, "${sourceDescription}"); + $*{exceptionCode} + } + """, + 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 + return handleJSObjectType(type, isMember, failureCode, exceptionCode, sourceDescription) + + if type.isDictionary(): + # There are no nullable dictionaries + assert not type.nullable() or isCallbackReturnValue + # All optional dictionaries always have default values, so we + # should be able to assume not isOptional here. + assert not isOptional + # 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 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 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, IDLNullValue)) + val = "(${haveValue}) ? ${val} : JS::NullHandleValue" + else: + val = "${val}" + + dictLoc = "${declName}" + if type.nullable(): + dictLoc += ".SetValue()" + + conversionCode = fill(""" + if (!${dictLoc}.Init(cx, ${val}, "${desc}", $${passedToJSImpl})) { + $*{exceptionCode} + } + """, + dictLoc=dictLoc, + val=val, + desc=firstCap(sourceDescription), + exceptionCode=exceptionCode) + + if failureCode is not None: + if isDefinitelyObject: + dictionaryTest = "IsObjectValueConvertibleToDictionary" + else: + dictionaryTest = "IsConvertibleToDictionary" + + template = fill(""" + { // scope for isConvertible + bool isConvertible; + if (!${testConvertible}(cx, ${val}, &isConvertible)) { + $*{exceptionCode} + } + if (!isConvertible) { + $*{failureCode} + } + + $*{conversionCode} + } + + """, + testConvertible=dictionaryTest, + val=val, + exceptionCode=exceptionCode, + 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 and isCallbackReturnValue: + # Go ahead and just convert directly into our actual return value + declType = CGWrapper(declType, post="&") + declArgs = "aRetVal" + elif not isMember and typeNeedsRooting(type): + declType = CGTemplatedType("RootedDictionary", declType) + declArgs = "cx" + else: + declArgs = None + + return JSToNativeConversionInfo(template, declType=declType, + declArgs=declArgs) + + if type.isVoid(): + assert not isOptional + # This one only happens for return values, and its easy: Just + # ignore the jsval. + return JSToNativeConversionInfo("") + + if type.isDate(): + assert not isEnforceRange and not isClamp + + declType = CGGeneric("Date") + if type.nullable(): + declType = CGTemplatedType("Nullable", declType) + dateVal = "${declName}.SetValue()" + else: + dateVal = "${declName}" + + if failureCode is None: + notDate = ('ThrowErrorMessage(cx, MSG_NOT_DATE, "%s");\n' + "%s" % (firstCap(sourceDescription), exceptionCode)) + else: + notDate = failureCode + + conversion = fill( + """ + JS::Rooted<JSObject*> possibleDateObject(cx, &$${val}.toObject()); + { // scope for isDate + bool isDate; + if (!JS_ObjectIsDate(cx, possibleDateObject, &isDate)) { + $*{exceptionCode} + } + if (!isDate) { + $*{notDate} + } + if (!${dateVal}.SetTimeStamp(cx, possibleDateObject)) { + $*{exceptionCode} + } + } + """, + exceptionCode=exceptionCode, + dateVal=dateVal, + notDate=notDate) + + conversion = wrapObjectTemplate(conversion, type, + "${declName}.SetNull();\n", notDate) + return JSToNativeConversionInfo(conversion, + declType=declType, + dealWithOptional=isOptional) + + 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" + + 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 + template = fill(""" + if (${nullCondition}) { + $${declName}.SetNull(); + } else if (!ValueToPrimitive<${typeName}, ${conversionBehavior}>(cx, $${val}, &${writeLoc})) { + $*{exceptionCode} + } + """, + nullCondition=nullCondition, + typeName=typeName, + conversionBehavior=conversionBehavior, + 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}, &${writeLoc})) { + $*{exceptionCode} + } + """, + typeName=typeName, + conversionBehavior=conversionBehavior, + writeLoc=writeLoc, + exceptionCode=exceptionCode) + declType = CGGeneric(typeName) + + if type.isFloat() and not type.isUnrestricted(): + if lenientFloatCode is not None: + nonFiniteCode = lenientFloatCode + else: + nonFiniteCode = ('ThrowErrorMessage(cx, 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 (!mozilla::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 = CGIfElseWrapper( + "${haveValue}", + CGGeneric(template), + CGGeneric("%s = %s;\n" % (writeLoc, defaultStr))).define() + + 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(true)" if value.value else "JS::BooleanValue(false)" + 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, + treatNullAs=self.argument.treatNullAs, + isEnforceRange=self.argument.enforceRange, + isClamp=self.argument.clamp, + 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("binding_detail::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() + + # 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) { + ${elemType}& slot = *${declName}.AppendElement(mozilla::fallible); + """) + ).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): + # 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 +mozMapWrapLevel = 0 + + +def getWrapTemplateForType(type, descriptorProvider, result, successCode, + returnsNewObject, exceptionCode, typedArraysAreStructs, + 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 typedArraysAreStructs is true, then if the type is a typed array, + "result" is one of the dom::TypedArray 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, 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.isVoid(): + return (setUndefined(), True) + + if (type.isSequence() or type.isMozMap()) and type.nullable(): + # These are both wrapped in Nullable<> + recTemplate, recInfall = getWrapTemplateForType(type.inner, descriptorProvider, + "%s.Value()" % result, successCode, + returnsNewObject, exceptionCode, + typedArraysAreStructs) + 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", + 'typedArraysAreStructs': typedArraysAreStructs + }) + 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 (0); + 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.isMozMap(): + # Now do non-nullable MozMap. Our success code is just to break to + # where we define the property on the object. Note that we bump the + # mozMapWrapLevel around this call so that nested MozMap conversions + # will use different temp value names. + global mozMapWrapLevel + valueName = "mozMapValue%d" % mozMapWrapLevel + mozMapWrapLevel += 1 + innerTemplate = wrapForType( + type.inner, descriptorProvider, + { + 'result': valueName, + 'successCode': "break;\n", + 'jsvalRef': "tmp", + 'jsvalHandle': "&tmp", + 'returnsNewObject': returnsNewObject, + 'exceptionCode': exceptionCode, + 'obj': "returnObj", + 'typedArraysAreStructs': typedArraysAreStructs + }) + mozMapWrapLevel -= 1 + code = fill( + """ + + nsTArray<nsString> keys; + ${result}.GetKeys(keys); + JS::Rooted<JSObject*> returnObj(cx, JS_NewPlainObject(cx)); + if (!returnObj) { + $*{exceptionCode} + } + // Scope for 'tmp' + { + JS::Rooted<JS::Value> tmp(cx); + for (size_t idx = 0; idx < keys.Length(); ++idx) { + auto& ${valueName} = ${result}.Get(keys[idx]); + // 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 (0); + if (!JS_DefineUCProperty(cx, returnObj, keys[idx].get(), + keys[idx].Length(), tmp, + JSPROP_ENUMERATE)) { + $*{exceptionCode} + } + } + } + $*{set} + """, + result=result, + exceptionCode=exceptionCode, + valueName=valueName, + innerTemplate=innerTemplate, + set=setObject("*returnObj")) + + return (code, False) + + if type.isGeckoInterface() and not type.isCallbackInterface(): + descriptor = descriptorProvider.getDescriptor(type.unroll().inner.identifier.name) + if type.nullable(): + 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: + # Hack: the "Promise" interface is OK to return from + # non-newobject things even when it's not wrappercached; that + # happens when using SpiderMonkey promises, and the WrapObject() + # method will just return the existing reflector, which is just + # not stored in a wrappercache. + if (not returnsNewObject and + descriptor.interface.identifier.name != "Promise"): + raise MethodNotNewObjectError(descriptor.interface.identifier.name) + wrapMethod = "WrapNewBindingNonWrapperCachedObject" + wrapArgs = "cx, ${obj}, %s, ${jsvalHandle}" % result + if isConstructorRetval: + wrapArgs += ", desiredProto" + wrap = "%s(%s)" % (wrapMethod, wrapArgs) + if not descriptor.hasXPConnectImpls: + # Can only fail to wrap as a new-binding object + # if they already threw an exception. + # XXX Assertion disabled for now, see bug 991271. + failed = ("MOZ_ASSERT(true || JS_IsExceptionPending(cx));\n" + + exceptionCode) + else: + if descriptor.notflattened: + raise TypeError("%s has XPConnect impls but not flattened; " + "fallback won't work correctly" % + descriptor.interface.identifier.name) + # Try old-style wrapping for bindings which might be XPConnect impls. + failed = wrapAndSetPtr("HandleNewBindingWrappingFailure(cx, ${obj}, %s, ${jsvalHandle})" % result) + 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.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.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(): + wrapCode = setObject( + "*GetCallbackFromCallbackObject(%(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 typedArraysAreStructs)): + # 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 not (type.isUnion() or type.isPrimitive() or type.isDictionary() or + type.isDate() or + (type.isSpiderMonkeyInterface() and typedArraysAreStructs)): + raise TypeError("Need to learn to wrap %s" % type) + + if type.nullable(): + recTemplate, recInfal = getWrapTemplateForType(type.inner, descriptorProvider, + "%s.Value()" % result, successCode, + returnsNewObject, exceptionCode, + typedArraysAreStructs) + return ("if (%s.IsNull()) {\n" % result + + indent(setNull()) + + "}\n" + + recTemplate, recInfal) + + if type.isSpiderMonkeyInterface(): + assert typedArraysAreStructs + # 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) + + if type.isDate(): + return (wrapAndSetPtr("%s.ToDateObject(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('typedArraysAreStructs', 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 + (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.isMozMap(): + 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.isVoid(): + # 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.isDOMString() or returnType.isUSVString(): + if isMember: + return CGGeneric("nsString"), "ref", None, None, None + return CGGeneric("DOMString"), "ref", None, None, None + if returnType.isByteString(): + return CGGeneric("nsCString"), "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(): + result = CGGeneric(descriptorProvider.getDescriptor( + returnType.unroll().inner.identifier.name).nativeType) + conversion = None + if isMember: + result = CGGeneric("StrongPtrForMember<%s>::Type" % result.define()) + else: + conversion = CGGeneric("StrongOrRawPtr<%s>" % result.define()) + 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.isMozMap(): + nullable = returnType.nullable() + if nullable: + returnType = returnType.inner + result, _, _, _, _ = getRetvalDeclarationForType(returnType.inner, + descriptorProvider, + isMember="MozMap") + # While we have our inner type, set up our rooter, if needed + if not isMember and typeNeedsRooting(returnType): + rooter = CGGeneric("MozMapRooter<%s> resultRooter(cx, &result);\n" % + result.define()) + else: + rooter = None + result = CGTemplatedType("MozMap", 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 + if returnType.isDate(): + result = CGGeneric("Date") + if returnType.nullable(): + result = CGTemplatedType("Nullable", result) + return result, None, None, None, 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))) + + +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. + + needsSubjectPrincipal is a boolean indicating whether the call should + receive the subject nsIPrincipal as argument. + + needsCallerType is a boolean indicating whether the call should receive + a PrincipalType for the caller. + + isFallible is a boolean indicating whether the call should be fallible. + + 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. + """ + def __init__(self, isFallible, needsSubjectPrincipal, needsCallerType, + arguments, argsPre, returnType, extendedAttributes, descriptor, + nativeMethodName, static, object="self", argsPost=[], + resultVar=None): + 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.isMozMap(): + 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()) and not a.type.nullable()) or + a.type.isDOMString()): + arg = CGWrapper(arg, pre="NonNullHelper(", post=")") + args.append(arg) + + needResultDecl = False + + # 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)) + + if needsSubjectPrincipal: + args.append(CGGeneric("subjectPrincipal")) + + if needsCallerType: + args.append(CGGeneric("callerType")) + + if isFallible: + args.append(CGGeneric("rv")) + args.extend(CGGeneric(arg) for arg in argsPost) + + # Build up our actual call + self.cgRoot = CGList([]) + + call = CGGeneric(nativeMethodName) + if not static: + call = CGWrapper(call, pre="%s->" % object) + call = CGList([call, CGWrapper(args, pre="(", post=")")]) + if 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 resultRooter is not None: + self.cgRoot.prepend(resultRooter) + 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.prepend(CGWrapper(result, post=";\n")) + if resultOutParam is None: + call = CGWrapper(call, pre=resultVar + " = ") + elif result is not None: + assert resultOutParam is None + call = CGWrapper(call, pre=resultVar + " = ") + + call = CGWrapper(call, post=";\n") + self.cgRoot.append(call) + + if needsSubjectPrincipal: + getPrincipal = dedent( + """ + JSCompartment* compartment = js::GetContextCompartment(cx); + MOZ_ASSERT(compartment); + JSPrincipals* principals = JS_GetCompartmentPrincipals(compartment); + """) + + if descriptor.interface.isExposedInAnyWorker(): + self.cgRoot.prepend(CGGeneric(fill( + """ + Maybe<nsIPrincipal*> subjectPrincipal; + if (NS_IsMainThread()) { + $*{getPrincipal} + subjectPrincipal.emplace(nsJSPrincipals::get(principals)); + } + """, + getPrincipal=getPrincipal))) + else: + self.cgRoot.prepend(CGGeneric(fill( + """ + $*{getPrincipal} + // Initializing a nonnull is pretty darn annoying... + NonNull<nsIPrincipal> subjectPrincipal; + subjectPrincipal = static_cast<nsIPrincipal*>(nsJSPrincipals::get(principals)); + """, + getPrincipal=getPrincipal))) + + if needsCallerType: + # Note that we do not want to use + # IsCallerChrome/ThreadsafeIsCallerChrome directly because those + # will pull in the check for UniversalXPConnect, which we ideally + # don't want to have in the new thing we're doing here. If not + # NS_IsMainThread(), though, we'll go ahead and call + # ThreasafeIsCallerChrome(), since that won't mess with + # UnivesalXPConnect and we don't want to worry about the right + # worker includes here. + callerCheck = CGGeneric("callerType = nsContentUtils::IsSystemPrincipal(nsContentUtils::SubjectPrincipal()) ? CallerType::System : CallerType::NonSystem;\n") + if descriptor.interface.isExposedInAnyWorker(): + callerCheck = CGIfElseWrapper( + "NS_IsMainThread()", + callerCheck, + CGGeneric("callerType = nsContentUtils::ThreadsafeIsCallerChrome() ? CallerType::System : CallerType::NonSystem;\n")); + self.cgRoot.prepend(callerCheck) + self.cgRoot.prepend(CGGeneric("CallerType callerType;\n")) + + if isFallible: + self.cgRoot.prepend(CGGeneric("binding_detail::FastErrorResult rv;\n")) + self.cgRoot.append(CGGeneric(dedent( + """ + if (MOZ_UNLIKELY(rv.MaybeSetPendingException(cx))) { + return false; + } + """))) + + self.cgRoot.append(CGGeneric("MOZ_ASSERT(!JS_IsExceptionPending(cx));\n")) + + def define(self): + return self.cgRoot.define() + + +def getUnionMemberName(type): + if type.isGeckoInterface(): + return type.inner.identifier.name + if type.isEnum(): + return type.inner.identifier.name + return type.name + + +class MethodNotNewObjectError(Exception): + def __init__(self, typename): + self.typename = typename + +# 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 +mapWrapLevel = 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.isMozMap(): + origValue = value + origType = type + if type.nullable(): + type = type.inner + value = "%s.Value()" % value + global mapWrapLevel + key = "mapName%d" % mapWrapLevel + mapWrapLevel += 1 + wrapElement = wrapTypeIntoCurrentCompartment(type.inner, + "%s.Get(%sKeys[%sIndex])" % (value, key, key)) + mapWrapLevel -= 1 + if not wrapElement: + return None + wrapCode = CGWrapper(CGIndenter(wrapElement), + pre=(""" + nsTArray<nsString> %sKeys; + %s.GetKeys(%sKeys); + for (uint32_t %sIndex = 0; %sIndex < %sKeys.Length(); ++%sIndex) { + """ % (key, value, key, key, key, key, key)), + post="}\n") + if origType.nullable(): + wrapCode = CGIfWrapper(wrapCode, "!%s.IsNull()" % origValue) + 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.isString() or type.isPrimitive() or type.isEnum() or + type.isGeckoInterface() or type.isCallback() or type.isDate()): + # 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. + """ + # 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): + 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 + self.extendedAttributes = descriptor.getExtendedAttributes(idlNode, + getter=getter, + setter=setter) + self.arguments = arguments + self.argCount = len(arguments) + self.isConstructor = isConstructor + cgThings = [] + + # Here, we check if the current getter, setter, method, interface or + # inherited interfaces have the UnsafeInPrerendering extended attribute + # and if so, we add a check to make sure it is safe. + if (idlNode.getExtendedAttribute("UnsafeInPrerendering") or + descriptor.interface.getExtendedAttribute("UnsafeInPrerendering") or + any(i.getExtendedAttribute("UnsafeInPrerendering") + for i in descriptor.interface.getInheritedInterfaces())): + cgThings.append(CGGeneric(dedent( + """ + if (!mozilla::dom::EnforceNotInPrerendering(cx, obj)) { + // Return false from the JSNative in order to trigger + // an uncatchable exception. + MOZ_ASSERT(!JS_IsExceptionPending(cx)); + return false; + } + """))) + + deprecated = (idlNode.getExtendedAttribute("Deprecated") or + (idlNode.isStatic() and descriptor.interface.getExtendedAttribute("Deprecated"))) + if deprecated: + cgThings.append(CGGeneric(dedent( + """ + DeprecationWarning(cx, obj, nsIDocument::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") + + # Hack for making Promise.prototype.then work well over Xrays. + if (not idlNode.isStatic() and + descriptor.name == "Promise" and + idlNode.isMethod() and + idlNode.identifier.name == "then"): + cgThings.append(CGGeneric(dedent( + """ + JS::Rooted<JSObject*> calleeGlobal(cx, xpc::XrayAwareCalleeGlobal(&args.callee())); + """))) + argsPre.append("calleeGlobal") + + needsUnwrap = False + argsPost = [] + if isConstructor: + if descriptor.name == "Promise": + # Hack for Promise for now: pass in our desired proto so the + # implementation can create the reflector with the right proto. + argsPost.append("desiredProto") + # Also, we do not want to enter the content compartment when the + # Promise constructor is called via Xrays, because we want to + # create our callback functions that we will hand to our caller + # in the Xray compartment. The reason we want to do that is the + # following situation, over Xrays: + # + # contentWindow.Promise.race([Promise.resolve(5)]) + # + # Ideally this would work. Internally, race() does a + # contentWindow.Promise.resolve() on everything in the array. + # Per spec, to support subclassing, + # contentWindow.Promise.resolve has to do: + # + # var resolve, reject; + # var p = new contentWindow.Promise(function(a, b) { + # resolve = a; + # reject = b; + # }); + # resolve(arg); + # return p; + # + # where "arg" is, in this case, the chrome-side return value of + # Promise.resolve(5). But if the "resolve" function in that + # case were created in the content compartment, then calling it + # would wrap "arg" in an opaque wrapper, and that function tries + # to get .then off the argument, which would throw. So we need + # to create the "resolve" function in the chrome compartment, + # and hence want to be running the entire Promise constructor + # (which creates that function) in the chrome compartment in + # this case. So don't set needsUnwrap here. + else: + needsUnwrap = True + needsUnwrappedVar = False + unwrappedVar = "obj" + elif descriptor.interface.isJSImplemented(): + if not idlNode.isStatic(): + needsUnwrap = True + needsUnwrappedVar = True + argsPost.append("js::GetObjectCompartment(unwrappedObj ? *unwrappedObj : obj)") + elif needScopeObject(returnType, arguments, self.extendedAttributes, + descriptor.wrapperCache, True, + idlNode.getExtendedAttribute("StoreInSlot")): + needsUnwrap = True + needsUnwrappedVar = True + argsPre.append("unwrappedObj ? *unwrappedObj : obj") + + if idlNode.isStatic() and not isConstructor and descriptor.name == "Promise": + # Hack for Promise for now: pass in the "this" value to + # Promise static methods. + argsPre.append("args.thisv()") + + 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()") + + ourName = "%s.%s" % (descriptor.interface.identifier.name, + idlNode.identifier.name) + if idlNode.isMethod(): + argDescription = "argument %(index)d of " + ourName + elif setter: + argDescription = "value being assigned to %s" % ourName + 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( + """ + ${obj} = js::CheckedUnwrap(${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<JSAutoCompartment> ac;\n")) + xraySteps.append(CGGeneric("ac.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 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.maplikeOrSetlikeOrIterable, + idlNode.identifier.name)) + else: + cgThings.append(CGCallGenerator( + self.isFallible(), + idlNode.getExtendedAttribute('NeedsSubjectPrincipal'), + needsCallerType(idlNode), + self.getArguments(), argsPre, returnType, + self.extendedAttributes, descriptor, + nativeMethodName, + static, argsPost=argsPost, resultVar=resultVar)) + + if useCounterName: + # Generate a telemetry call for when [UseCounter] is used. + code = "SetDocumentAndPageUseCounter(cx, obj, eUseCounter_%s);\n" % useCounterName + cgThings.append(CGGeneric(code)) + + self.cgRoot = CGList(cgThings) + + def getArguments(self): + return [(a, "arg" + str(i)) for i, a in enumerate(self.arguments)] + + def isFallible(self): + return 'infallible' not in self.extendedAttributes + + def wrap_return_value(self): + wrapCode = "" + + returnsNewObject = memberReturnsNewObject(self.idlNode) + if (returnsNewObject and + self.returnType.isGeckoInterface()): + wrapCode += dedent( + """ + static_assert(!IsPointer<decltype(result)>::value, + "NewObject implies that we need to keep the object alive with a strong reference."); + """) + + setSlot = self.idlNode.isAttr() and self.idlNode.slotIndices is not None + if 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 setSlot else "obj" + } + try: + wrapCode += wrapForType(self.returnType, self.descriptor, resultTemplateValues) + except MethodNotNewObjectError, err: + assert not returnsNewObject + raise TypeError("%s being returned from non-NewObject method or property %s.%s" % + (err.typename, + self.descriptor.interface.identifier.name, + self.idlNode.identifier.name)) + if setSlot: + # 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::ThreadsafeIsCallerChrome()) { + 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: + conversionScope = "isXray ? obj : slotStorage" + else: + conversionScope = "slotStorage" + + wrapCode = fill( + """ + { + JS::Rooted<JSObject*> conversionScope(cx, ${conversionScope}); + JSAutoCompartment ac(cx, conversionScope); + do { // block we break out of when done wrapping + $*{wrapCode} + } while (0); + $*{postConversionSteps} + } + { // And now store things in the compartment of our slotStorage. + JSAutoCompartment ac(cx, slotStorage); + $*{slotStorageSteps} + } + // And now make sure args.rval() is in the caller compartment + 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=" break;\n}\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 (defaulting to False) for whether to fall through. + """ + def __init__(self, expression, body, fallThrough=False): + CGList.__init__(self, []) + self.append(CGGeneric("case " + expression + ": {\n")) + bodyList = CGList([body]) + if fallThrough: + bodyList.append(CGGeneric("MOZ_FALLTHROUGH;\n")) + else: + 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, constructorName=None): + CGThing.__init__(self) + + if isConstructor: + assert constructorName is not None + methodName = constructorName + else: + methodName = "%s.%s" % (descriptor.interface.identifier.name, method.identifier.name) + argDesc = "argument %d of " + methodName + + if method.getExtendedAttribute("UseCounter"): + useCounterName = methodName.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 (MOZ_UNLIKELY(args.length() < ${requiredArgs})) { + return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, "${methodName}"); + } + """, + 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, True)) + 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: + # + # void(long... foo); + # void(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 Date object being passed to a Date or "object" arg. + # 3) A RegExp object being passed to a RegExp or "object" arg. + # 4) A callable object being passed to a callback or "object" arg. + # 5) An iterable object being passed to a sequence arg. + # 6) Any non-Date and non-RegExp 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 typed arrays and arraybuffers) 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 Date + objectSigs.extend(s for s in possibleSignatures + if distinguishingType(s).isDate()) + + # 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 MozMap. There should be only one of these! + genericObjectSigs = [ + s for s in possibleSignatures + if (distinguishingType(s).isDictionary() or + distinguishingType(s).isMozMap() 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, typed arrays, + # sequences, and Dates 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 (0);\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 = filter(filterLambda, possibleSignatures) + 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 ThrowErrorMessage(cx, MSG_OVERLOAD_RESOLUTION_FAILED, "%d", "%d", "%s");\n' % + (distinguishingIndex + 1, argCount, methodName))) + + 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('return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, "%s");\n' % + methodName))) + 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): + 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) + + +class CGNavigatorGetterCall(CGPerSignatureCall): + """ + A class to generate a native object getter call for an IDL getter for a + property generated by NavigatorProperty. + """ + def __init__(self, returnType, _, descriptor, attr): + nativeMethodName = "%s::ConstructNavigatorObject" % (toBindingNamespace(returnType.inner.identifier.name)) + CGPerSignatureCall.__init__(self, returnType, [], nativeMethodName, + True, descriptor, attr, getter=True) + + def getArguments(self): + # The navigator object should be associated with the global of + # the navigator it's coming from, which will be the global of + # the object whose slot it gets cached in. That's stored in + # "slotStorage". + return [(FakeArgument(BuiltinTypes[IDLBuiltinType.Types.object], + self.idlNode), + "slotStorage")] + + +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, interfaceMember, name="arg", allowTreatNonCallableAsNull=False): + self.type = type + self.optional = False + self.variadic = False + self.defaultValue = None + self._allowTreatNonCallableAsNull = allowTreatNonCallableAsNull + # For FakeArguments generated by maplike/setlike convenience functions, + # we won't have an interfaceMember to pass in. + if interfaceMember: + self.treatNullAs = interfaceMember.treatNullAs + else: + self.treatNullAs = "Default" + if isinstance(interfaceMember, IDLAttribute): + self.enforceRange = interfaceMember.enforceRange + self.clamp = interfaceMember.clamp + else: + self.enforceRange = False + self.clamp = False + + 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, attr, allowTreatNonCallableAsNull=True)], + nativeMethodName, attr.isStatic(), + descriptor, attr, setter=True, useCounterName=useCounterName) + + def wrap_return_value(self): + attr = self.idlNode + if self.descriptor.wrapperCache and attr.slotIndices is not None: + if attr.getExtendedAttribute("StoreInSlot"): + args = "cx, self" + else: + args = "self" + clearSlot = ("%s(%s);\n" % + (MakeClearCachedValueNativeName(self.idlNode), args)) + else: + clearSlot = "" + + # We have no return value + return ("\n" + "%s" + "return true;\n" % clearSlot) + + +class CGAbstractBindingMethod(CGAbstractStaticMethod): + """ + Common class to generate the JSNatives for all our methods, getters, and + setters. This will generate the function declaration and unwrap the + |this| 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. + + getThisObj should be code for getting a JSObject* for the binding + object. If this is None, we will auto-generate code based on + descriptor to do the right thing. "" 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. + + If allowCrossOriginThis is true, then this-unwrapping will first do an + UncheckedUnwrap and after that operate on the result. + """ + def __init__(self, descriptor, name, args, unwrapFailureCode=None, + getThisObj=None, + callArgs="JS::CallArgs args = JS::CallArgsFromVp(argc, vp);\n", + allowCrossOriginThis=False): + CGAbstractStaticMethod.__init__(self, descriptor, name, "bool", args) + + if unwrapFailureCode is None: + self.unwrapFailureCode = 'return ThrowErrorMessage(cx, MSG_THIS_DOES_NOT_IMPLEMENT_INTERFACE, "Value", "%s");\n' % descriptor.interface.identifier.name + else: + self.unwrapFailureCode = unwrapFailureCode + + if getThisObj == "": + self.getThisObj = None + else: + if getThisObj is None: + if descriptor.interface.isOnGlobalProtoChain(): + ensureCondition = "!args.thisv().isNullOrUndefined() && !args.thisv().isObject()" + getThisObj = "args.thisv().isObject() ? &args.thisv().toObject() : js::GetGlobalForObjectCrossCompartment(&args.callee())" + else: + ensureCondition = "!args.thisv().isObject()" + getThisObj = "&args.thisv().toObject()" + unwrapFailureCode = self.unwrapFailureCode % {'securityError': 'false'} + ensureThisObj = CGIfWrapper(CGGeneric(unwrapFailureCode), + ensureCondition) + else: + ensureThisObj = None + self.getThisObj = CGList( + [ensureThisObj, + CGGeneric("JS::Rooted<JSObject*> obj(cx, %s);\n" % + getThisObj)]) + self.callArgs = callArgs + self.allowCrossOriginThis = allowCrossOriginThis + + 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)); + """) + + # Our descriptor might claim that we're not castable, simply because + # we're someone's consequential interface. But for this-unwrapping, we + # know that we're the real deal. So fake a descriptor here for + # consumption by CastableObjectUnwrapper. + body += str(CastableObjectUnwrapper( + self.descriptor, + "rootSelf", + "&rootSelf", + "self", + self.unwrapFailureCode, + allowCrossOriginObj=self.allowCrossOriginThis)) + + 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()) + + 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:]) + + +class CGGenericMethod(CGAbstractBindingMethod): + """ + A class for generating the C++ code for an IDL method. + + If allowCrossOriginThis is true, then this-unwrapping will first do an + UncheckedUnwrap and after that operate on the result. + """ + def __init__(self, descriptor, allowCrossOriginThis=False): + unwrapFailureCode = ( + 'return ThrowInvalidThis(cx, args, %%(securityError)s, "%s");\n' % + descriptor.interface.identifier.name) + name = "genericCrossOriginMethod" if allowCrossOriginThis else "genericMethod" + CGAbstractBindingMethod.__init__(self, descriptor, name, + JSNativeArguments(), + unwrapFailureCode=unwrapFailureCode, + allowCrossOriginThis=allowCrossOriginThis) + + def generate_code(self): + return CGGeneric(dedent(""" + const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev()); + 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 ok; + """)) + + +class CGGenericPromiseReturningMethod(CGAbstractBindingMethod): + """ + A class for generating the C++ code for an IDL method that returns a Promise. + + Does not handle cross-origin this. + """ + def __init__(self, descriptor): + unwrapFailureCode = dedent(""" + ThrowInvalidThis(cx, args, %%(securityError)s, "%s");\n + return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee), + args.rval());\n""" % + descriptor.interface.identifier.name) + + name = "genericPromiseReturningMethod" + customCallArgs = dedent(""" + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + // Make sure to save the callee before someone maybe messes with rval(). + JS::Rooted<JSObject*> callee(cx, &args.callee()); + """) + + CGAbstractBindingMethod.__init__(self, descriptor, name, + JSNativeArguments(), + callArgs=customCallArgs, + unwrapFailureCode=unwrapFailureCode) + + def generate_code(self): + return CGGeneric(dedent(""" + const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev()); + MOZ_ASSERT(info->type() == JSJitInfo::Method); + JSJitMethodOp method = info->method; + bool ok = method(cx, obj, self, JSJitMethodCallArgs(args)); + if (ok) { + #ifdef DEBUG + AssertReturnTypeMatchesJitinfo(info, args.rval()); + #endif + return true; + } + + MOZ_ASSERT(info->returnType() == JSVAL_TYPE_OBJECT); + return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee), + args.rval()); + """)) + + +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('%s*' % descriptor.nativeType, 'self'), + Argument('const JSJitMethodCallArgs&', 'args')] + CGAbstractStaticMethod.__init__(self, descriptor, name, 'bool', args) + + def definition_body(self): + nativeName = CGSpecializedMethod.makeNativeName(self.descriptor, + self.method) + return CGMethodCall(nativeName, self.method.isStatic(), self.descriptor, + self.method).define() + + @staticmethod + def makeNativeName(descriptor, method): + name = method.identifier.name + return MakeNativeName(descriptor.binaryNameFor(name)) + + +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) + + def definition_body(self): + return fill( + """ + // Make sure to save the callee before someone maybe messes + // with rval(). + JS::Rooted<JSObject*> callee(cx, &args.callee()); + bool ok = ${methodName}(${args}); + if (ok) { + return true; + } + return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee), + args.rval()); + """, + methodName=self.method.name, + args=", ".join(arg.name for arg in self.args)) + + @staticmethod + def makeName(methodName): + return methodName + "_promiseWrapper" + + +class CGJsonifierMethod(CGSpecializedMethod): + def __init__(self, descriptor, method): + assert method.isJsonifier() + CGSpecializedMethod.__init__(self, descriptor, method) + + def definition_body(self): + ret = dedent(""" + JS::Rooted<JSObject*> result(cx, JS_NewPlainObject(cx)); + if (!result) { + return false; + } + """) + + jsonDescriptors = [self.descriptor] + interface = self.descriptor.interface.parent + while interface: + descriptor = self.descriptor.getDescriptor(interface.identifier.name) + if descriptor.operations['Jsonifier']: + jsonDescriptors.append(descriptor) + interface = interface.parent + + # Iterate the array in reverse: oldest ancestor first + for descriptor in jsonDescriptors[::-1]: + ret += fill( + """ + if (!${parentclass}::JsonifyAttributes(cx, obj, 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)) + return CGMethodCall(nativeName, False, self.descriptor, + self._legacycaller) + + +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<JS::PropertyDescriptor> desc(cx); + if (!self->DoResolve(cx, obj, id, &desc)) { + return false; + } + if (!desc.object()) { + 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. + if (!desc.value().isUndefined()) { + desc.attributesRef() |= JSPROP_RESOLVING; + if (!JS_DefinePropertyById(cx, obj, id, desc)) { + 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')] + # Our "self" is actually the "obj" argument in this case, not the thisval. + CGAbstractBindingMethod.__init__( + self, descriptor, ENUMERATE_HOOK_NAME, + args, getThisObj="", callArgs="") + + def generate_code(self): + return CGGeneric(dedent(""" + AutoTArray<nsString, 8> names; + binding_detail::FastErrorResult rv; + self->GetOwnPropertyNames(cx, names, rv); + if (rv.MaybeSetPendingException(cx)) { + return false; + } + bool dummy; + for (uint32_t i = 0; i < names.Length(); ++i) { + if (!JS_HasUCProperty(cx, obj, names[i].get(), names[i].Length(), &dummy)) { + return false; + } + } + return true; + """)) + + def definition_body(self): + if self.descriptor.isGlobal(): + # Enumerate standard classes + prefix = dedent(""" + if (!EnumerateGlobal(cx, obj)) { + 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) + + +class CGGenericGetter(CGAbstractBindingMethod): + """ + A class for generating the C++ code for an IDL attribute getter. + """ + def __init__(self, descriptor, lenientThis=False, allowCrossOriginThis=False): + if lenientThis: + name = "genericLenientGetter" + unwrapFailureCode = dedent(""" + MOZ_ASSERT(!JS_IsExceptionPending(cx)); + if (!ReportLenientThisUnwrappingFailure(cx, &args.callee())) { + return false; + } + args.rval().set(JS::UndefinedValue()); + return true; + """) + else: + if allowCrossOriginThis: + name = "genericCrossOriginGetter" + else: + name = "genericGetter" + unwrapFailureCode = ( + 'return ThrowInvalidThis(cx, args, %%(securityError)s, "%s");\n' % + descriptor.interface.identifier.name) + CGAbstractBindingMethod.__init__(self, descriptor, name, JSNativeArguments(), + unwrapFailureCode, + allowCrossOriginThis=allowCrossOriginThis) + + def generate_code(self): + return CGGeneric(dedent(""" + const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev()); + 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 ok; + """)) + + +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('%s*' % descriptor.nativeType, 'self'), + Argument('JSJitGetterCallArgs', 'args') + ] + CGAbstractStaticMethod.__init__(self, descriptor, name, "bool", args) + + def definition_body(self): + if self.attr.isMaplikeOrSetlikeAttr(): + # If the interface is maplike/setlike, there will be one getter + # method for the size property of the backing object. Due to having + # to unpack the backing object from the slot, this requires its own + # generator. + return getMaplikeOrSetlikeSizeGetterBody(self.descriptor, self.attr) + nativeName = CGSpecializedGetter.makeNativeName(self.descriptor, + self.attr) + if self.attr.slotIndices is not None: + if self.descriptor.hasXPConnectImpls: + raise TypeError("Interface '%s' has XPConnect impls, so we " + "can't use our slot for property '%s'!" % + (self.descriptor.interface.identifier.name, + self.attr.identifier.name)) + + # 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 + # (navigator properties, window.document) we want to avoid calling + # the getter more than once. In the case of navigator properties + # that's because the getter actually creates a new object each time. + # In the case of window.document, it's because the getter can start + # returning null, which would get hidden in he 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::GetObjectClass(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)) + else: + prefix = "" + + if self.attr.navigatorObjectGetter: + cgGetterCall = CGNavigatorGetterCall + else: + cgGetterCall = CGGetterCall + return (prefix + + cgGetterCall(self.attr.type, nativeName, + self.descriptor, self.attr).define()) + + @staticmethod + def makeNativeName(descriptor, attr): + name = attr.identifier.name + nativeName = MakeNativeName(descriptor.binaryNameFor(name)) + _, resultOutParam, _, _, _ = getRetvalDeclarationForType(attr.type, + descriptor) + infallible = ('infallible' in + descriptor.getExtendedAttributes(attr, getter=True)) + if resultOutParam or attr.type.nullable() or not infallible: + nativeName = "Get" + nativeName + return nativeName + + +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) + + +class CGGenericSetter(CGAbstractBindingMethod): + """ + A class for generating the C++ code for an IDL attribute setter. + """ + def __init__(self, descriptor, lenientThis=False, allowCrossOriginThis=False): + if lenientThis: + name = "genericLenientSetter" + unwrapFailureCode = dedent(""" + MOZ_ASSERT(!JS_IsExceptionPending(cx)); + if (!ReportLenientThisUnwrappingFailure(cx, &args.callee())) { + return false; + } + args.rval().set(JS::UndefinedValue()); + return true; + """) + else: + if allowCrossOriginThis: + name = "genericCrossOriginSetter" + else: + name = "genericSetter" + unwrapFailureCode = ( + 'return ThrowInvalidThis(cx, args, %%(securityError)s, "%s");\n' % + descriptor.interface.identifier.name) + + CGAbstractBindingMethod.__init__(self, descriptor, name, JSNativeArguments(), + unwrapFailureCode, + allowCrossOriginThis=allowCrossOriginThis) + + def generate_code(self): + return CGGeneric(fill( + """ + if (args.length() == 0) { + return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, "${name} attribute setter"); + } + const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev()); + 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; + """, + name=self.descriptor.interface.identifier.name)) + + +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('%s*' % descriptor.nativeType, 'self'), + Argument('JSJitSetterCallArgs', 'args')] + CGAbstractStaticMethod.__init__(self, descriptor, name, "bool", args) + + def definition_body(self): + nativeName = CGSpecializedSetter.makeNativeName(self.descriptor, + self.attr) + return CGSetterCall(self.attr.type, nativeName, self.descriptor, + self.attr).define() + + @staticmethod + def makeNativeName(descriptor, attr): + name = attr.identifier.name + return "Set" + MakeNativeName(descriptor.binaryNameFor(name)) + + +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.length() == 0) { + return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, "${name} setter"); + } + """, + name=self.attr.identifier.name)) + call = CGSetterCall(self.attr.type, nativeName, self.descriptor, + self.attr) + return CGList([checkForArg, call]) + + +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 ThrowErrorMessage(cx, 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) + + +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) + + +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, nsIDocument::eLenientSetter); + return true; + """) + + +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=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) + + return fill( + """ + static const JSJitInfo ${infoName} = ${jitInfo}; + $*{slotAssert} + """, + infoName=infoName, + jitInfo=jitInfoInitializer(False), + slotAssert=slotAssert) + + def define(self): + if self.member.isAttr(): + getterinfo = ("%s_getterinfo" % + IDLToCIdentifier(self.member.identifier.name)) + # We need the cast here because JSJitGetterOp has a "void* self" + # while we have the right type. + getter = ("(JSJitGetterOp)get_%s" % + IDLToCIdentifier(self.member.identifier.name)) + getterinfal = "infallible" in self.descriptor.getExtendedAttributes(self.member, getter=True) + + movable = self.mayBeMovable() and getterinfal + eliminatable = self.mayBeEliminatable() and getterinfal + aliasSet = self.aliasSet() + + getterinfal = getterinfal 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") + 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("LenientSetter") 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.void]], + 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. + hasInfallibleImpl = "infallible" in self.descriptor.getExtendedAttributes(self.member) + 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 + # 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.isVoid(): + # No return, every time + return "JSVAL_TYPE_UNDEFINED" + if t.isSequence(): + return "JSVAL_TYPE_OBJECT" + if t.isMozMap(): + 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 reduce(CGMemberJITInfo.getSingleReturnType, + u.flatMemberTypes, "") + if t.isDictionary(): + return "JSVAL_TYPE_OBJECT" + if t.isDate(): + 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.isVoid() + 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.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)" % + reduce(CGMemberJITInfo.getSingleArgType, + u.flatMemberTypes, type)) + if t.isDictionary(): + return "JSJitInfo::Object" + if t.isDate(): + 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_MISSING, 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): + return '_' + 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 + strings = CGNamespace( + self.stringsNamespace(), + CGGeneric(declare=("extern const EnumEntry %s[%d];\n" % + (ENUM_ENTRY_VARIABLE_NAME, self.nEnumStrings())), + 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 declare(self): + decl = fill( + """ + enum class ${name} : uint32_t { + $*{enums} + EndGuard_ + }; + """, + name=self.enum.identifier.name, + enums=",\n".join(map(getEnumValueName, self.enum.values())) + ",\n") + strings = CGNamespace(self.stringsNamespace(), + CGGeneric(declare="extern const EnumEntry %s[%d];\n" + % (ENUM_ENTRY_VARIABLE_NAME, self.nEnumStrings()))) + 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() + + if type.isSequence() or type.isMozMap(): + if type.isSequence(): + wrapperType = "Sequence" + else: + wrapperType = "MozMap" + # We don't use the returned template here, so it's OK to just pass no + # sourceDescription. + elementInfo = getJSToNativeConversionInfo(type.inner, + descriptorProvider, + isMember=wrapperType) + return CGTemplatedType(wrapperType, elementInfo.declType, + 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) + # Allow null pointers for old-binding classes. + if type.unroll().inner.isExternal(): + typeName = CGWrapper(typeName, post="*") + else: + typeName = CGWrapper(typeName, post="&") + return typeName + + if type.isSpiderMonkeyInterface(): + typeName = CGGeneric(type.name) + return CGWrapper(typeName, post=" const &") + + if type.isDOMString() or type.isUSVString(): + return CGGeneric("const nsAString&") + + 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, + ownsMembers=False): + 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. + prefix = "" if ownsMembers else "mUnion." + tryNextCode = ("$*{destroyHolder}\n" + "%sDestroy%s();\n" + "tryNext = true;\n" + "return true;\n" % (prefix, name)) + + conversionInfo = getJSToNativeConversionInfo( + type, descriptorProvider, failureCode=tryNextCode, + isDefinitelyObject=not type.isDictionary(), + isMember=("OwningUnion" if ownsMembers else None), + sourceDescription="member of %s" % unionType) + + if conversionInfo.holderType is not None: + assert not ownsMembers + destroyHolder = "%s.reset();\n" % holderName + else: + destroyHolder = "" + + ctorNeedsCx = conversionInfo.declArgs == "cx" + ctorArgs = "cx" if ctorNeedsCx else "" + + structType = conversionInfo.declType.define() + externalType = getUnionAccessorSignatureType(type, descriptorProvider).define() + + if type.isObject(): + if ownsMembers: + body = dedent(""" + MOZ_ASSERT(mType == eUninitialized); + mValue.mObject.SetValue(obj); + mType = eObject; + """) + else: + body = dedent(""" + MOZ_ASSERT(mUnion.mType == mUnion.eUninitialized); + mUnion.mValue.mObject.SetValue(cx, obj); + mUnion.mType = mUnion.eObject; + """) + + # 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 + dedent(""" + if (passedToJSImpl && !CallerSubsumes(obj)) { + ThrowErrorMessage(cx, MSG_PERMISSION_DENIED_TO_PASS_ARG, "%s"); + return false; + } + return true; + """) + + setter = ClassMethod("SetToObject", "bool", + [Argument("JSContext*", "cx"), + Argument("JSObject*", "obj"), + Argument("bool", "passedToJSImpl", default="false")], + inline=True, bodyInHeader=True, + body=body) + + else: + # Important: we need to not have our declName involve + # maybe-GCing operations. + if conversionInfo.holderType is not None: + holderArgs = conversionInfo.holderArgs + if holderArgs is None: + holderArgs = "" + initHolder = "%s.emplace(%s);\n" % (holderName, holderArgs) + else: + initHolder = "" + + jsConversion = fill( + initHolder + conversionInfo.template, + val="value", + maybeMutableVal="value", + declName="memberSlot", + holderName=(holderName if ownsMembers else "%s.ref()" % holderName), + destroyHolder=destroyHolder, + 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) + + if ownsMembers: + handleType = "JS::Handle<JS::Value>" + else: + handleType = "JS::MutableHandle<JS::Value>" + + setter = ClassMethod("TrySetTo" + name, "bool", + [Argument("JSContext*", "cx"), + Argument(handleType, "value"), + Argument("bool&", "tryNext"), + Argument("bool", "passedToJSImpl", default="false")], + inline=not ownsMembers, + bodyInHeader=not ownsMembers, + body=jsConversion) + + return { + "name": name, + "structType": structType, + "externalType": externalType, + "setter": setter, + "holderType": conversionInfo.holderType.define() if conversionInfo.holderType else None, + "ctorArgs": ctorArgs, + "ctorArgList": [Argument("JSContext*", "cx")] if ctorNeedsCx else [] + } + + +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", "Type", body="eUninitialized"), + ClassMember("mValue", "Value")] + ctor = ClassConstructor([], bodyInHeader=True, visibility="public", + explicit=True) + + methods = [] + enumValues = ["eUninitialized"] + toJSValCases = [CGCase("eUninitialized", CGGeneric("return false;\n"))] + destructorCases = [CGCase("eUninitialized", None)] + assignmentCases = [ + CGCase("eUninitialized", + CGGeneric('MOZ_ASSERT(mType == eUninitialized,\n' + ' "We need to destroy ourselves?");\n'))] + traceCases = [] + unionValues = [] + if self.type.hasNullableType: + enumValues.append("eNull") + methods.append(ClassMethod("IsNull", "bool", [], const=True, inline=True, + body="return mType == eNull;\n", + bodyInHeader=True)) + methods.append(ClassMethod("SetNull", "void", [], inline=True, + body=("Uninit();\n" + "mType = eNull;\n"), + bodyInHeader=True)) + destructorCases.append(CGCase("eNull", None)) + assignmentCases.append(CGCase("eNull", + CGGeneric("MOZ_ASSERT(mType == eUninitialized);\n" + "mType = eNull;\n"))) + toJSValCases.append(CGCase("eNull", CGGeneric("rval.setNull();\n" + "return true;\n"))) + + hasObjectType = any(t.isObject() for t in self.type.flatMemberTypes) + for t in self.type.flatMemberTypes: + vars = getUnionTypeTemplateVars(self.type, + t, self.descriptorProvider, + ownsMembers=self.ownsMembers) + if vars["name"] != "Object" 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);")) + uninit = "Uninit();" + if hasObjectType and not self.ownsMembers: + uninit = 'MOZ_ASSERT(mType != eObject, "This will not play well with Rooted");\n' + uninit + methods.append(ClassMethod( + "SetAs" + vars["name"], + vars["structType"] + "&", + vars["ctorArgList"], + bodyInHeader=not self.ownsMembers, + body=body % uninit)) + if self.ownsMembers: + methods.append(vars["setter"]) + # Provide a SetStringData() method to support string defaults. + if t.isByteString(): + methods.append( + ClassMethod("SetStringData", "void", + [Argument("const nsCString::char_type*", "aData"), + Argument("nsCString::size_type", "aLength")], + inline=True, bodyInHeader=True, + body="RawSetAs%s().Assign(aData, aLength);\n" % t.name)) + elif t.isString(): + methods.append( + ClassMethod("SetStringData", "void", + [Argument("const nsString::char_type*", "aData"), + Argument("nsString::size_type", "aLength")], + inline=True, bodyInHeader=True, + body="RawSetAs%s().Assign(aData, aLength);\n" % t.name)) + + body = fill( + """ + MOZ_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("return mType == e${name};\n", **vars) + methods.append(ClassMethod("Is" + vars["name"], + "bool", + [], + const=True, + bodyInHeader=True, + body=body)) + + body = fill( + """ + MOZ_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)) + enumValues.append("e" + vars["name"]) + + skipToJSVal = False + try: + toJSValCases.append( + CGCase("e" + vars["name"], + self.getConversionToJS(vars, t))) + except MethodNotNewObjectError: + # If we can't have a ToJSVal() because one of our members can + # only be returned from [NewObject] methods, then just skip + # generating ToJSVal. + skipToJSVal = True + destructorCases.append( + CGCase("e" + vars["name"], + CGGeneric("Destroy%s();\n" % vars["name"]))) + assignmentCases.append( + CGCase("e" + vars["name"], + CGGeneric("SetAs%s() = aOther.GetAs%s();\n" % + (vars["name"], vars["name"])))) + if self.ownsMembers and typeNeedsRooting(t): + if t.isObject(): + traceCases.append( + CGCase("e" + vars["name"], + CGGeneric('JS::UnsafeTraceRoot(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.isMozMap(): + traceCases.append( + CGCase("e" + vars["name"], + CGGeneric("TraceMozMap(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.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() + "\nreturn false;\n", + const=True)) + + constructors = [ctor] + selfName = CGUnionStruct.unionTypeName(self.type, self.ownsMembers) + if self.ownsMembers: + if traceCases: + traceBody = CGSwitch("mType", traceCases, + default=CGGeneric("")).define() + else: + traceBody = "" + methods.append(ClassMethod("TraceUnion", "void", + [Argument("JSTracer*", "trc")], + body=traceBody)) + if CGUnionStruct.isUnionCopyConstructible(self.type): + constructors.append( + ClassConstructor( + [Argument("const %s&" % selfName, "aOther")], + bodyInHeader=True, + visibility="public", + explicit=True, + body="*this = aOther;\n")) + methods.append(ClassMethod( + "operator=", "void", + [Argument("const %s&" % selfName, "aOther")], + body=CGSwitch("aOther.mType", assignmentCases).define())) + disallowCopyConstruction = False + else: + disallowCopyConstruction = True + else: + disallowCopyConstruction = True + + if self.ownsMembers: + friend = " friend void ImplCycleCollectionUnlink(%s& aUnion);\n" % CGUnionStruct.unionTypeName(self.type, True) + else: + friend = " friend class %sArgument;\n" % str(self.type) + + bases = [ClassBase("AllOwningUnionBase")] if self.ownsMembers else [] + 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=[ClassEnum("Type", enumValues, visibility="private")], + unions=[ClassUnion("Value", unionValues, visibility="private")]) + + def getConversionToJS(self, templateVars, type): + 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, + "typedArraysAreStructs": 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 CGUnionConversionStruct(CGThing): + def __init__(self, type, descriptorProvider): + CGThing.__init__(self) + self.type = type.unroll() + self.descriptorProvider = descriptorProvider + + def declare(self): + + structName = str(self.type) + members = [ClassMember("mUnion", structName + "&", + body="const_cast<%s&>(aUnion)" % structName)] + # Argument needs to be a const ref because that's all Maybe<> allows + ctor = ClassConstructor([Argument("const %s&" % structName, "aUnion")], + bodyInHeader=True, + visibility="public", + explicit=True) + methods = [] + + if self.type.hasNullableType: + methods.append(ClassMethod("SetNull", "bool", [], + body=("MOZ_ASSERT(mUnion.mType == mUnion.eUninitialized);\n" + "mUnion.mType = mUnion.eNull;\n" + "return true;\n"), + inline=True, bodyInHeader=True)) + + for t in self.type.flatMemberTypes: + vars = getUnionTypeTemplateVars(self.type, + t, self.descriptorProvider) + methods.append(vars["setter"]) + if vars["name"] != "Object": + body = fill( + """ + MOZ_ASSERT(mUnion.mType == mUnion.eUninitialized); + mUnion.mType = mUnion.e${name}; + return mUnion.mValue.m${name}.SetValue(${ctorArgs}); + """, + **vars) + methods.append(ClassMethod("RawSetAs" + vars["name"], + vars["structType"] + "&", + vars["ctorArgList"], + bodyInHeader=True, + body=body, + visibility="private")) + # Provide a SetStringData() method to support string defaults. + if t.isByteString(): + methods.append( + ClassMethod("SetStringData", "void", + [Argument("const nsDependentCString::char_type*", "aData"), + Argument("nsDependentCString::size_type", "aLength")], + inline=True, bodyInHeader=True, + body="RawSetAs%s().Rebind(aData, aLength);\n" % t.name)) + elif t.isString(): + methods.append( + ClassMethod("SetStringData", "void", + [Argument("const nsDependentString::char_type*", "aData"), + Argument("nsDependentString::size_type", "aLength")], + inline=True, bodyInHeader=True, + body="RawSetAs%s().Rebind(aData, aLength);\n" % t.name)) + + if vars["holderType"] is not None: + holderType = CGTemplatedType("Maybe", + CGGeneric(vars["holderType"])).define() + members.append(ClassMember("m%sHolder" % vars["name"], + holderType)) + + return CGClass(structName + "Argument", + members=members, + constructors=[ctor], + methods=methods, + disallowCopyConstruction=True).declare() + + def define(self): + return "" + + def deps(self): + return set() + + +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): + """ + 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 + ClassItem.__init__(self, name, visibility) + + def getDecorators(self, declaring): + decorators = [] + if self.inline: + decorators.append('inline') + if declaring: + if self.static: + decorators.append('static') + if self.virtual: + 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. + + 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, + visibility="private", explicit=False, constexpr=False, baseConstructors=None, + body=""): + assert not (inline and constexpr) + assert not (bodyInHeader and constexpr) + self.args = args + self.inline = inline or bodyInHeader + self.bodyInHeader = bodyInHeader or constexpr + self.explicit = explicit + self.constexpr = constexpr + self.baseConstructors = baseConstructors or [] + self.body = body + ClassItem.__init__(self, None, visibility) + + def getDecorators(self, declaring): + decorators = [] + if self.explicit: + decorators.append('explicit') + if self.inline and declaring: + decorators.append('inline') + if self.constexpr and declaring: + 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: + 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 ClassTypedef(ClassItem): + def __init__(self, name, type, visibility="public"): + self.type = type + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return 'typedef %s %s;\n' % (self.type, self.name) + + def define(self, cgClass): + # Only goes in the header + return '' + + +class ClassEnum(ClassItem): + def __init__(self, name, entries, values=None, visibility="public"): + self.entries = entries + self.values = values + 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) + name = '' if not self.name else ' ' + self.name + return 'enum%s\n{\n%s\n};\n' % (name, 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=[], + typedefs=[], 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.typedefs = typedefs + 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" + "void operator=(const %s&) = delete;\n" % (name, name, name)) + + disallowedCopyConstructors = [DisallowedCopyConstructor()] + else: + disallowedCopyConstructors = [] + + order = [self.enums, self.unions, + self.typedefs, 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 CGResolveOwnProperty(CGAbstractStaticMethod): + 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<JS::PropertyDescriptor>', 'desc'), + ] + CGAbstractStaticMethod.__init__(self, descriptor, "ResolveOwnProperty", + "bool", args) + + def definition_body(self): + return "return js::GetProxyHandler(obj)->getOwnPropertyDescriptor(cx, wrapper, id, desc);\n" + + +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<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. + JSAutoCompartment ac(cx, obj); + JS::Rooted<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.object() && + !objDesc.value().isUndefined() && + !JS_DefinePropertyById(cx, obj, id, objDesc)) { + return false; + } + } + return self->DoResolve(cx, wrapper, id, desc); + """)) + + +class CGEnumerateOwnProperties(CGAbstractStaticMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle<JSObject*>', 'wrapper'), + Argument('JS::Handle<JSObject*>', 'obj'), + Argument('JS::AutoIdVector&', 'props')] + CGAbstractStaticMethod.__init__(self, descriptor, + "EnumerateOwnProperties", "bool", args) + + def definition_body(self): + return "return js::GetProxyHandler(obj)->ownPropertyKeys(cx, wrapper, props);\n" + + +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::AutoIdVector&', 'props')] + CGAbstractBindingMethod.__init__(self, descriptor, + "EnumerateOwnPropertiesViaGetOwnPropertyNames", + args, getThisObj="", + callArgs="") + + def generate_code(self): + return CGGeneric(dedent(""" + AutoTArray<nsString, 8> names; + binding_detail::FastErrorResult rv; + self->GetOwnPropertyNames(cx, names, rv); + if (rv.MaybeSetPendingException(cx)) { + return false; + } + // OK to pass null as "proxy" because it's ignored if + // shadowPrototypeProperties is true + return AppendNamedPropertyIds(cx, nullptr, names, true, props); + """)) + + +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)) + 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) + + if operation.isSetter() or operation.isCreator(): + # arguments[0] is the index or name of the item that we're setting. + argument = arguments[1] + info = getJSToNativeConversionInfo( + argument.type, descriptor, + treatNullAs=argument.treatNullAs, + 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.idlNode), + 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. + """ + def __init__(self, descriptor, name, value=None, argumentHandleValue=None, + resultVar=None, foundVar=None): + CGProxySpecialOperation.__init__(self, descriptor, name, + argumentHandleValue=argumentHandleValue, + resultVar=resultVar, + foundVar=foundVar) + self.value = value + + 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 += "binding_detail::FakeString %s;\n" % argName + + main = fill( + """ + ${nativeType}* self = UnwrapProxy(proxy); + $*{op} + """, + nativeType=self.descriptor.nativeType, + op=CGProxySpecialOperation.define(self)) + + 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, argumentHandleValue=None): + CGProxyNamedOperation.__init__(self, descriptor, 'NamedSetter', + argumentHandleValue=argumentHandleValue) + + +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::GetProxyPrivate(obj).toPrivate()); + """, + type=self.descriptor.nativeType) + + +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<JS::PropertyDescriptor>', 'desc')] + ClassMethod.__init__(self, "getOwnPropDescriptor", "bool", args, + virtual=True, override=True, const=True) + self.descriptor = descriptor + + def getBody(self): + indexedGetter = self.descriptor.operations['IndexedGetter'] + indexedSetter = self.descriptor.operations['IndexedSetter'] + + if self.descriptor.supportsIndexedProperties(): + readonly = toStringBool(indexedSetter is None) + fillDescriptor = "FillPropertyDescriptor(desc, proxy, %s);\nreturn true;\n" % readonly + templateValues = { + 'jsvalRef': 'desc.value()', + 'jsvalHandle': 'desc.value()', + 'obj': 'proxy', + 'successCode': fillDescriptor + } + getIndexed = fill( + """ + uint32_t index = GetArrayIndexFromId(cx, id); + if (IsArrayIndex(index)) { + $*{callGetter} + } + + """, + callGetter=CGProxyIndexedGetter(self.descriptor, templateValues).define()) + else: + getIndexed = "" + + if self.descriptor.supportsNamedProperties(): + operations = self.descriptor.operations + readonly = toStringBool(operations['NamedSetter'] is None) + fillDescriptor = ( + "FillPropertyDescriptor(desc, proxy, %s, %s);\n" + "return true;\n" % + (readonly, + toStringBool(self.descriptor.namedPropertiesEnumerable))) + templateValues = {'jsvalRef': 'desc.value()', 'jsvalHandle': 'desc.value()', + 'obj': 'proxy', 'successCode': fillDescriptor} + + computeCondition = dedent(""" + bool hasOnProto; + if (!HasPropertyOnPrototype(cx, proxy, id, &hasOnProto)) { + return false; + } + callNamedGetter = !hasOnProto; + """) + if self.descriptor.interface.getExtendedAttribute('OverrideBuiltins'): + 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) { + $*{namedGetCode} + } + """, + outerCondition=outerCondition, + computeCondition=computeCondition, + namedGetCode=namedGetCode) + namedGet += "\n" + else: + namedGet = "" + + return fill( + """ + bool isXray = xpc::WrapperFactory::IsXrayWrapper(proxy); + $*{getIndexed} + JS::Rooted<JSObject*> expando(cx); + if (!isXray && (expando = GetExpandoObject(proxy))) { + if (!JS_GetOwnPropertyDescriptorById(cx, expando, id, desc)) { + return false; + } + if (desc.object()) { + // Pretend the property lives on the wrapper. + desc.object().set(proxy); + return true; + } + } + + $*{namedGet} + desc.object().set(nullptr); + return true; + """, + getIndexed=getIndexed, + 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*', 'defined')] + 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: + if self.descriptor.operations['IndexedCreator'] is not indexedSetter: + raise TypeError("Can't handle creator that's different from the setter") + set += fill( + """ + uint32_t index = GetArrayIndexFromId(cx, id); + if (IsArrayIndex(index)) { + *defined = true; + $*{callSetter} + return opresult.succeed(); + } + """, + 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(cx, id))) { + *defined = true; + return opresult.failNoIndexedSetter(); + } + """) + + namedSetter = self.descriptor.operations['NamedSetter'] + if namedSetter: + if self.descriptor.hasUnforgeableMembers: + raise TypeError("Can't handle a named setter on an interface " + "that has unforgeables. Figure out how that " + "should work!") + if self.descriptor.operations['NamedCreator'] is not namedSetter: + raise TypeError("Can't handle creator that's different from the setter") + # If we support indexed properties, we won't get down here for + # indices, so we can just do our setter unconditionally here. + set += fill( + """ + *defined = true; + $*{callSetter} + + return opresult.succeed(); + """, + callSetter=CGProxyNamedSetter(self.descriptor).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( + """ + bool found = false; + $*{presenceChecker} + + if (found) { + *defined = true; + return opresult.failNoNamedSetter(); + } + """, + presenceChecker=CGProxyNamedPresenceChecker(self.descriptor, foundVar="found").define()) + 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.hasUnforgeableMembers: + 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)); + JSAutoCompartment ac(cx, proxy); + bool deleteSucceeded; + 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"); + + """) + + indexedBody = getDeleterBody(self.descriptor, "Indexed") + if indexedBody is not None: + delete += fill( + """ + uint32_t index = GetArrayIndexFromId(cx, 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('OverrideBuiltins'): + 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::AutoIdVector&', 'props')] + ClassMethod.__init__(self, "ownPropNames", "bool", args, + virtual=True, override=True, const=True) + self.descriptor = descriptor + + def getBody(self): + # Per spec, we do indices, then named props, then everything else + if self.descriptor.supportsIndexedProperties(): + addIndices = dedent(""" + + uint32_t length = UnwrapProxy(proxy)->Length(); + MOZ_ASSERT(int32_t(length) >= 0); + for (int32_t i = 0; i < int32_t(length); ++i) { + if (!props.append(INT_TO_JSID(i))) { + return false; + } + } + """) + else: + addIndices = "" + + if self.descriptor.supportsNamedProperties(): + if self.descriptor.interface.getExtendedAttribute('OverrideBuiltins'): + shadow = "!isXray" + else: + shadow = "false" + addNames = fill( + """ + nsTArray<nsString> names; + UnwrapProxy(proxy)->GetSupportedNames(names); + if (!AppendNamedPropertyIds(cx, proxy, names, ${shadow}, props)) { + return false; + } + """, + shadow=shadow) + if not self.descriptor.namedPropertiesEnumerable: + addNames = CGIfWrapper(CGGeneric(addNames), + "flags & JSITER_HIDDEN").define() + addNames = "\n" + addNames + else: + addNames = "" + + return fill( + """ + bool isXray = xpc::WrapperFactory::IsXrayWrapper(proxy); + $*{addIndices} + $*{addNames} + + JS::Rooted<JSObject*> expando(cx); + if (!isXray && (expando = DOMProxyHandler::GetExpandoObject(proxy)) && + !js::GetPropertyKeys(cx, expando, flags, &props)) { + return false; + } + + return true; + """, + addIndices=addIndices, + addNames=addNames) + + +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.supportsIndexedProperties(): + indexed = fill( + """ + uint32_t index = GetArrayIndexFromId(cx, 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('OverrideBuiltins'): + 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" + + return fill( + """ + MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), + "Should not have a XrayWrapper here"); + + $*{indexed} + + 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; + """, + indexed=indexed, + 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): + getUnforgeableOrExpando = dedent(""" + { // Scope for expando + JS::Rooted<JSObject*> expando(cx, DOMProxyHandler::GetExpandoObject(proxy)); + if (expando) { + bool hasProp; + if (!JS_HasPropertyById(cx, expando, id, &hasProp)) { + return false; + } + + if (hasProp) { + // Forward the get to the expando object, but our receiver is whatever our + // receiver is. + return JS_ForwardGetPropertyTo(cx, expando, id, receiver, vp); + } + } + } + """) + + templateValues = {'jsvalRef': 'vp', 'jsvalHandle': 'vp', 'obj': 'proxy'} + + if self.descriptor.supportsIndexedProperties(): + getIndexedOrExpando = fill( + """ + uint32_t index = GetArrayIndexFromId(cx, 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 = dedent(""" + bool foundOnPrototype; + if (!GetPropertyOnPrototype(cx, proxy, receiver, id, &foundOnPrototype, vp)) { + return false; + } + + if (foundOnPrototype) { + return true; + } + + """) + if self.descriptor.interface.getExtendedAttribute('OverrideBuiltins'): + getNamed = getNamed + getOnPrototype + else: + getNamed = getOnPrototype + getNamed + + return fill( + """ + MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), + "Should not have a XrayWrapper here"); + + $*{indexedOrExpando} + + $*{named} + vp.setUndefined(); + return true; + """, + indexedOrExpando=getIndexedOrExpando, + named=getNamed) + + +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 [OverrideBuiltins], + # always call the NamedSetter and never do anything else. + namedSetter = self.descriptor.operations['NamedSetter'] + if (namedSetter is not None and + self.descriptor.interface.getExtendedAttribute('OverrideBuiltins')): + # Check assumptions. + if self.descriptor.supportsIndexedProperties(): + raise ValueError("In interface " + self.descriptor.name + ": " + + "Can't cope with [OverrideBuiltins] and an indexed getter") + if self.descriptor.operations['NamedCreator'] is not namedSetter: + raise ValueError("In interface " + self.descriptor.name + ": " + + "Can't cope with named setter that is not also a named creator") + if self.descriptor.hasUnforgeableMembers: + raise ValueError("In interface " + self.descriptor.name + ": " + + "Can't cope with [OverrideBuiltins] and unforgeable members") + + callSetter = CGProxyNamedSetter(self.descriptor, argumentHandleValue="v") + return (assertion + + callSetter.define() + + "*done = true;\n" + "return true;\n") + + # 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: + if self.descriptor.operations['IndexedCreator'] is not indexedSetter: + raise ValueError("In interface " + self.descriptor.name + ": " + + "Can't cope with indexed setter that is not " + + "also an indexed creator") + setIndexed = fill( + """ + uint32_t index = GetArrayIndexFromId(cx, id); + if (IsArrayIndex(index)) { + $*{callSetter} + *done = true; + return true; + } + + """, + 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): + return 'return "%s";\n' % 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('JSFreeOp*', 'fop'), 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).define()) + + +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() + + 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(); + // 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, + get=get) + + +class CGDOMJSProxyHandler_getInstance(ClassMethod): + def __init__(self): + ClassMethod.__init__(self, "getInstance", "const DOMProxyHandler*", [], static=True) + + def getBody(self): + return dedent(""" + static const DOMProxyHandler instance; + return &instance; + """) + + +class CGDOMJSProxyHandler_getPrototypeIfOrdinary(ClassMethod): + def __init__(self): + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle<JSObject*>', 'proxy'), + Argument('bool*', 'isOrdinary'), + Argument('JS::MutableHandle<JSObject*>', 'proto')] + + ClassMethod.__init__(self, "getPrototypeIfOrdinary", "bool", args, + virtual=True, override=True, const=True) + + def getBody(self): + return dedent(""" + *isOrdinary = false; + return true; + """) + + +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(CGClass): + def __init__(self, descriptor): + assert (descriptor.supportsIndexedProperties() or + descriptor.supportsNamedProperties() or + descriptor.hasNonOrdinaryGetPrototypeOf()) + 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), + CGDOMJSProxyHandler_getInstance(), + 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('OverrideBuiltins'))): + methods.append(CGDOMJSProxyHandler_setCustom(descriptor)) + if descriptor.hasNonOrdinaryGetPrototypeOf(): + methods.append(CGDOMJSProxyHandler_getPrototypeIfOrdinary()) + if descriptor.operations['LegacyCaller']: + methods.append(CGDOMJSProxyHandler_call()) + methods.append(CGDOMJSProxyHandler_isCallable()) + + if descriptor.interface.getExtendedAttribute('OverrideBuiltins'): + parentClass = 'ShadowingDOMProxyHandler' + 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.isGenericMethod = False + self.isCrossOriginMethod = False + self.isPromiseReturningMethod = False + self.isGenericGetter = False + self.isLenientGetter = False + self.isCrossOriginGetter = False + self.isGenericSetter = False + self.isLenientSetter = False + self.isCrossOriginSetter = False + self.isJsonifier = False + + +def memberProperties(m, descriptor): + props = MemberProperties() + if m.isMethod(): + if m == descriptor.operations['Jsonifier']: + props.isGenericMethod = descriptor.needsSpecialGenericOps() + props.isJsonifier = True + elif (not m.isIdentifierLess() or m == descriptor.operations['Stringifier']): + if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject(): + if descriptor.needsSpecialGenericOps(): + if m.returnsPromise(): + props.isPromiseReturningMethod = True + else: + props.isGenericMethod = True + if m.getExtendedAttribute("CrossOriginCallable"): + props.isCrossOriginMethod = True + elif m.isAttr(): + if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject(): + if m.hasLenientThis(): + props.isLenientGetter = True + elif m.getExtendedAttribute("CrossOriginReadable"): + props.isCrossOriginGetter = True + elif descriptor.needsSpecialGenericOps(): + props.isGenericGetter = True + if not m.readonly: + if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject(): + if m.hasLenientThis(): + props.isLenientSetter = True + elif IsCrossOriginWritable(m, descriptor): + props.isCrossOriginSetter = True + elif descriptor.needsSpecialGenericOps(): + props.isGenericSetter = True + elif m.getExtendedAttribute("PutForwards"): + if IsCrossOriginWritable(m, descriptor): + props.isCrossOriginSetter = True + elif descriptor.needsSpecialGenericOps(): + props.isGenericSetter = True + elif (m.getExtendedAttribute("Replaceable") or + m.getExtendedAttribute("LenientSetter")): + if descriptor.needsSpecialGenericOps(): + props.isGenericSetter = 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() + + cgThings = [] + 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))) + + # These are set to true if at least one non-static + # method/getter/setter or jsonifier exist on the interface. + (hasMethod, hasGetter, hasLenientGetter, hasSetter, hasLenientSetter, + hasPromiseReturningMethod) = False, False, False, False, False, False + jsonifierMethod = None + crossOriginMethods, crossOriginGetters, crossOriginSetters = set(), set(), set() + unscopableNames = list() + for n in descriptor.interface.namedConstructors: + cgThings.append(CGClassConstructor(descriptor, n, + NamedConstructorName(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 props.isJsonifier: + jsonifierMethod = 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: + crossOriginMethods.add(m.identifier.name) + # If we've hit the maplike/setlike member itself, go ahead and + # generate its convenience functions. + elif m.isMaplikeOrSetlike(): + cgThings.append(CGMaplikeOrSetlikeHelperGenerator(descriptor, m)) + elif m.isAttr(): + if m.stringifier: + raise TypeError("Stringifier attributes not supported yet. " + "See bug 824857.\n" + "%s" % m.location) + 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(): + if isNonExposedNavigatorObjectGetter(m, descriptor): + continue + cgThings.append(CGSpecializedGetter(descriptor, m)) + if props.isCrossOriginGetter: + crossOriginGetters.add(m.identifier.name) + 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: + crossOriginSetters.add(m.identifier.name) + elif m.getExtendedAttribute("PutForwards"): + cgThings.append(CGSpecializedForwardingSetter(descriptor, m)) + if props.isCrossOriginSetter: + crossOriginSetters.add(m.identifier.name) + elif m.getExtendedAttribute("Replaceable"): + cgThings.append(CGSpecializedReplaceableSetter(descriptor, m)) + elif m.getExtendedAttribute("LenientSetter"): + cgThings.append(CGSpecializedLenientSetter(descriptor, m)) + if (not m.isStatic() and + descriptor.interface.hasInterfacePrototypeObject()): + cgThings.append(CGMemberJITInfo(descriptor, m)) + + hasMethod = hasMethod or props.isGenericMethod + hasPromiseReturningMethod = (hasPromiseReturningMethod or + props.isPromiseReturningMethod) + hasGetter = hasGetter or props.isGenericGetter + hasLenientGetter = hasLenientGetter or props.isLenientGetter + hasSetter = hasSetter or props.isGenericSetter + hasLenientSetter = hasLenientSetter or props.isLenientSetter + + if jsonifierMethod: + cgThings.append(CGJsonifyAttributesMethod(descriptor)) + cgThings.append(CGJsonifierMethod(descriptor, jsonifierMethod)) + cgThings.append(CGMemberJITInfo(descriptor, jsonifierMethod)) + if hasMethod: + cgThings.append(CGGenericMethod(descriptor)) + if hasPromiseReturningMethod: + cgThings.append(CGGenericPromiseReturningMethod(descriptor)) + if len(crossOriginMethods): + cgThings.append(CGGenericMethod(descriptor, + allowCrossOriginThis=True)) + if hasGetter: + cgThings.append(CGGenericGetter(descriptor)) + if hasLenientGetter: + cgThings.append(CGGenericGetter(descriptor, lenientThis=True)) + if len(crossOriginGetters): + cgThings.append(CGGenericGetter(descriptor, + allowCrossOriginThis=True)) + if hasSetter: + cgThings.append(CGGenericSetter(descriptor)) + if hasLenientSetter: + cgThings.append(CGGenericSetter(descriptor, lenientThis=True)) + if len(crossOriginSetters): + cgThings.append(CGGenericSetter(descriptor, + allowCrossOriginThis=True)) + + if descriptor.interface.isNavigatorProperty(): + cgThings.append(CGConstructNavigatorObject(descriptor)) + + 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 descriptor.concrete and descriptor.wrapperCache: + cgThings.append(CGClassObjectMovedHook(descriptor)) + + # Generate the _ClearCachedFooValue methods before the property arrays that use them. + if descriptor.interface.isJSImplemented(): + for m in clearableCachedAttrs(descriptor): + cgThings.append(CGJSImplClearCachedValueMethod(descriptor, m)) + + # Need to output our generated hasinstance bits before + # PropertyArrays tries to use them. + if (descriptor.interface.hasInterfaceObject() and + NeedsGeneratedHasInstance(descriptor)): + cgThings.append(CGHasInstanceHook(descriptor)) + + properties = PropertyArrays(descriptor) + cgThings.append(CGGeneric(define=str(properties))) + cgThings.append(CGNativeProperties(descriptor, properties)) + + if descriptor.interface.hasInterfaceObject(): + cgThings.append(CGClassConstructor(descriptor, + descriptor.interface.ctor())) + cgThings.append(CGInterfaceObjectJSClass(descriptor, properties)) + cgThings.append(CGNamedConstructors(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() or descriptor.interface.isNavigatorProperty()) and + not descriptor.interface.isExternal() and + descriptor.isExposedConditionally()): + cgThings.append(CGConstructorEnabled(descriptor)) + + if descriptor.registersGlobalNamesOnWindow: + cgThings.append(CGDefineDOMInterfaceMethod(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.concrete: + if descriptor.proxy: + if descriptor.interface.totalMembersInSlots != 0: + raise TypeError("We can't have extra reserved slots for " + "proxy interface %s" % + descriptor.interface.identifier.name) + cgThings.append(CGGeneric(fill( + """ + static_assert(IsBaseOf<nsISupports, ${nativeType} >::value, + "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)) + cgThings.append(CGDOMJSProxyHandlerDefiner(handlerThing)) + cgThings.append(CGDOMProxyJSClass(descriptor)) + else: + cgThings.append(CGDOMJSClass(descriptor)) + cgThings.append(CGGetJSClassMethod(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, properties)) + cgThings.append(CGWrapMethod(descriptor)) + else: + cgThings.append(CGWrapNonWrapperCacheMethod(descriptor, + properties)) + + # 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: + cgThings.append(CGResolveOwnProperty(descriptor)) + cgThings.append(CGEnumerateOwnProperties(descriptor)) + 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 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")) + + # CGCreateInterfaceObjectsMethod needs to come after our + # CGDOMJSClass and unscopables, if any. + cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties, + haveUnscopables)) + + # CGGetProtoObjectMethod and CGGetConstructorObjectMethod need + # to come after CGCreateInterfaceObjectsMethod. + if descriptor.interface.hasInterfacePrototypeObject(): + cgThings.append(CGGetProtoObjectHandleMethod(descriptor)) + if descriptor.interface.hasChildInterfaces(): + cgThings.append(CGGetProtoObjectMethod(descriptor)) + if descriptor.interface.hasInterfaceObject(): + cgThings.append(CGGetConstructorObjectHandleMethod(descriptor)) + cgThings.append(CGGetConstructorObjectMethod(descriptor)) + + # See whether we need we need to generate an IsPermitted method + if crossOriginGetters or crossOriginSetters or crossOriginMethods: + cgThings.append(CGIsPermittedMethod(descriptor, + crossOriginGetters, + crossOriginSetters, + crossOriginMethods)) + + cgThings = CGList((CGIndenter(t, declareOnly=True) for t in cgThings), "\n") + cgThings = CGWrapper(cgThings, pre='\n', post='\n') + self.cgRoot = CGWrapper(CGNamespace(toBindingNamespace(descriptor.name), + cgThings), + post='\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)); + + // 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, + isEnforceRange=member.enforceRange, + isClamp=member.clamp, + isMember="Dictionary", + isOptional=member.canHaveMissingValue(), + defaultValue=member.defaultValue, + sourceDescription=self.getMemberSourceDescription(member))) + for member in dictionary.members] + + # If we have a union member containing something in the same + # file as us, bail: the C++ includes won't work out. + for member in dictionary.members: + type = member.type.unroll() + if type.isUnion(): + for t in type.flatMemberTypes: + if (t.isDictionary() and + CGHeaders.getDeclarationFilename(t.inner) == + CGHeaders.getDeclarationFilename(dictionary)): + raise TypeError( + "Dictionary contains a union that contains a " + "dictionary in the same WebIDL file. This won't " + "compile. Move the inner dictionary to a " + "different file.\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 + MOZ_ASSERT_IF(!cx, val.isNull()); + """) + + if self.needToInitIds: + body += fill( + """ + ${dictName}Atoms* atomsCache = nullptr; + if (cx) { + atomsCache = GetAtomCache<${dictName}Atoms>(cx); + if (!*reinterpret_cast<jsid**>(atomsCache) && !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( + """ + { // scope for isConvertible + bool isConvertible; + if (!IsConvertibleToDictionary(cx, val, &isConvertible)) { + return false; + } + if (!isConvertible) { + return ThrowErrorMessage(cx, MSG_NOT_DICTIONARY, sourceDescription); + } + } + + """) + + 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('JSContext*', 'cx'), + Argument('JS::Handle<JS::Value>', 'val'), + 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. + JSAutoCompartment ac(cx, binding_detail::UnprivilegedJunkScopeOrWorkerGlobal()); + 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) && !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) + + def assignmentOperator(self): + body = CGList([]) + if self.dictionary.parent: + body.append(CGGeneric( + "%s::operator=(aOther);\n" % + self.makeClassName(self.dictionary.parent))) + 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) + return ClassMethod( + "operator=", "void", + [Argument("const %s&" % self.makeClassName(self.dictionary), + "aOther")], + 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 + ctors = [ + ClassConstructor( + [], + visibility="public", + baseConstructors=baseConstructors, + body=( + "// Safe to pass a null context if we pass a null value\n" + "Init(nullptr, JS::NullHandleValue);\n")), + 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()) + + methods.append(self.initMethod()) + canBeRepresentedAsJSON = self.dictionarySafeToJSONify(self.dictionary) + if canBeRepresentedAsJSON: + methods.append(self.initFromJSONMethod()) + try: + methods.append(self.toObjectInternalMethod()) + if canBeRepresentedAsJSON: + methods.append(self.toJSONMethod()) + except MethodNotNewObjectError: + # If we can't have a ToObjectInternal() because one of our members + # can only be returned from [NewObject] methods, then just skip + # generating ToObjectInternal() and ToJSON (since the latter depens + # on the former). + pass + methods.append(self.traceDictionaryMethod()) + + if CGDictionary.isDictionaryCopyConstructible(d): + disallowCopyConstruction = False + # Note: no base constructors because our operator= will + # deal with that. + ctors.append(ClassConstructor([Argument("const %s&" % selfName, + "aOther")], + bodyInHeader=True, + visibility="public", + explicit=True, + body="*this = aOther;\n")) + methods.append(self.assignmentOperator()) + else: + disallowCopyConstruction = True + + 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): + """ + 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 + replacements = { + "val": "temp.ref()", + "maybeMutableVal": "temp.ptr()", + "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" + } + # We can't handle having a holderType here + assert conversionInfo.holderType is None + if conversionInfo.dealWithOptional: + replacements["declName"] = "(" + replacements["declName"] + ".Value())" + if member.defaultValue: + 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" + 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 ThrowErrorMessage(cx, 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", "toSource", "toString", "toLocaleString", "valueOf", + "watch", "unwatch", "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. + 'typedArraysAreStructs': 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(0);\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::UnsafeTraceRoot(trc, %s, "%s");\n' % + ("&"+memberData, memberName)) + if type.nullable(): + trace = CGIfWrapper(trace, memberData) + elif type.isAny(): + trace = CGGeneric('JS::UnsafeTraceRoot(trc, %s, "%s");\n' % + ("&"+memberData, memberName)) + elif (type.isSequence() or type.isDictionary() or + type.isSpiderMonkeyInterface() or type.isUnion()): + 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) + else: + assert type.isSpiderMonkeyInterface() + trace = CGGeneric('%s.TraceSelf(trc);\n' % memberData) + if type.nullable(): + trace = CGIfWrapper(trace, "!%s.IsNull()" % memberNullable) + elif type.isMozMap(): + # If you implement this, add a MozMap<object> to + # TestInterfaceJSDictionary and test it in test_bug1036214.html + # to make sure we end up with the correct security properties. + assert False + 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 'any' and 'object' that have a default + value, so they're safe to trace at all times. + """ + 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.isAny(): + return "JS::UndefinedValue()" + if type.isObject(): + return "nullptr" + 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 None + + 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.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() + + 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 CGResolveSystemBinding(CGAbstractMethod): + def __init__(self, config): + CGAbstractMethod.__init__(self, None, 'ResolveSystemBinding', 'bool', + [Argument('JSContext*', 'aCx'), + Argument('JS::Handle<JSObject*>', 'aObj'), + Argument('JS::Handle<jsid>', 'aId'), + Argument('bool*', 'aResolvedp')]) + self.config = config + + def definition_body(self): + descriptors = self.config.getDescriptors(hasInterfaceObject=True, + isExposedInSystemGlobals=True, + register=True) + + def descNameToId(name): + return "s%s_id" % name + jsidNames = [descNameToId(desc.name) for desc in descriptors] + jsidDecls = CGList(CGGeneric("static jsid %s;\n" % name) + for name in jsidNames) + + jsidInits = CGList( + (CGIfWrapper( + CGGeneric("return false;\n"), + '!AtomizeAndPinJSString(aCx, %s, "%s")' % + (descNameToId(desc.name), desc.interface.identifier.name)) + for desc in descriptors), + "\n") + jsidInits.append(CGGeneric("idsInited = true;\n")) + jsidInits = CGIfWrapper(jsidInits, "!idsInited") + jsidInits = CGList([CGGeneric("static bool idsInited = false;\n"), + jsidInits]) + + definitions = CGList([], "\n") + for desc in descriptors: + bindingNS = toBindingNamespace(desc.name) + defineCode = "!%s::GetConstructorObject(aCx)" % bindingNS + defineCode = CGIfWrapper(CGGeneric("return false;\n"), defineCode) + defineCode = CGList([defineCode, + CGGeneric("*aResolvedp = true;\n")]) + + condition = "JSID_IS_VOID(aId) || aId == %s" % descNameToId(desc.name) + if desc.isExposedConditionally(): + condition = "(%s) && %s::ConstructorEnabled(aCx, aObj)" % (condition, bindingNS) + + definitions.append(CGIfWrapper(defineCode, condition)) + + return CGList([CGGeneric("MOZ_ASSERT(NS_IsMainThread());\n"), + jsidDecls, + jsidInits, + definitions, + CGGeneric("return true;\n")], + "\n").define() + + +def getGlobalNames(config): + names = [] + for desc in config.getDescriptors(registersGlobalNamesOnWindow=True): + names.append((desc.name, desc)) + names.extend((n.identifier.name, desc) for n in desc.interface.namedConstructors) + return names + +class CGGlobalNamesString(CGGeneric): + def __init__(self, config): + globalNames = getGlobalNames(config) + currentOffset = 0 + strings = [] + for (name, _) in globalNames: + strings.append('/* %i */ "%s\\0"' % (currentOffset, name)) + currentOffset += len(name) + 1 # Add trailing null. + define = fill(""" + const uint32_t WebIDLGlobalNameHash::sCount = ${count}; + + const char WebIDLGlobalNameHash::sNames[] = + $*{strings} + + """, + count=len(globalNames), + strings="\n".join(strings) + ";\n") + + CGGeneric.__init__(self, define=define) + + +class CGRegisterGlobalNames(CGAbstractMethod): + def __init__(self, config): + CGAbstractMethod.__init__(self, None, 'RegisterWebIDLGlobalNames', + 'void', []) + self.config = config + + def definition_body(self): + def getCheck(desc): + if not desc.isExposedConditionally(): + return "nullptr" + return "%sBinding::ConstructorEnabled" % desc.name + + define = "" + currentOffset = 0 + for (name, desc) in getGlobalNames(self.config): + length = len(name) + define += "WebIDLGlobalNameHash::Register(%i, %i, %sBinding::DefineDOMInterface, %s);\n" % (currentOffset, length, desc.name, getCheck(desc)) + currentOffset += length + 1 # Add trailing null. + return 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(self.children.iteritems()): + decls.append(CGNamespace(namespace, child._build(atTopLevel=False), declareOnly=True)) + + 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.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.isMozMap(): + 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(): + continue + builder.add(d.nativeType) + # If we're an interface and we have a maplike/setlike declaration, + # we'll have helper functions exposed to the native side of our + # bindings, which will need to show up in the header. If either of + # our key/value types are interfaces, they'll be passed as + # arguments to helper functions, and they'll need to be forward + # declared in the header. + if d.interface.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) + + # We just about always need NativePropertyHooks + builder.addInMozillaDom("NativePropertyHooks", isStruct=True) + builder.addInMozillaDom("ProtoAndIfaceCache") + # Add the atoms cache type, even if we don't need it. + for d in descriptors: + # Iterators have native types that are template classes, so + # creating an 'Atoms' cache type doesn't work for them, and is one + # of the cases where we don't need it anyways. + if d.interface.isIteratorInterface(): + continue + builder.add(d.nativeType + "Atoms", isStruct=True) + + 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) + + 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()) + + +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', + ), + True) + bindingDeclareHeaders = dict.fromkeys(( + 'mozilla/dom/BindingDeclarations.h', + 'mozilla/dom/Nullable.h', + 'mozilla/ErrorResult.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) + bindingDeclareHeaders["mozilla/dom/IterableIterator.h"] = any(d.interface.isIteratorInterface() or + d.interface.isIterable() 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) + + bindingDeclareHeaders["jsapi.h"] = any(descriptorHasCrossOriginProperties(d) for d in descriptors) + bindingDeclareHeaders["jspubtd.h"] = not bindingDeclareHeaders["jsapi.h"] + bindingDeclareHeaders["js/RootingAPI.h"] = not bindingDeclareHeaders["jsapi.h"] + + def descriptorRequiresPreferences(desc): + iface = desc.interface + return any(m.getExtendedAttribute("Pref") for m in iface.members + [iface]) + + def descriptorDeprecated(desc): + iface = desc.interface + return any(m.getExtendedAttribute("Deprecated") for m in iface.members + [iface]) + + bindingHeaders["nsIDocument.h"] = any( + descriptorDeprecated(d) for d in descriptors) + bindingHeaders["mozilla/Preferences.h"] = any( + descriptorRequiresPreferences(d) for d in descriptors) + bindingHeaders["mozilla/dom/DOMJSProxyHandler.h"] = any( + d.concrete and d.proxy for d in descriptors) + + 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)))) or + # JS-implemented interfaces with clearable cached + # attrs have chromeonly _clearFoo methods. + (desc.interface.isJSImplemented() and + any(clearableCachedAttrs(desc)))) + + # 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 + + bindingHeaders["nsContentUtils.h"] = ( + any(descriptorHasChromeOnly(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 + bindingHeaders["nsIGlobalObject.h"] = jsImplemented + bindingHeaders["AtomList.h"] = hasNonEmptyDictionaries or jsImplemented or callbackDescriptors + + 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) + + # Do codegen for all the enums + enums = config.getEnums(webIDLFile) + cgthings = [CGEnum(e) for e in enums] + + hasCode = (descriptors or callbackDescriptors or dictionaries or + callbacks) + bindingHeaders["mozilla/dom/BindingUtils.h"] = hasCode + bindingHeaders["mozilla/OwningNonNull.h"] = 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] + def descriptorRequiresTelemetry(desc): + iface = desc.interface + return any(m.getExtendedAttribute("UseCounter") for m in iface.members) + bindingHeaders["mozilla/UseCounter.h"] = any( + descriptorRequiresTelemetry(d) for d in descriptors) + bindingHeaders["mozilla/dom/SimpleGlobalObject.h"] = any( + CGDictionary.dictionarySafeToJSONify(d) for d in dictionaries) + bindingHeaders["XrayWrapper.h"] = any( + d.wantsXrays and d.wantsXrayExpandoClass for d in descriptors) + bindingHeaders["mozilla/dom/XrayExpandoClass.h"] = any( + d.wantsXrays for d in descriptors) + + 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. + + 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()]) + return set() + + def getDependencies(unionTypeOrDictionary): + if isinstance(unionTypeOrDictionary, IDLDictionary): + deps = set() + if unionTypeOrDictionary.parent: + deps.add(unionTypeOrDictionary.parent) + for member in unionTypeOrDictionary.members: + deps |= getDependenciesFromType(member.type) + return deps + + assert unionTypeOrDictionary.isType() and unionTypeOrDictionary.isUnion() + deps = set() + for member in unionTypeOrDictionary.flatMemberTypes: + deps |= getDependenciesFromType(member) + return deps + + def getName(unionTypeOrDictionary): + if isinstance(unionTypeOrDictionary, IDLDictionary): + return unionTypeOrDictionary.identifier.name + + assert unionTypeOrDictionary.isType() and unionTypeOrDictionary.isUnion() + return unionTypeOrDictionary.name + + for t in dependencySortObjects(dictionaries + unionStructs, getDependencies, getName): + if t.isDictionary(): + cgthings.append(CGDictionary(t, config)) + else: + assert t.isUnion() + cgthings.append(CGUnionStruct(t, config)) + cgthings.append(CGUnionStruct(t, config, True)) + + # Do codegen for all the callbacks. + cgthings.extend(CGCallbackFunction(c, config) for c in callbacks) + + cgthings.extend([CGNamespace('binding_detail', CGFastCallback(c)) + for c in callbacks]) + + # 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, typedArraysAreStructs=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 bindingHeaders.iteritems() + if include] + bindingDeclareHeaders = [header + for header, include in bindingDeclareHeaders.iteritems() + 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", + typedArraysAreStructs=True, variadicIsSequence=False, + resultNotAddRefed=False, + virtual=False, + override=False): + """ + If typedArraysAreStructs is false, typed arrays will be passed as + JS::Handle<JSObject*>. If it's true they will be passed as one of the + dom::TypedArray 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.typedArraysAreStructs = typedArraysAreStructs + 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].isVoid()), + breakAfterReturnDecl=" ", + breakAfterSelf=breakAfterSelf, + visibility=visibility, + virtual=virtual, + override=override) + + 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.isVoid(): + 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.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(): + 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(): + iface = type.unroll().inner + result = CGGeneric(self.descriptorProvider.getDescriptor( + iface.identifier.name).prettyNativeType) + 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().SwapElements(${declName}.Value()); + } + """) + else: + returnCode = "aRetVal.SwapElements(${declName});\n" + return "void", "", returnCode + if type.isMozMap(): + # If we want to handle MozMap-of-MozMap return values, we're + # going to need to fix example codegen to not produce MozMap<void> + # for the relevant argument... + assert not isMember + # In this case we convert directly into our outparam to start with + return "void", "", "" + if type.isDate(): + result = CGGeneric("Date") + if type.nullable(): + result = CGTemplatedType("Nullable", result) + return (result.define(), "%s()" % result.define(), + "return ${declName};\n") + 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.isDOMString() or returnType.isUSVString(): + args.append(Argument("nsString&", "aRetVal")) + elif returnType.isByteString(): + 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.isMozMap(): + nullable = returnType.nullable() + if nullable: + returnType = returnType.inner + # And now the actual underlying type + elementDecl = self.getReturnType(returnType.inner, True) + type = CGTemplatedType("MozMap", 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 self.member.getExtendedAttribute('NeedsSubjectPrincipal'): + # Cheat and assume self.descriptorProvider is a descriptor + if self.descriptorProvider.interface.isExposedInAnyWorker(): + args.append(Argument("Maybe<nsIPrincipal*>", "aSubjectPrincipal")) + else: + args.append(Argument("nsIPrincipal&", "aPrincipal")) + # And the caller type, if desired. + if needsCallerType(self.member): + args.append(Argument("CallerType", "aCallerType")) + # And the ErrorResult + if 'infallible' not in self.extendedAttrs: + # Use aRv so it won't conflict with local vars named "rv" + args.append(Argument("ErrorResult&", "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", + "MozMap" + """ + 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.isMozMap(): + nullable = type.nullable() + if nullable: + type = type.inner + elementType = type.inner + argType = self.getArgType(elementType, False, "MozMap")[0] + decl = CGTemplatedType("MozMap", 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.isGeckoInterface() and not type.isCallbackInterface(): + iface = type.unroll().inner + argIsPointer = type.nullable() or iface.isExternal() + forceOwningType = (iface.isCallback() or isMember or + iface.identifier.name == "Promise") + 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.typedArraysAreStructs: + return "JS::Handle<JSObject*>", False, False + + # Unroll for the name, in case we're nullable. + return type.unroll().name, True, True + + if type.isDOMString() or type.isUSVString(): + if isMember: + declType = "nsString" + else: + declType = "nsAString" + return declType, True, False + + if type.isByteString(): + 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 + + if type.isDate(): + return "Date", False, 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.void], + [FakeArgument(attr.type, attr)]), + 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.namedConstructors: + appendMethod(n, isConstructor=True) + for m in iface.members: + if m.isMethod(): + if m.isIdentifierLess(): + continue + if not m.isStatic() or not skipStaticMethods: + appendMethod(m) + elif m.isAttr(): + 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 + if name == "IndexedCreator" or name == "NamedCreator": + # These are identical to the setters + 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], + op, 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 + if name == "Jsonifier": + # 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 = descriptor.operations.items() + ops.sort(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], + []), + {"infallible": True})) + # 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]), + []), + {"infallible": True})) + + 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 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): + return ("// TODO: return something sensible here, and change the return type\n" + "%s*" % self.descriptor.nativeType.split('::')[-1]) + + def getGetParentObjectBody(self): + return None + + def deps(self): + return self._deps + + +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_SCRIPT_HOLDER_CLASS(%s)\n" + "\n" % self.nativeLeafName(descriptor)) + + if descriptor.interface.hasChildInterfaces(): + decorators = "" + else: + decorators = "final" + + 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 + ctordtor = dedent(""" + ${nativeType}::${nativeType}() + { + // Add |MOZ_COUNT_CTOR(${nativeType});| for a non-refcounted object. + } + + ${nativeType}::~${nativeType}() + { + // Add |MOZ_COUNT_DTOR(${nativeType});| for a non-refcounted object. + } + """) + + if self.parentIface: + ccImpl = dedent(""" + + // 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}) + + """) + else: + ccImpl = dedent(""" + + // 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 + + """) + + if self.descriptor.wrapperCache: + reflectorArg = "" + reflectorPassArg = "" + returnType = "JSObject*" + else: + reflectorArg = ", JS::MutableHandle<JSObject*> aReflector" + reflectorPassArg = ", aReflector" + returnType = "bool" + classImpl = ccImpl + ctordtor + "\n" + dedent(""" + ${returnType} + ${nativeType}::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto${reflectorArg}) + { + return ${ifaceName}Binding::Wrap(aCx, this, aGivenProto${reflectorPassArg}); + } + + """) + return string.Template(classImpl).substitute( + ifaceName=self.descriptor.name, + nativeType=self.nativeLeafName(self.descriptor), + parentType=self.nativeLeafName(self.parentDesc) if self.parentIface else "", + returnType=returnType, + reflectorArg=reflectorArg, + reflectorPassArg=reflectorPassArg) + + @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() + for member in descriptor.interface.members: + if not member.isAttr() and not member.isMethod(): + continue + if member.isStatic(): + builder.addInMozillaDom("GlobalObject") + if member.isAttr() and 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("JSCompartment*", "aCompartment", "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): + virtual = False + override = False + if (method.identifier.name == "eventListenerWasAdded" or + method.identifier.name == "eventListenerWasRemoved"): + virtual = True + override = 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, + virtual=virtual, + override=override) + + def getArgs(self, returnType, argList): + if self.isConstructor: + # Skip the JSCompartment bits for constructors; it's handled + # manually in getImpl. + return CGNativeMember.getArgs(self, returnType, argList) + 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() + assert args[0].argType == 'const GlobalObject&' + assert args[1].argType == 'JSContext*' + constructorArgs = [arg.name for arg in args[2:]] + constructorArgs.append("js::GetObjectCompartment(scopeObj)") + initCall = fill( + """ + // Wrap the object before calling __Init so that __DOM_IMPL__ is available. + JS::Rooted<JSObject*> scopeObj(cx, globalHolder->GetGlobalJSObject()); + MOZ_ASSERT(js::IsObjectInContextCompartment(scopeObj, cx)); + JS::Rooted<JS::Value> wrappedVal(cx); + if (!GetOrCreateDOMReflector(cx, impl, &wrappedVal)) { + //XXX Assertion disabled for now, see bug 991271. + MOZ_ASSERT(true || 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 genConstructorBody(self.descriptor, initCall) + + +def genConstructorBody(descriptor, initCall=""): + return fill( + """ + JS::Rooted<JSObject*> jsImplObj(cx); + nsCOMPtr<nsIGlobalObject> globalHolder = + ConstructJSImplementation("${contractId}", global, &jsImplObj, aRv); + if (aRv.Failed()) { + return nullptr; + } + // Build the C++ implementation. + RefPtr<${implClass}> impl = new ${implClass}(jsImplObj, globalHolder); + $*{initCall} + return impl.forget(); + """, + contractId=descriptor.interface.getJSImplementation(), + implClass=descriptor.name, + initCall=initCall) + + +# We're always fallible +def callbackGetterName(attr, descriptor): + return "Get" + MakeNativeName( + descriptor.binaryNameFor(attr.identifier.name)) + + +def callbackSetterName(attr, descriptor): + return "Set" + MakeNativeName( + descriptor.binaryNameFor(attr.identifier.name)) + + +class CGJSImplClearCachedValueMethod(CGAbstractBindingMethod): + def __init__(self, descriptor, attr): + if attr.getExtendedAttribute("StoreInSlot"): + raise TypeError("[StoreInSlot] is not supported for JS-implemented WebIDL. See bug 1056325.") + + CGAbstractBindingMethod.__init__(self, descriptor, + MakeJSImplClearCachedValueNativeName(attr), + JSNativeArguments()) + self.attr = attr + + def generate_code(self): + return CGGeneric(fill( + """ + ${bindingNamespace}::${fnName}(self); + args.rval().setUndefined(); + return true; + """, + bindingNamespace=toBindingNamespace(self.descriptor.name), + fnName=MakeClearCachedValueNativeName(self.attr))) + + +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.void], + [FakeArgument(attr.type, attr)]), + descriptor.getExtendedAttributes(attr, + setter=True), + passJSBitsAsNeeded=False) + + def getImpl(self): + callbackArgs = [arg.name for arg in self.getArgs(BuiltinTypes[IDLBuiltinType.Types.void], + [FakeArgument(self.member.type, self.member)])] + 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)) + constructorBody = dedent(""" + // Make sure we're an nsWrapperCache already + MOZ_ASSERT(static_cast<nsWrapperCache*>(this)); + // And that our ancestor has not called SetIsNotDOMBinding() + MOZ_ASSERT(IsDOMBinding()); + """) + 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_INHERITED(${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_SCRIPT_HOLDER_CLASS(%s)\n" % + descriptor.name) + extradefinitions = fill( + """ + NS_IMPL_CYCLE_COLLECTION_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_SCRIPT_OBJECTS + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(${ifaceName}) + 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<nsISupports> mParent; + + """, + isupportsDecl=isupportsDecl, + ccDecl=ccDecl, + jsImplName=jsImplName(descriptor.name)) + + 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, /* aIncumbentGlobal = */ nullptr))" % + jsImplName(descriptor.name)), + "mParent(aParent)"] + parentInterface = descriptor.interface.parent + while parentInterface: + if parentInterface.isJSImplemented(): + baseConstructors.insert( + 0, "%s(aJSImplObject, 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("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 + JSAutoCompartment ac(aCx, mImpl->Callback()); + if (!JS_WrapObject(aCx, &obj)) { + return nullptr; + } + if (!JS_DefineProperty(aCx, mImpl->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 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.length() < 2) { + return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, "${ifaceName}._create"); + } + if (!args[0].isObject()) { + return ThrowErrorMessage(cx, MSG_NOT_OBJECT, "Argument 1 of ${ifaceName}._create"); + } + if (!args[1].isObject()) { + return ThrowErrorMessage(cx, MSG_NOT_OBJECT, "Argument 2 of ${ifaceName}._create"); + } + + // 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()); + RefPtr<${implName}> impl = new ${implName}(arg, globalHolder); + MOZ_ASSERT(js::IsObjectInContextCompartment(arg, cx)); + return GetOrCreateDOMReflector(cx, impl, args.rval()); + """, + ifaceName=self.descriptor.interface.identifier.name, + implName=self.descriptor.name) + + +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("nsIGlobalObject*", "aIncumbentGlobal")], + bodyInHeader=True, + visibility="public", + explicit=True, + baseConstructors=[ + "%s(aCx, aCallback, aIncumbentGlobal)" % self.baseName, + ], + body=body), + ClassConstructor( + [Argument("JSContext*", "aCx"), + Argument("JS::Handle<JSObject*>", "aCallback"), + Argument("nsIGlobalObject*", "aIncumbentGlobal"), + Argument("const FastCallbackConstructor&", "")], + bodyInHeader=True, + visibility="public", + explicit=True, + baseConstructors=[ + "%s(aCx, aCallback, aIncumbentGlobal, FastCallbackConstructor())" % self.baseName, + ], + body=body), + ClassConstructor( + [Argument("JS::Handle<JSObject*>", "aCallback"), + Argument("JS::Handle<JSObject*>", "aAsyncStack"), + Argument("nsIGlobalObject*", "aIncumbentGlobal")], + bodyInHeader=True, + visibility="public", + explicit=True, + baseConstructors=[ + "%s(aCallback, aAsyncStack, aIncumbentGlobal)" % self.baseName, + ], + body=body)] + + def getMethodImpls(self, method): + assert method.needThisHandling + args = list(method.args) + # Strip out the JSContext*/JSObject* args + # that got added. + assert args[0].name == "cx" and args[0].argType == "JSContext*" + 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.GetContext()", "thisValJS"] + argnames + argnamesWithoutThis = ["s.GetContext()", "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 aCompartment 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 aCompartment is provided + # and its principal doesn't subsume either the callback or the + # exception. + args.append(Argument("JSCompartment*", "aCompartment", "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, "rv"); + # 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 aCompartment 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, "rv") + + errorReturn = method.getDefaultRetval() + + setupCall = fill( + """ + if (!aExecutionReason) { + aExecutionReason = "${executionReason}"; + } + CallSetup s(this, aRv, aExecutionReason, aExceptionHandling, aCompartment); + 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( + """ + IgnoredErrorResult rv; + return ${methodName}(${callArgs}); + """, + methodName=method.name, + callArgs=", ".join(argnamesWithoutRv)) + bodyWithoutThisAndRv = fill( + """ + IgnoredErrorResult rv; + return ${methodName}(${callArgs}); + """, + methodName=method.name, + callArgs=", ".join(argnamesWithoutThisAndRv)) + + return [ClassMethod(method.name, method.returnType, args, + bodyInHeader=True, + templateArgs=["typename T"], + body=bodyWithThis), + ClassMethod(method.name, method.returnType, argsWithoutThis, + bodyInHeader=True, + body=bodyWithoutThis), + ClassMethod(method.name, method.returnType, argsWithoutRv, + bodyInHeader=True, + templateArgs=["typename T"], + body=bodyWithThisWithoutRv), + ClassMethod(method.name, method.returnType, argsWithoutThisAndRv, + bodyInHeader=True, + body=bodyWithoutThisAndRv), + method] + + def deps(self): + return self._deps + + +class CGCallbackFunction(CGCallback): + def __init__(self, callback, descriptorProvider): + self.callback = callback + CGCallback.__init__(self, callback, descriptorProvider, + "CallbackFunction", + methods=[CallCallback(callback, descriptorProvider)]) + + 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("JSContext*", "aCx"), + Argument("JS::Handle<JSObject*>", "aCallback"), + Argument("nsIGlobalObject*", "aIncumbentGlobal")], + bodyInHeader=True, + visibility="public", + explicit=True, + baseConstructors=[ + "%s(aCx, aCallback, aIncumbentGlobal, FastCallbackConstructor())" % + baseName, + ], + body="") + + traceMethod = ClassMethod("Trace", "void", + [Argument("JSTracer*", "aTracer")], + inline=True, + bodyInHeader=True, + visibility="public", + body="%s::Trace(aTracer);\n" % baseName) + holdMethod = ClassMethod("HoldJSObjectsIfMoreThanOneOwner", "void", + [], + inline=True, + bodyInHeader=True, + visibility="public", + body=( + "%s::HoldJSObjectsIfMoreThanOneOwner();\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, typedArraysAreStructs=False): + iface = descriptor.interface + attrs = [m for m in iface.members if m.isAttr() and not m.isStatic()] + getters = [CallbackGetter(a, descriptor, typedArraysAreStructs) + for a in attrs] + setters = [CallbackSetter(a, descriptor, typedArraysAreStructs) + 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()] + methods = [CallbackOperation(m, sig, descriptor, typedArraysAreStructs) + for m in methods for sig in m.signatures()] + 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)) + if any(m.isAttr() or m.isMethod() for m in iface.members) or (iface.isJSImplemented() and iface.ctor()): + methods.append(initIdsClassMethod([descriptor.binaryNameFor(m.identifier.name) + for m in iface.members + if m.isAttr() or m.isMethod()] + + (["__init"] if iface.isJSImplemented() and iface.ctor() else []), + iface.identifier.name + "Atoms")) + CGCallback.__init__(self, iface, descriptor, "CallbackInterface", + methods, getters=getters, setters=setters) + + +class FakeMember(): + def __init__(self, name=None): + self.treatNullAs = "Default" + 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, typedArraysAreStructs=False, + wrapScope='CallbackKnownNotGray()'): + """ + 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={}, + passJSBitsAsNeeded=False, + visibility=visibility, + typedArraysAreStructs=typedArraysAreStructs) + # 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.exceptionCode = ("aRv.Throw(NS_ERROR_UNEXPECTED);\n" + "return%s;\n" % self.getDefaultRetval()) + self.body = self.getImpl() + + def getImpl(self): + setupCall = self.getCallSetup() + declRval = self.getRvalDecl() + if self.argCount > 0: + argvDecl = fill( + """ + JS::AutoValueVector argv(cx); + if (!argv.resize(${argCount})) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return${errorReturn}; + } + """, + argCount=self.argCountStr, + errorReturn=self.getDefaultRetval()) + else: + # Avoid weird 0-sized arrays + argvDecl = "" + convertArgs = self.getArgConversions() + doCall = self.getCall() + returnResult = self.getResultConversion() + + return setupCall + declRval + argvDecl + convertArgs + doCall + returnResult + + def getResultConversion(self): + replacements = { + "val": "rval", + "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, + exceptionCode=self.exceptionCode, + 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 (0);\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 = "" + + try: + 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': self.wrapScope, + 'returnsNewObject': False, + 'exceptionCode': self.exceptionCode, + 'typedArraysAreStructs': self.typedArraysAreStructs + }) + except MethodNotNewObjectError as err: + raise TypeError("%s being passed as an argument to %s but is not " + "wrapper cached, so can't be reliably converted to " + "a JS object." % + (err.typename, self.getPrettyName())) + 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("JSCompartment*", "aCompartment", "nullptr")) + return args + # We want to allow the caller to pass in a "this" value, as + # well as a JSContext. + return [Argument("JSContext*", "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 + # aCompartment for us. + callSetup += ', "%s", eRethrowContentExceptions, aCompartment, /* aIsJSImplementedWebIDL = */ ' % self.getPrettyName() + callSetup += toStringBool(isJSImplementedDescriptor(self.descriptorProvider)) + else: + callSetup += ', "%s", aExceptionHandling, aCompartment' % self.getPrettyName() + callSetup += ");\n" + return fill( + """ + $*{callSetup} + JSContext* cx = s.GetContext(); + if (!cx) { + MOZ_ASSERT(aRv.Failed()); + return${errorReturn}; + } + """, + 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 CallbackMethod(CallbackMember): + def __init__(self, sig, name, descriptorProvider, needThisHandling, + rethrowContentException=False, typedArraysAreStructs=False): + CallbackMember.__init__(self, sig, name, descriptorProvider, + needThisHandling, rethrowContentException, + typedArraysAreStructs=typedArraysAreStructs) + + def getRvalDecl(self): + return "JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());\n" + + 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)) { + aRv.NoteJSContextException(cx); + return${errorReturn}; + } + """, + declCallable=self.getCallableDecl(), + declThis=self.getThisDecl(), + callGuard=self.getCallGuard(), + thisVal=self.getThisVal(), + args=args, + errorReturn=self.getDefaultRetval()) + + +class CallCallback(CallbackMethod): + def __init__(self, callback, descriptorProvider): + self.callback = callback + CallbackMethod.__init__(self, callback.signatures()[0], "Call", + descriptorProvider, needThisHandling=True) + + 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, + typedArraysAreStructs=False): + self.singleOperation = singleOperation + self.methodName = descriptor.binaryNameFor(jsName) + CallbackMethod.__init__(self, signature, nativeName, descriptor, + singleOperation, rethrowContentException, + typedArraysAreStructs=typedArraysAreStructs) + + 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) && !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, typedArraysAreStructs): + self.ensureASCIIName(method) + self.method = method + jsName = method.identifier.name + CallbackOperationBase.__init__(self, signature, + jsName, + MakeNativeName(descriptor.binaryNameFor(jsName)), + descriptor, descriptor.interface.isSingleOperationInterface(), + rethrowContentException=descriptor.interface.isJSImplemented(), + typedArraysAreStructs=typedArraysAreStructs) + + 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, typedArraysAreStructs): + self.ensureASCIIName(attr) + self.attrName = attr.identifier.name + CallbackMember.__init__(self, sig, name, descriptor, + needThisHandling=False, + rethrowContentException=descriptor.interface.isJSImplemented(), + typedArraysAreStructs=typedArraysAreStructs) + + def getPrettyName(self): + return "%s.%s" % (self.descriptorProvider.interface.identifier.name, + self.attrName) + + +class CallbackGetter(CallbackAccessor): + def __init__(self, attr, descriptor, typedArraysAreStructs): + CallbackAccessor.__init__(self, attr, + (attr.type, []), + callbackGetterName(attr, descriptor), + descriptor, + typedArraysAreStructs) + + def getRvalDecl(self): + return "JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());\n" + + def getCall(self): + return fill( + """ + JS::Rooted<JSObject *> callback(cx, mCallback); + ${atomCacheName}* atomsCache = GetAtomCache<${atomCacheName}>(cx); + if ((!*reinterpret_cast<jsid**>(atomsCache) && !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)), + errorReturn=self.getDefaultRetval()) + + +class CallbackSetter(CallbackAccessor): + def __init__(self, attr, descriptor, typedArraysAreStructs): + CallbackAccessor.__init__(self, attr, + (BuiltinTypes[IDLBuiltinType.Types.void], + [FakeArgument(attr.type, attr)]), + callbackSetterName(attr, descriptor), + descriptor, typedArraysAreStructs) + + def getRvalDecl(self): + # We don't need an rval + return "" + + def getCall(self): + return fill( + """ + MOZ_ASSERT(argv.length() == 1); + ${atomCacheName}* atomsCache = GetAtomCache<${atomCacheName}>(cx); + if ((!*reinterpret_cast<jsid**>(atomsCache) && !InitIds(cx, atomsCache)) || + !JS_SetPropertyById(cx, CallbackKnownNotGray(), 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)), + 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.void], sig[1]), + "__init", "__Init", descriptor, + singleOperation=False, + rethrowContentException=True, + typedArraysAreStructs=True) + + def getPrettyName(self): + return "__init" + + +def getMaplikeOrSetlikeErrorReturn(helperImpl): + """ + Generate return values based on whether a maplike or setlike generated + method is an interface method (which returns bool) or a helper function + (which uses ErrorResult). + """ + if helperImpl: + return dedent( + """ + aRv.Throw(NS_ERROR_UNEXPECTED); + return%s; + """ % helperImpl.getDefaultRetval()) + return "return false;\n" + + +def getMaplikeOrSetlikeBackingObject(descriptor, maplikeOrSetlike, helperImpl=None): + """ + Generate code to get/create a JS backing object for a maplike/setlike + declaration from the declaration slot. + """ + func_prefix = maplikeOrSetlike.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, + helperImpl=None): + CGThing.__init__(self) + # True if this will be the body of a C++ helper function. + self.helperImpl = helperImpl + self.descriptor = descriptor + self.maplikeOrSetlike = maplikeOrSetlike + self.cgRoot = CGList([]) + impl_method_name = methodName + if impl_method_name[0] == "_": + # double underscore means this is a js-implemented chrome only rw + # function. Truncate the double underscore so calling the right + # underlying JSAPI function still works. + impl_method_name = impl_method_name[2:] + self.cgRoot.append(CGGeneric( + getMaplikeOrSetlikeBackingObject(self.descriptor, + self.maplikeOrSetlike, + self.helperImpl))) + self.returnStmt = getMaplikeOrSetlikeErrorReturn(self.helperImpl) + + # Generates required code for the method. Method descriptions included + # in definitions below. Throw if we don't have a method to fill in what + # we're looking for. + try: + methodGenerator = getattr(self, impl_method_name) + except AttributeError: + raise TypeError("Missing %s method definition '%s'" % + (self.maplikeOrSetlike.maplikeOrSetlikeType, + methodName)) + # Method generator returns tuple, containing: + # + # - a list of CGThings representing setup code for preparing to call + # the JS API function + # - a list of arguments needed for the JS API function we're calling + # - list of code CGThings needed for return value conversion. + (setupCode, arguments, setResult) = methodGenerator() + + # Create the actual method call, and then wrap it with the code to + # return the value if needed. + funcName = (self.maplikeOrSetlike.prefix + + MakeNativeName(impl_method_name)) + # Append the list of setup code CGThings + self.cgRoot.append(CGList(setupCode)) + # Create the JS API call + self.cgRoot.append(CGWrapper( + CGGeneric(fill( + """ + if (!JS::${funcName}(${args})) { + $*{errorReturn} + } + """, + funcName=funcName, + args=", ".join(["cx", "backingObj"] + arguments), + errorReturn=self.returnStmt)))) + # Append result conversion + self.cgRoot.append(CGList(setResult)) + + def mergeTuples(self, a, b): + """ + Expecting to take 2 tuples were all elements are lists, append the lists in + the second tuple to the lists in the first. + """ + return tuple([x + y for x, y in zip(a, b)]) + + def appendArgConversion(self, name): + """ + Generate code to convert arguments to JS::Values, so they can be + passed into JSAPI functions. + """ + return CGGeneric(fill( + """ + JS::Rooted<JS::Value> ${name}Val(cx); + if (!ToJSValue(cx, ${name}, &${name}Val)) { + $*{errorReturn} + } + """, + name=name, + errorReturn=self.returnStmt)) + + def appendKeyArgConversion(self): + """ + Generates the key argument for methods. Helper functions will use + an AutoValueVector, while interface methods have seperate JS::Values. + """ + if self.helperImpl: + return ([], ["argv[0]"], []) + return ([self.appendArgConversion("arg0")], ["arg0Val"], []) + + def appendKeyAndValueArgConversion(self): + """ + Generates arguments for methods that require a key and value. Helper + functions will use an AutoValueVector, while interface methods have + seperate JS::Values. + """ + r = self.appendKeyArgConversion() + if self.helperImpl: + return self.mergeTuples(r, ([], ["argv[1]"], [])) + return self.mergeTuples(r, ([self.appendArgConversion("arg1")], + ["arg1Val"], + [])) + + def appendIteratorResult(self): + """ + Generate code to output JSObject* return values, needed for functions that + return iterators. Iterators cannot currently be wrapped via Xrays. If + something that would return an iterator is called via Xray, fail early. + """ + # TODO: Bug 1173651 - Remove check once bug 1023984 is fixed. + code = CGGeneric(dedent( + """ + // TODO (Bug 1173651): Xrays currently cannot wrap iterators. Change + // after bug 1023984 is fixed. + if (xpc::WrapperFactory::IsXrayWrapper(obj)) { + JS_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()], ["&aRetVal"], []) + return ([CGGeneric("bool result;\n")], ["&result"], []) + + def forEach(self): + """ + void forEach(callback c, any thisval); + + ForEach takes a callback, and a possible value to use as 'this'. The + callback needs to take value, key, and the interface object + implementing maplike/setlike. In order to make sure that the third arg + is our interface object instead of the map/set backing object, we + create a js function with the callback and original object in its + storage slots, then use a helper function in BindingUtils to make sure + the callback is called correctly. + """ + assert(not self.helperImpl) + code = [CGGeneric(dedent( + """ + // Create a wrapper function. + JSFunction* func = js::NewFunctionWithReserved(cx, ForEachHandler, 3, 0, nullptr); + if (!func) { + return false; + } + JS::Rooted<JSObject*> funcObj(cx, JS_GetFunctionObject(func)); + JS::Rooted<JS::Value> funcVal(cx, JS::ObjectValue(*funcObj)); + js::SetFunctionNativeReserved(funcObj, FOREACH_CALLBACK_SLOT, + JS::ObjectValue(*arg0)); + js::SetFunctionNativeReserved(funcObj, FOREACH_MAPLIKEORSETLIKEOBJ_SLOT, + JS::ObjectValue(*obj)); + """))] + arguments = ["funcVal", "arg1"] + return (code, arguments, []) + + def set(self): + """ + object set(key, value); + + Maplike only function, takes key and sets value to it, returns + interface object unless being called from a C++ helper. + """ + assert self.maplikeOrSetlike.isMaplike() + r = self.appendKeyAndValueArgConversion() + if self.helperImpl: + return r + return self.mergeTuples(r, self.appendSelfResult()) + + def add(self): + """ + object add(value); + + Setlike only function, adds value to set, returns interface object + unless being called from a C++ helper + """ + assert self.maplikeOrSetlike.isSetlike() + r = self.appendKeyArgConversion() + if self.helperImpl: + return r + return self.mergeTuples(r, self.appendSelfResult()) + + def get(self): + """ + type? get(key); + + Retrieves a value from a backing object based on the key. Returns value + if key is in backing object, undefined otherwise. + """ + assert self.maplikeOrSetlike.isMaplike() + r = self.appendKeyArgConversion() + code = [CGGeneric(dedent( + """ + JS::Rooted<JS::Value> result(cx); + """))] + arguments = ["&result"] + return self.mergeTuples(r, (code, arguments, [])) + + def has(self): + """ + bool has(key); + + Check if an entry exists in the backing object. Returns true if value + exists in backing object, false otherwise. + """ + return self.mergeTuples(self.appendKeyArgConversion(), + self.appendBoolResult()) + + def keys(self): + """ + object keys(); + + Returns new object iterator with all keys from backing object. + """ + return self.appendIteratorResult() + + def values(self): + """ + object values(); + + Returns new object iterator with all values from backing object. + """ + return self.appendIteratorResult() + + def entries(self): + """ + object entries(); + + Returns new object iterator with all keys and values from backing + object. Keys will be null for set. + """ + return self.appendIteratorResult() + + def clear(self): + """ + void clear(); + + Removes all entries from map/set. + """ + return ([], [], []) + + def delete(self): + """ + bool delete(key); + + Deletes an entry from the backing object. Returns true if value existed + in backing object, false otherwise. + """ + return self.mergeTuples(self.appendKeyArgConversion(), + self.appendBoolResult()) + + def define(self): + return self.cgRoot.define() + + +class CGMaplikeOrSetlikeHelperFunctionGenerator(CallbackMember): + """ + Generates code to allow C++ to perform operations on backing objects. Gets + a context from the binding wrapper, turns arguments into JS::Values (via + CallbackMember/CGNativeMember argument conversion), then uses + CGMaplikeOrSetlikeMethodGenerator to generate the body. + + """ + + class HelperFunction(CGAbstractMethod): + """ + Generates context retrieval code and rooted JSObject for interface for + CGMaplikeOrSetlikeMethodGenerator to use + """ + def __init__(self, descriptor, name, args, code, needsBoolReturn=False): + self.code = code + CGAbstractMethod.__init__(self, descriptor, name, + "bool" if needsBoolReturn else "void", + args) + + def definition_body(self): + return self.code + + def __init__(self, descriptor, maplikeOrSetlike, name, needsKeyArg=False, + needsValueArg=False, needsBoolReturn=False): + args = [] + self.maplikeOrSetlike = maplikeOrSetlike + self.needsBoolReturn = needsBoolReturn + if needsKeyArg: + args.append(FakeArgument(maplikeOrSetlike.keyType, None, 'aKey')) + if needsValueArg: + assert needsKeyArg + args.append(FakeArgument(maplikeOrSetlike.valueType, None, 'aValue')) + # Run CallbackMember init function to generate argument conversion code. + # wrapScope is set to 'obj' when generating maplike or setlike helper + # functions, as we don't have access to the CallbackPreserveColor + # method. + CallbackMember.__init__(self, + [BuiltinTypes[IDLBuiltinType.Types.void], args], + name, descriptor, False, + wrapScope='obj') + # Wrap CallbackMember body code into a CGAbstractMethod to make + # generation easier. + self.implMethod = CGMaplikeOrSetlikeHelperFunctionGenerator.HelperFunction( + descriptor, name, self.args, self.body, needsBoolReturn) + + def getCallSetup(self): + return dedent( + """ + MOZ_ASSERT(self); + AutoJSAPI jsapi; + jsapi.Init(); + 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. + JSAutoCompartment tempCompartment(cx, binding_detail::UnprivilegedJunkScopeOrWorkerGlobal()); + 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); + JSAutoCompartment reflectorCompartment(cx, obj); + """ % self.getDefaultRetval()) + + def getArgs(self, returnType, argList): + # We don't need the context or the value. We'll generate those instead. + args = CGNativeMember.getArgs(self, returnType, argList) + # Prepend a pointer to the binding object onto the arguments + return [Argument(self.descriptorProvider.nativeType + "*", "self")] + args + + def getResultConversion(self): + if self.needsBoolReturn: + return "return aRetVal;\n" + return "return;\n" + + def getRvalDecl(self): + if self.needsBoolReturn: + return "bool aRetVal;\n" + return "" + + def getArgcDecl(self): + # Don't need argc for anything. + return None + + def getDefaultRetval(self): + if self.needsBoolReturn: + return " false" + return "" + + def getCall(self): + return CGMaplikeOrSetlikeMethodGenerator(self.descriptorProvider, + self.maplikeOrSetlike, + self.name.lower(), + helperImpl=self).define() + + def getPrettyName(self): + return self.name + + def declare(self): + return self.implMethod.declare() + + def define(self): + return self.implMethod.define() + + +class CGMaplikeOrSetlikeHelperGenerator(CGNamespace): + """ + Declares and defines convenience methods for accessing backing objects on + setlike/maplike interface. Generates function signatures, un/packs + backing objects from slot, etc. + """ + def __init__(self, descriptor, maplikeOrSetlike): + self.descriptor = descriptor + # 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), + CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor, + maplikeOrSetlike, + "Has", + needsKeyArg=True, + needsBoolReturn=True)] + if self.maplikeOrSetlike.isMaplike(): + self.helpers.append( + CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor, + maplikeOrSetlike, + "Set", + needsKeyArg=True, + needsValueArg=True)) + else: + assert(self.maplikeOrSetlike.isSetlike()) + self.helpers.append( + CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor, + maplikeOrSetlike, + "Add", + needsKeyArg=True)) + CGNamespace.__init__(self, self.namespace, CGList(self.helpers)) + + +class 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, iterable, methodName): + if methodName == "forEach": + CGGeneric.__init__(self, fill( + """ + if (!JS::IsCallable(arg0)) { + ThrowErrorMessage(cx, MSG_NOT_CALLABLE, "Argument 1 of ${ifaceName}.forEach"); + return false; + } + JS::AutoValueArray<3> callArgs(cx); + callArgs[2].setObject(*obj); + JS::Rooted<JS::Value> ignoredReturnVal(cx); + for (size_t i = 0; i < self->GetIterableLength(); ++i) { + if (!ToJSValue(cx, self->GetValueAtIndex(i), callArgs[0])) { + return false; + } + if (!ToJSValue(cx, self->GetKeyAtIndex(i), callArgs[1])) { + return false; + } + if (!JS::Call(cx, arg1, arg0, JS::HandleValueArray(callArgs), + &ignoredReturnVal)) { + return false; + } + } + """, + ifaceName=descriptor.interface.identifier.name)) + return + CGGeneric.__init__(self, fill( + """ + typedef ${iterClass} itrType; + RefPtr<itrType> result(new itrType(self, + itrType::IterableIteratorType::${itrMethod}, + &${ifaceName}IteratorBinding::Wrap)); + """, + iterClass=iteratorNativeType(descriptor), + ifaceName=descriptor.interface.identifier.name, + itrMethod=methodName.title())) + + +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.identifier.name) + 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 x: x, 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 len(members) == 0: + continue + + structs.append(buildAtomCacheStructure(d.interface, + lambda x: d.binaryNameFor(x), + 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/BindingUtils.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(((JSJitInfo*)nullptr)->%s) * 8))" % jitInfoField + return CGGeneric(declare="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\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.identifier.name + 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 RegisterBindings(config): + + curr = CGList([CGGlobalNamesString(config), CGRegisterGlobalNames(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, + isExposedInWindow=True, + register=True)] + defineIncludes.append('mozilla/dom/WebIDLGlobalNameHash.h') + defineIncludes.extend([CGHeaders.getDeclarationFilename(desc.interface) + for desc in config.getDescriptors(isNavigatorProperty=True, + register=True)]) + 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 ResolveSystemBinding(config): + + curr = CGResolveSystemBinding(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, + isExposedInSystemGlobals=True)] + defineIncludes.append("nsThreadUtils.h") # For NS_IsMainThread + defineIncludes.append("js/Id.h") # For jsid + defineIncludes.append("mozilla/dom/BindingUtils.h") # AtomizeAndPinJSString + + curr = CGHeaders([], [], [], [], [], defineIncludes, + 'ResolveSystemBinding', curr) + + # Add include guards. + curr = CGIncludeGuard('ResolveSystemBinding', curr) + + # Done. + return curr + + @staticmethod + def UnionTypes(config): + unionTypes = UnionsForFile(config, None) + (includes, implincludes, declarations, + traverseMethods, unlinkMethods, + unionStructs) = UnionTypes(unionTypes, config) + + 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") + implincludes.add("mozilla/dom/PrimitiveConversions.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 UnionConversions(config): + unionTypes = [] + for l in config.unionsPerFilename.itervalues(): + unionTypes.extend(l) + unionTypes.sort(key=lambda u: u.name) + headers, unions = UnionConversions(unionTypes, + config) + + # Wrap all of that in our namespaces. + curr = CGNamespace.build(['mozilla', 'dom'], unions) + + curr = CGWrapper(curr, post='\n') + + headers.update(["nsDebug.h", "mozilla/dom/UnionTypes.h"]) + curr = CGHeaders([], [], [], [], headers, [], 'UnionConversions', curr) + + # Add include guards. + curr = CGIncludeGuard('UnionConversions', 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 'infallible' not in self.extendedAttrs: + raise TypeError("Event code generator does not support [Throws]!") + 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.isGeckoInterface(): + return "return " + memberName + ";\n" + if type.isDOMString() or type.isByteString() or type.isUSVString(): + 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(): + return "aRetVal = " + memberName + ";\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(filter(lambda x: x.isAttr(), iface.members)) - 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")) + self.args.append(Argument('ErrorResult&', 'aRv')) + 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")) + self.args.append(Argument('ErrorResult&', 'aRv')) + 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 = [] + for m in descriptor.interface.members: + if m.isAttr(): + if m.type.isAny(): + # 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)))) + if getattr(m, "originatingInterface", + descriptor.interface) != descriptor.interface: + continue + 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] + baseDeclarations = fill( + """ + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(${nativeType}, ${parentType}) + protected: + virtual ~${nativeType}(); + explicit ${nativeType}(mozilla::dom::EventTarget* aOwner); + + """, + nativeType=self.descriptor.nativeType.split('::')[-1], + parentType=self.parentType) + + className = descriptor.nativeType.split('::')[-1] + 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 %sBinding::Wrap(aCx, this, aGivenProto);\n" % self.descriptor.name + + def implTraverse(self): + retVal = "" + for m in self.descriptor.interface.members: + # Unroll the type so we pick up sequences of interfaces too. + if m.isAttr() and idlTypeNeedsCycleCollection(m.type): + retVal += (" NS_IMPL_CYCLE_COLLECTION_TRAVERSE(" + + CGDictionary.makeMemberName(m.identifier.name) + + ")\n") + return retVal + + def implUnlink(self): + retVal = "" + for m in self.descriptor.interface.members: + if m.isAttr(): + name = CGDictionary.makeMemberName(m.identifier.name) + # Unroll the type so we pick up sequences of interfaces too. + if idlTypeNeedsCycleCollection(m.type): + retVal += " NS_IMPL_CYCLE_COLLECTION_UNLINK(" + name + ")\n" + elif m.type.isAny(): + retVal += " tmp->" + name + ".setUndefined();\n" + elif m.type.isObject() or m.type.isSpiderMonkeyInterface(): + retVal += " tmp->" + name + " = nullptr;\n" + return retVal + + def implTrace(self): + retVal = "" + for m in self.descriptor.interface.members: + if m.isAttr(): + name = CGDictionary.makeMemberName(m.identifier.name) + if m.type.isAny() or m.type.isObject() or m.type.isSpiderMonkeyInterface(): + retVal += " NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(" + name + ")\n" + elif typeNeedsRooting(m.type): + raise TypeError("Need to implement tracing for event " + "member of type %s" % m.type) + return retVal + + def define(self): + dropJS = "" + for m in self.descriptor.interface.members: + if m.isAttr(): + member = CGDictionary.makeMemberName(m.identifier.name) + if m.type.isAny(): + dropJS += member + " = JS::UndefinedValue();\n" + elif m.type.isObject() or m.type.isSpiderMonkeyInterface(): + dropJS += member + " = nullptr;\n" + if dropJS != "": + dropJS += "mozilla::DropJSObjects(this);\n" + # Just override CGClass and do our own thing + nativeType = self.descriptor.nativeType.split('::')[-1] + ctorParams = ("aOwner, nullptr, nullptr" if self.parentType == "Event" + else "aOwner") + + 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_INHERITED(${nativeType}) + NS_INTERFACE_MAP_END_INHERITING(${parentType}) + + ${nativeType}::${nativeType}(mozilla::dom::EventTarget* aOwner) + : ${parentType}(${ctorParams}) + { + } + + ${nativeType}::~${nativeType}() + { + $*{dropJS} + } + + """, + ifaceName=self.descriptor.name, + nativeType=nativeType, + ctorParams=ctorParams, + parentType=self.parentType, + traverse=self.implTraverse(), + unlink=self.implUnlink(), + trace=self.implTrace(), + 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.isDOMString() or type.isUSVString(): + nativeType = CGGeneric("nsString") + elif type.isByteString(): + nativeType = CGGeneric("nsCString") + 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.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/ErrorResult.h", + "mozilla/dom/%sBinding.h" % interfaceName, + 'mozilla/dom/BindingUtils.h', + ], + [ + "%s.h" % interfaceName, + "js/GCAPI.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 000000000..5c96580a1 --- /dev/null +++ b/dom/bindings/Configuration.py @@ -0,0 +1,791 @@ +# 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 WebIDL import IDLImplementsStatement +import os +from collections import defaultdict + +autogenerated_comment = "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n" + + +class DescriptorProvider: + """ + A way of getting descriptors for interface names. Subclasses must + have a getDescriptor method callable with the interface name only. + """ + def __init__(self): + pass + + +class Configuration(DescriptorProvider): + """ + Represents global configuration state based on IDL parse data and + the configuration file. + """ + def __init__(self, filename, parseData, generatedEvents=[]): + DescriptorProvider.__init__(self) + + # Read the configuration file. + glbl = {} + execfile(filename, glbl) + config = glbl['DOMInterfaces'] + + # 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.optimizedOutDescriptorNames = set() + self.generatedEvents = generatedEvents + self.maxProtoChainLength = 0 + for thing in parseData: + if isinstance(thing, IDLImplementsStatement): + # Our build system doesn't support dep build involving + # addition/removal of "implements" statements that appear in a + # different .webidl file than their LHS interface. Make sure we + # don't have any of those. + # + # But whitelist a RHS that is LegacyQueryInterface, + # since people shouldn't be adding any of those. + if (thing.implementor.filename() != thing.filename() and + thing.implementee.identifier.name != "LegacyQueryInterface"): + raise TypeError( + "The binding build system doesn't really support " + "'implements' statements which don't appear in the " + "file in which the left-hand side of the statement is " + "defined. Don't do this unless your right-hand side " + "is LegacyQueryInterface.\n" + "%s\n" + "%s" % + (thing.location, thing.implementor.location)) + + assert not thing.isType() + + if not thing.isInterface() and not thing.isNamespace(): + continue + iface = thing + self.interfaces[iface.identifier.name] = iface + if iface.identifier.name not in config: + # Completely skip consequential interfaces with no descriptor + # if they have no interface object because chances are we + # don't need to do anything interesting with them. + if iface.isConsequential() and not iface.hasInterfaceObject(): + self.optimizedOutDescriptorNames.add(iface.identifier.name) + continue + entry = {} + else: + entry = config[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(lambda x, y: cmp(x.name, y.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.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) + + for (t, _) in getAllTypes(self.descriptors, self.dictionaries, self.callbacks): + while True: + if t.isMozMap(): + t = t.inner + elif t.unroll() != t: + t = t.unroll() + elif t.isPromise(): + t = t.promiseInnerType() + else: + break + if t.isUnion(): + 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 = self.unionsPerFilename[f] + unionsForFilename = filter(lambda u: u.name != t.name, + unionsForFilename) + 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 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 = [] + for key, val in filters.iteritems(): + 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': + getter = lambda x: (not x.interface.isExternal() and + x.interface.hasInterfaceObject()) + elif key == 'hasInterfacePrototypeObject': + getter = lambda x: (not x.interface.isExternal() and + x.interface.hasInterfacePrototypeObject()) + elif key == 'hasInterfaceOrInterfacePrototypeObject': + getter = lambda x: x.hasInterfaceOrInterfacePrototypeObject() + elif key == 'isCallback': + getter = lambda x: x.interface.isCallback() + elif key == 'isExternal': + getter = lambda x: x.interface.isExternal() + elif key == 'isJSImplemented': + getter = lambda x: x.interface.isJSImplemented() + elif key == 'isNavigatorProperty': + getter = lambda x: x.interface.isNavigatorProperty() + elif key == 'isExposedInAnyWorker': + getter = lambda x: x.interface.isExposedInAnyWorker() + elif key == 'isExposedInWorkerDebugger': + getter = lambda x: x.interface.isExposedInWorkerDebugger() + elif key == 'isExposedInAnyWorklet': + getter = lambda x: x.interface.isExposedInAnyWorklet() + elif key == 'isExposedInSystemGlobals': + getter = lambda x: x.interface.isExposedInSystemGlobals() + elif key == 'isExposedInWindow': + getter = lambda x: x.interface.isExposedInWindow() + 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 = filter(lambda x: f[0](x) == f[1], curr) + return curr + + def getEnums(self, webIDLFile): + return filter(lambda e: e.filename() == webIDLFile, self.enums) + + def getDictionaries(self, webIDLFile): + return filter(lambda d: d.filename() == webIDLFile, self.dictionaries) + + def getCallbacks(self, webIDLFile): + return filter(lambda c: c.filename() == webIDLFile, self.callbacks) + + 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 + + if interfaceName in self.optimizedOutDescriptorNames: + raise NoSuchDescriptorError( + "No descriptor for '%s', which is a mixin ([NoInterfaceObject] " + "and a consequential interface) without an explicit " + "Bindings.conf annotation." % interfaceName) + + raise NoSuchDescriptorError("For " + interfaceName + " found no matches") + + +class NoSuchDescriptorError(TypeError): + def __init__(self, str): + TypeError.__init__(self, str) + + +def methodReturnsJSObject(method): + assert method.isMethod() + if method.returnsPromise(): + return True + + for signature in method.signatures(): + returnType = signature[0] + if returnType.isObject() or returnType.isSpiderMonkeyInterface(): + return True + + return False + + +def MemberIsUnforgeable(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.isUnforgeable() or + descriptor.interface.getExtendedAttribute("Unforgeable"))) + + +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.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(): + 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) + + self.hasXPConnectImpls = desc.get('hasXPConnectImpls', False) + + # If we're concrete, we need to crawl our ancestor interfaces and mark + # them as having a concrete descendant. + self.concrete = (not self.interface.isExternal() and + not self.interface.isCallback() and + not self.interface.isNamespace() and + desc.get('concrete', True)) + self.hasUnforgeableMembers = (self.concrete and + any(MemberIsUnforgeable(m, self) for m in + self.interface.members)) + self.operations = { + 'IndexedGetter': None, + 'IndexedSetter': None, + 'IndexedCreator': None, + 'IndexedDeleter': None, + 'NamedGetter': None, + 'NamedSetter': None, + 'NamedCreator': None, + 'NamedDeleter': None, + 'Stringifier': None, + 'LegacyCaller': None, + 'Jsonifier': None + } + + # Stringifiers and jsonifiers 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.isJsonifier(): + addOperation('Jsonifier', m) + + 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.isCreator(): + addIndexedOrNamedOperation('Creator', 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.hasNonOrdinaryGetPrototypeOf()) + + if self.proxy: + if (not self.operations['IndexedGetter'] and + (self.operations['IndexedSetter'] or + self.operations['IndexedDeleter'] or + self.operations['IndexedCreator'])): + 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'] or + self.operations['NamedCreator'])): + 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 + desc.get('wrapperCache', True)) + # Nasty temporary hack for supporting both DOM and SpiderMonkey promises + # without too much pain + if self.interface.identifier.name == "Promise": + assert self.wrapperCache + # But really, we're only wrappercached if we have an interface + # object (that is, when we're not using SpiderMonkey promises). + self.wrapperCache = self.interface.hasInterfaceObject() + + self.name = interface.identifier.name + + # self.extendedAttributes is a dict of dicts, keyed on + # all/getterOnly/setterOnly and then on member name. Values are an + # array of extended attributes. + self.extendedAttributes = {'all': {}, 'getterOnly': {}, 'setterOnly': {}} + + def addExtendedAttribute(attribute, config): + def add(key, members, attribute): + for member in members: + self.extendedAttributes[key].setdefault(member, []).append(attribute) + + if isinstance(config, dict): + for key in ['all', 'getterOnly', 'setterOnly']: + add(key, config.get(key, []), attribute) + elif isinstance(config, list): + add('all', config, attribute) + else: + assert isinstance(config, str) + if config == '*': + iface = self.interface + while iface: + add('all', map(lambda m: m.name, iface.members), attribute) + iface = iface.parent + else: + add('all', [config], attribute) + + if self.interface.isJSImplemented(): + addExtendedAttribute('implicitJSContext', ['constructor']) + else: + for attribute in ['implicitJSContext']: + addExtendedAttribute(attribute, desc.get(attribute, {})) + + if self.interface.identifier.name == 'Navigator': + for m in self.interface.members: + if m.isAttr() and m.navigatorObjectGetter: + # These getters call ConstructNavigatorObject to construct + # the value, and ConstructNavigatorObject needs a JSContext. + self.extendedAttributes['all'].setdefault(m.identifier.name, []).append('implicitJSContext') + + self._binaryNames = desc.get('binaryNames', {}) + self._binaryNames.setdefault('__legacycaller', 'LegacyCall') + self._binaryNames.setdefault('__stringifier', 'Stringify') + + if not self.interface.isExternal(): + def isTestInterface(iface): + return (iface.identifier.name in ["TestInterface", + "TestJSImplInterface", + "TestRenamedInterface"]) + + for member in self.interface.members: + if not member.isAttr() and not member.isMethod(): + continue + binaryName = member.getExtendedAttribute("BinaryName") + if binaryName: + assert isinstance(binaryName, list) + assert len(binaryName) == 1 + self._binaryNames.setdefault(member.identifier.name, + binaryName[0]) + + # Build the prototype chain. + self.prototypeChain = [] + parent = interface + while parent: + self.prototypeChain.insert(0, parent.identifier.name) + parent = parent.parent + config.maxProtoChainLength = max(config.maxProtoChainLength, + len(self.prototypeChain)) + + def binaryNameFor(self, name): + return self._binaryNames.get(name, name) + + @property + def prototypeNameChain(self): + return map(lambda p: self.getDescriptor(p).name, self.prototypeChain) + + @property + def parentPrototypeName(self): + if len(self.prototypeChain) == 1: + return None + return self.getDescriptor(self.prototypeChain[-2]).name + + def hasInterfaceOrInterfacePrototypeObject(self): + + # Forward-declared interfaces don't need either interface object or + # interface prototype object as they're going to use QI. + if self.interface.isExternal(): + return False + + return self.interface.hasInterfaceObject() or self.interface.hasInterfacePrototypeObject() + + @property + def hasNamedPropertiesObject(self): + if self.interface.isExternal(): + return False + + return self.isGlobal() and self.supportsNamedProperties() + + def getExtendedAttributes(self, member, getter=False, setter=False): + def ensureValidThrowsExtendedAttribute(attr): + if (attr is not None and attr is not True): + raise TypeError("Unknown value for 'Throws': " + attr[0]) + + def maybeAppendInfallibleToAttrs(attrs, throws): + ensureValidThrowsExtendedAttribute(throws) + if throws is None: + attrs.append("infallible") + + name = member.identifier.name + throws = self.interface.isJSImplemented() or member.getExtendedAttribute("Throws") + if member.isMethod(): + # JSObject-returning [NewObject] methods must be fallible, + # since they have to (fallibly) allocate the new JSObject. + if (member.getExtendedAttribute("NewObject") and + methodReturnsJSObject(member)): + throws = True + attrs = self.extendedAttributes['all'].get(name, []) + maybeAppendInfallibleToAttrs(attrs, throws) + return attrs + + assert member.isAttr() + assert bool(getter) != bool(setter) + key = 'getterOnly' if getter else 'setterOnly' + attrs = self.extendedAttributes['all'].get(name, []) + self.extendedAttributes[key].get(name, []) + if throws is None: + throwsAttr = "GetterThrows" if getter else "SetterThrows" + throws = member.getExtendedAttribute(throwsAttr) + maybeAppendInfallibleToAttrs(attrs, throws) + return attrs + + def supportsIndexedProperties(self): + return self.operations['IndexedGetter'] is not None + + def supportsNamedProperties(self): + return self.operations['NamedGetter'] is not None + + def hasNonOrdinaryGetPrototypeOf(self): + return self.interface.getExtendedAttribute("NonOrdinaryGetPrototypeOf") + + 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 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", + "HTMLAppletElement"]) + def needsXrayNamedDeleterHook(self): + return self.operations["NamedDeleter"] is not None + + def needsSpecialGenericOps(self): + """ + Returns true if this descriptor requires generic ops other than + GenericBindingMethod/GenericBindingGetter/GenericBindingSetter. + + In practice we need to do this if our this value might be an XPConnect + object or if we need to coerce null/undefined to the global. + """ + return self.hasXPConnectImpls or self.interface.isOnGlobalProtoChain() + + def isGlobal(self): + """ + Returns true if this is the primary interface for a global object + of some sort. + """ + return (self.interface.getExtendedAttribute("Global") or + self.interface.getExtendedAttribute("PrimaryGlobal")) + + @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 (not self.interface.isExternal() and + 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) + + +# Some utility methods +def getTypesFromDescriptor(descriptor): + """ + Get all argument and return types for all members of the descriptor + """ + members = [m for m in descriptor.interface.members] + if descriptor.interface.ctor(): + members.append(descriptor.interface.ctor()) + members.extend(descriptor.interface.namedConstructors) + 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 + types.append(returnType) + types.extend(a.type for a in arguments) + + types.extend(a.type for a in members if a.isAttr()) + + if descriptor.interface.maplikeOrSetlikeOrIterable: + maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable + if maplikeOrSetlikeOrIterable.hasKeyType(): + types.append(maplikeOrSetlikeOrIterable.keyType) + if maplikeOrSetlikeOrIterable.hasValueType(): + types.append(maplikeOrSetlikeOrIterable.valueType) + return types + + +def getFlatTypes(types): + retval = set() + for type in types: + type = type.unroll() + if type.isUnion(): + retval |= set(type.flatMemberTypes) + else: + retval.add(type) + return retval + + +def getTypesFromDictionary(dictionary): + """ + Get all member types for this dictionary + """ + types = [] + curDict = dictionary + while curDict: + types.extend([m.type for m in curDict.members]) + curDict = curDict.parent + return types + + +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, None) + +def iteratorNativeType(descriptor): + assert descriptor.interface.isIterable() + iterableDecl = descriptor.interface.maplikeOrSetlikeOrIterable + assert iterableDecl.isPairIterator() + return "mozilla::dom::IterableIterator<%s>" % descriptor.nativeType diff --git a/dom/bindings/DOMJSClass.h b/dom/bindings/DOMJSClass.h new file mode 100644 index 000000000..6e779840f --- /dev/null +++ b/dom/bindings/DOMJSClass.h @@ -0,0 +1,467 @@ +/* -*- 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 "jsfriendapi.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Likely.h" + +#include "mozilla/dom/PrototypeList.h" // auto-generated + +#include "mozilla/dom/JSSlots.h" + +class nsCycleCollectionParticipant; + +// 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 { +namespace 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 Compartment 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 compartment, regardless of whether the other + * compartment is a Secure Context or not. + * + * Checking the JSContext's Compartment 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) +{ + return JS::CompartmentCreationOptionsRef(js::GetContextCompartment(aCx)).secureContext() || + JS::CompartmentCreationOptionsRef(js::GetObjectCompartment(aObj)).secureContext(); +} + +typedef bool +(* ResolveOwnProperty)(JSContext* cx, JS::Handle<JSObject*> wrapper, + JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::MutableHandle<JS::PropertyDescriptor> desc); + +typedef bool +(* EnumerateOwnProperties)(JSContext* cx, JS::Handle<JSObject*> wrapper, + JS::Handle<JSObject*> obj, + JS::AutoIdVector& 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 BackstagePass = 1u << 1; +static const uint32_t DedicatedWorkerGlobalScope = 1u << 2; +static const uint32_t SharedWorkerGlobalScope = 1u << 3; +static const uint32_t ServiceWorkerGlobalScope = 1u << 4; +static const uint32_t WorkerDebuggerGlobalScope = 1u << 5; +static const uint32_t WorkletGlobalScope = 1u << 6; +} // namespace GlobalNames + +struct PrefableDisablers { + inline bool isEnabled(JSContext* cx, JS::Handle<JSObject*> obj) const { + // Reading "enabled" on a worker thread is technically undefined behavior, + // because it's written only on main threads, with no barriers of any sort. + // So we want to avoid doing that. But we don't particularly want to make + // expensive NS_IsMainThread calls here. + // + // The good news is that "enabled" is only written for things that have a + // Pref annotation, and such things can never be exposed on non-Window + // globals; our IDL parser enforces that. So as long as we check our + // exposure set before checking "enabled" we will be ok. + if (nonExposedGlobals && + IsNonExposedGlobal(cx, js::GetGlobalForObjectCrossCompartment(obj), + nonExposedGlobals)) { + return false; + } + if (!enabled) { + return false; + } + if (secureContext && !IsSecureContextOrObjectIsFromSecureContext(cx, obj)) { + return false; + } + if (enabledFunc && + !enabledFunc(cx, js::GetGlobalForObjectCrossCompartment(obj))) { + return false; + } + return true; + } + + // A boolean indicating whether this set of specs is enabled. Not const + // because it will change at runtime if the corresponding pref is changed. + bool enabled; + + // A boolean indicating whether a Secure Context is required. + const bool secureContext; + + // Bitmask of global names that we should not be exposed in. + const uint16_t nonExposedGlobals; + + // 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 { + if (MOZ_LIKELY(!disablers)) { + return true; + } + return disablers->isEnabled(cx, obj); + } + + // Things that can disable this set of specs. |nullptr| means "cannot be + // disabled". + 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; +}; + +// Conceptually, NativeProperties has seven (Prefable<T>*, jsid*, T*) trios +// (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 21 pointers, but in most instances most of the trios 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 trios (stored in trios[]), plus four bits for each of the 7 possible +// trios: 1 bit that states if that trio is present, and 3 that state that +// trio's offset (if present) in trios[]. +// +// All trio accesses should be done via the getters, which contain assertions +// that check we don't overrun the end of the struct. (The trio 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. +// +// 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. +// +template <int N> +struct NativePropertiesN { + // Trio structs are stored in the trios[] array, and each element in the + // array could require a different T. Therefore, we can't use the correct + // type for mPrefables and mSpecs. Instead we use void* and cast to the + // correct type in the getters. + struct Trio { + const /*Prefable<const T>*/ void* const mPrefables; + const jsid* const mIds; + const /*T*/ void* const mSpecs; + }; + + const int32_t iteratorAliasMethodIndex; + + constexpr const NativePropertiesN<7>* Upcast() const { + return reinterpret_cast<const NativePropertiesN<7>*>(this); + } + +#define DO(SpecT, FieldName) \ +public: \ + /* The bitfields indicating the trio's presence and (if present) offset. */ \ + const uint32_t mHas##FieldName##s:1; \ + const uint32_t m##FieldName##sOffset:3; \ +private: \ + const Trio* FieldName##sTrio() const { \ + MOZ_ASSERT(Has##FieldName##s()); \ + return &trios[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##sTrio()->mPrefables); \ + } \ + const jsid* FieldName##Ids() const { \ + return FieldName##sTrio()->mIds; \ + } \ + const SpecT* FieldName##Specs() const { \ + return static_cast<const SpecT*>(FieldName##sTrio()->mSpecs); \ + } + + DO(JSFunctionSpec, StaticMethod) + DO(JSPropertySpec, StaticAttribute) + DO(JSFunctionSpec, Method) + DO(JSPropertySpec, Attribute) + DO(JSFunctionSpec, UnforgeableMethod) + DO(JSPropertySpec, UnforgeableAttribute) + DO(ConstantSpec, Constant) + +#undef DO + + const Trio trios[N]; +}; + +// Ensure the struct has the expected size. The 8 is for the +// iteratorAliasMethodIndex plus the bitfields; the rest is for trios[]. +static_assert(sizeof(NativePropertiesN<1>) == 8 + 3*sizeof(void*), "1 size"); +static_assert(sizeof(NativePropertiesN<2>) == 8 + 6*sizeof(void*), "2 size"); +static_assert(sizeof(NativePropertiesN<3>) == 8 + 9*sizeof(void*), "3 size"); +static_assert(sizeof(NativePropertiesN<4>) == 8 + 12*sizeof(void*), "4 size"); +static_assert(sizeof(NativePropertiesN<5>) == 8 + 15*sizeof(void*), "5 size"); +static_assert(sizeof(NativePropertiesN<6>) == 8 + 18*sizeof(void*), "6 size"); +static_assert(sizeof(NativePropertiesN<7>) == 8 + 21*sizeof(void*), "7 size"); + +// The "base" type. +typedef NativePropertiesN<7> NativeProperties; + +struct NativePropertiesHolder +{ + const NativeProperties* regular; + const NativeProperties* chromeOnly; +}; + +// 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 NativePropertyHooks instance for the parent interface (for + // ShimInterfaceInfo). + const NativePropertyHooks* mProtoHooks; + + // 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, + 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); + +// 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 js::Class 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; + + static const DOMJSClass* FromJSClass(const JSClass* base) { + MOZ_ASSERT(base->flags & JSCLASS_IS_DOMJSCLASS); + return reinterpret_cast<const DOMJSClass*>(base); + } + + static const DOMJSClass* FromJSClass(const js::Class* base) { + return FromJSClass(Jsvalify(base)); + } + + const JSClass* ToJSClass() const { return Jsvalify(&mBase); } +}; + +// Special JSClass for DOM interface and interface prototype objects. +struct DOMIfaceAndProtoJSClass +{ + // It would be nice to just inherit from js::Class, but that precludes pure + // compile-time initialization of the form + // |DOMJSInterfaceAndPrototypeClass = {...};|, since C++ only allows brace + // initialization for aggregate/POD types. + const js::Class mBase; + + // Either eInterface, 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 toString() on this interface or interface prototype + // object. + const char* mToString; + + ProtoGetter mGetParentProto; + + static const DOMIfaceAndProtoJSClass* FromJSClass(const JSClass* base) { + MOZ_ASSERT(base->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS); + return reinterpret_cast<const DOMIfaceAndProtoJSClass*>(base); + } + static const DOMIfaceAndProtoJSClass* FromJSClass(const js::Class* base) { + return FromJSClass(Jsvalify(base)); + } + + const JSClass* ToJSClass() const { return Jsvalify(&mBase); } +}; + +class ProtoAndIfaceCache; + +inline bool +DOMGlobalHasProtoAndIFaceCache(JSObject* global) +{ + MOZ_ASSERT(js::GetObjectClass(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::GetObjectClass(global)->flags & JSCLASS_DOM_GLOBAL)) { + return false; + } + return DOMGlobalHasProtoAndIFaceCache(global); +} + +inline ProtoAndIfaceCache* +GetProtoAndIfaceCache(JSObject* global) +{ + MOZ_ASSERT(js::GetObjectClass(global)->flags & JSCLASS_DOM_GLOBAL); + return static_cast<ProtoAndIfaceCache*>( + js::GetReservedSlot(global, DOM_PROTOTYPE_SLOT).toPrivate()); +} + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_DOMJSClass_h */ diff --git a/dom/bindings/DOMJSProxyHandler.cpp b/dom/bindings/DOMJSProxyHandler.cpp new file mode 100644 index 000000000..65e540bc1 --- /dev/null +++ b/dom/bindings/DOMJSProxyHandler.cpp @@ -0,0 +1,362 @@ +/* -*- 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 "nsDOMClassInfo.h" +#include "nsWrapperCacheInlines.h" +#include "mozilla/dom/BindingUtils.h" + +#include "jsapi.h" + +using namespace JS; + +namespace mozilla { +namespace dom { + +jsid s_length_id = JSID_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) +{ + JS::Rooted<JSObject*> expando(cx, DOMProxyHandler::GetExpandoObject(proxy)); + JS::Value v = js::GetProxyExtra(proxy, JSPROXYSLOT_EXPANDO); + bool isOverrideBuiltins = !v.isObject() && !v.isUndefined(); + if (expando) { + bool hasOwn; + if (!JS_AlreadyHasOwnPropertyById(cx, expando, id, &hasOwn)) + return js::ShadowCheckFailed; + + if (hasOwn) { + return isOverrideBuiltins ? + js::ShadowsViaIndirectExpando : js::ShadowsViaDirectExpando; + } + } + + if (!isOverrideBuiltins) { + // Our expando, if any, didn't shadow, so we're not shadowing at all. + return js::DoesntShadow; + } + + bool hasOwn; + if (!GetProxyHandler(proxy)->hasOwn(cx, proxy, id, &hasOwn)) + return js::ShadowCheckFailed; + + return hasOwn ? js::Shadows : js::DoesntShadowUnique; +} + +// Store the information for the specialized ICs. +struct SetDOMProxyInformation +{ + SetDOMProxyInformation() { + js::SetDOMProxyInformation((const void*) &DOMProxyHandler::family, + JSPROXYSLOT_EXPANDO, DOMProxyShadows); + } +}; + +SetDOMProxyInformation gSetDOMProxyInformation; + +// static +void +DOMProxyHandler::ClearExternalRefsForWrapperRelease(JSObject* obj) +{ + MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object"); + JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO); + if (v.isUndefined()) { + // No expando. + return; + } + + // See EnsureExpandoObject for the work we're trying to undo here. + + if (v.isObject()) { + // Drop us from the DOM expando hashtable. Don't worry about clearing our + // slot reference to the expando; we're about to die anyway. + xpc::ObjectScope(obj)->RemoveDOMExpandoObject(obj); + return; + } + + // Prevent having a dangling pointer to our expando from the + // ExpandoAndGeneration. + js::ExpandoAndGeneration* expandoAndGeneration = + static_cast<js::ExpandoAndGeneration*>(v.toPrivate()); + expandoAndGeneration->expando = UndefinedValue(); +} + +// static +JSObject* +DOMProxyHandler::GetAndClearExpandoObject(JSObject* obj) +{ + MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object"); + JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO); + if (v.isUndefined()) { + return nullptr; + } + + if (v.isObject()) { + js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, UndefinedValue()); + xpc::ObjectScope(obj)->RemoveDOMExpandoObject(obj); + } else { + js::ExpandoAndGeneration* expandoAndGeneration = + static_cast<js::ExpandoAndGeneration*>(v.toPrivate()); + v = expandoAndGeneration->expando; + if (v.isUndefined()) { + return nullptr; + } + // We have to expose v to active JS here. The reason for that is that we + // might be in the middle of a GC right now. If our proxy hasn't been + // traced yet, when it _does_ get traced it won't trace the expando, since + // we're breaking that link. But the Rooted we're presumably being placed + // into is also not going to trace us, because Rooted marking is done at + // the very beginning of the GC. In that situation, we need to manually + // mark the expando as live here. JS::ExposeValueToActiveJS will do just + // that for us. + // + // We don't need to do this in the non-expandoAndGeneration case, because + // in that case our value is stored in a slot and slots will already mark + // the old thing live when the value in the slot changes. + JS::ExposeValueToActiveJS(v); + expandoAndGeneration->expando = UndefinedValue(); + } + + + return &v.toObject(); +} + +// static +JSObject* +DOMProxyHandler::EnsureExpandoObject(JSContext* cx, JS::Handle<JSObject*> obj) +{ + NS_ASSERTION(IsDOMProxy(obj), "expected a DOM proxy object"); + JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO); + if (v.isObject()) { + return &v.toObject(); + } + + js::ExpandoAndGeneration* expandoAndGeneration; + if (!v.isUndefined()) { + expandoAndGeneration = static_cast<js::ExpandoAndGeneration*>(v.toPrivate()); + if (expandoAndGeneration->expando.isObject()) { + return &expandoAndGeneration->expando.toObject(); + } + } else { + expandoAndGeneration = nullptr; + } + + JS::Rooted<JSObject*> expando(cx, + JS_NewObjectWithGivenProto(cx, nullptr, nullptr)); + if (!expando) { + return nullptr; + } + + nsISupports* native = UnwrapDOMObject<nsISupports>(obj); + nsWrapperCache* cache; + CallQueryInterface(native, &cache); + if (expandoAndGeneration) { + cache->PreserveWrapper(native); + expandoAndGeneration->expando.setObject(*expando); + + return expando; + } + + if (!xpc::ObjectScope(obj)->RegisterDOMExpandoObject(obj)) { + return nullptr; + } + + cache->SetPreservingWrapper(true); + js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, 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, + JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, + MutableHandle<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 *defined) const +{ + if (desc.hasGetterObject() && desc.setter() == JS_StrictPropertyStub) { + return result.failGetterOnly(); + } + + 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; + } + *defined = 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. + JS::Rooted<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::watch(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, + JS::Handle<JSObject*> callable) const +{ + return js::WatchGuts(cx, proxy, id, callable); +} + +bool +BaseDOMProxyHandler::unwatch(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id) const +{ + return js::UnwatchGuts(cx, proxy, id); +} + +bool +BaseDOMProxyHandler::ownPropertyKeys(JSContext* cx, + JS::Handle<JSObject*> proxy, + JS::AutoIdVector& 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::AutoIdVector& 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) +{ + MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object"); + JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO); + if (v.isObject()) { + return &v.toObject(); + } + + if (v.isUndefined()) { + return nullptr; + } + + js::ExpandoAndGeneration* expandoAndGeneration = + static_cast<js::ExpandoAndGeneration*>(v.toPrivate()); + 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::GetProxyExtra(proxy, JSPROXYSLOT_EXPANDO); + MOZ_ASSERT(!v.isObject(), "Should not have expando object directly!"); + + if (v.isUndefined()) { + // This can happen if we GC while creating our object, before we get a + // chance to set up its JSPROXYSLOT_EXPANDO slot. + return; + } + + js::ExpandoAndGeneration* expandoAndGeneration = + static_cast<js::ExpandoAndGeneration*>(v.toPrivate()); + JS::TraceEdge(trc, &expandoAndGeneration->expando, + "Shadowing DOM proxy expando"); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/bindings/DOMJSProxyHandler.h b/dom/bindings/DOMJSProxyHandler.h new file mode 100644 index 000000000..1781649cc --- /dev/null +++ b/dom/bindings/DOMJSProxyHandler.h @@ -0,0 +1,268 @@ +/* -*- 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/Attributes.h" +#include "mozilla/Likely.h" + +#include "jsapi.h" +#include "js/Proxy.h" +#include "nsString.h" + +#define DOM_PROXY_OBJECT_SLOT js::PROXY_PRIVATE_SLOT + +namespace mozilla { +namespace dom { + +enum { + /** + * DOM proxies have an extra slot for the expando object at index + * JSPROXYSLOT_EXPANDO. + * + * 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 JSPROXYSLOT_EXPANDO 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.) + */ + JSPROXYSLOT_EXPANDO = 0 +}; + +template<typename T> struct Prefable; + +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<JS::PropertyDescriptor> desc) const override; + virtual bool ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::AutoIdVector &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::AutoIdVector &props) const override; + + bool watch(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, + JS::Handle<JSObject*> callable) const override; + bool unwatch(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id) 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::AutoIdVector& 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<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 *defined) 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 "external references" to this object. If you are not + * nsWrapperCAche::ReleaseWrapper, you do NOT want to be calling this method. + * + * XXXbz if we nixed the DOM expando hash and just had a finalizer that + * cleared out the value in the ExpandoAndGeneration in the shadowing case, + * could we just get rid of this function altogether? + */ + static void ClearExternalRefsForWrapperRelease(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 ExpandoAndGeneration. +class ShadowingDOMProxyHandler : public DOMProxyHandler +{ + virtual void trace(JSTracer* trc, JSObject* proxy) const override; +}; + +inline bool IsDOMProxy(JSObject *obj) +{ + const js::Class* clasp = js::GetObjectClass(obj); + return clasp->isProxy() && + js::GetProxyHandler(obj)->family() == &DOMProxyHandler::family; +} + +inline const DOMProxyHandler* +GetDOMProxyHandler(JSObject* obj) +{ + MOZ_ASSERT(IsDOMProxy(obj)); + return static_cast<const DOMProxyHandler*>(js::GetProxyHandler(obj)); +} + +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(JSContext* cx, 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... + if (MOZ_LIKELY(JSID_IS_INT(id))) { + return JSID_TO_INT(id); + } + if (MOZ_LIKELY(id == s_length_id)) { + return UINT32_MAX; + } + if (MOZ_UNLIKELY(!JSID_IS_ATOM(id))) { + return UINT32_MAX; + } + + JSLinearString* str = js::AtomToLinearString(JSID_TO_ATOM(id)); + char16_t s; + { + JS::AutoCheckCannotGC nogc; + if (js::LinearStringHasLatin1Chars(str)) { + s = *js::GetLatin1LinearStringChars(nogc, str); + } else { + s = *js::GetTwoByteLinearStringChars(nogc, str); + } + } + if (MOZ_LIKELY((unsigned)s >= 'a' && (unsigned)s <= 'z')) + return UINT32_MAX; + + uint32_t i; + return js::StringIsArrayIndex(str, &i) ? i : UINT32_MAX; +} + +inline bool +IsArrayIndex(uint32_t index) +{ + return index < UINT32_MAX; +} + +inline void +FillPropertyDescriptor(JS::MutableHandle<JS::PropertyDescriptor> desc, + JSObject* obj, bool readonly, bool enumerable = true) +{ + desc.object().set(obj); + desc.setAttributes((readonly ? JSPROP_READONLY : 0) | + (enumerable ? JSPROP_ENUMERATE : 0)); + desc.setGetter(nullptr); + desc.setSetter(nullptr); +} + +inline void +FillPropertyDescriptor(JS::MutableHandle<JS::PropertyDescriptor> desc, + JSObject* obj, const JS::Value& v, + bool readonly, bool enumerable = true) +{ + desc.value().set(v); + FillPropertyDescriptor(desc, obj, readonly, enumerable); +} + +inline void +FillPropertyDescriptor(JS::MutableHandle<JS::PropertyDescriptor> desc, + JSObject* obj, unsigned attributes, const JS::Value& v) +{ + desc.object().set(obj); + desc.value().set(v); + desc.setAttributes(attributes); + desc.setGetter(nullptr); + desc.setSetter(nullptr); +} + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_DOMProxyHandler_h */ diff --git a/dom/bindings/DOMString.h b/dom/bindings/DOMString.h new file mode 100644 index 000000000..72c8445ec --- /dev/null +++ b/dom/bindings/DOMString.h @@ -0,0 +1,245 @@ +/* -*- 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 "nsStringGlue.h" +#include "nsStringBuffer.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "nsDOMString.h" +#include "nsIAtom.h" + +namespace mozilla { +namespace 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 SetStringBuffer or SetEphemeralStringBuffer or SetOwnedString or + * SetOwnedAtom on this object. It's only OK to call + * SetStringBuffer/SetOwnedString/SetOwnedAtom 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 store a value in this class is to either to do nothing + * (which leaves this as an empty string), to call + * SetStringBuffer/SetEphemeralStringBuffer with a non-null stringbuffer, to + * call SetOwnedString, to call SetOwnedAtom, to call SetNull(), or to call + * AsAString() and set the value in the resulting nsString. These options are + * mutually exclusive! Don't do more than one of them. + * + * The proper way to extract a value is to check IsNull(). If not null, then + * check HasStringBuffer(). If that's true, check for a zero length, and if the + * length is nonzero call StringBuffer(). If the length is zero this is the + * empty string. If HasStringBuffer() returns false, call AsAString() and get + * the value from that. + */ +class MOZ_STACK_CLASS DOMString { +public: + DOMString() + : mStringBuffer(nullptr) + , mLength(0) + , mIsNull(false) + , mStringBufferOwned(false) + {} + ~DOMString() + { + MOZ_ASSERT(!mString || !mStringBuffer, + "Shouldn't have both present!"); + if (mStringBufferOwned) { + 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(!mStringBuffer, "We already have a stringbuffer?"); + MOZ_ASSERT(!mIsNull, "We're already set as null"); + if (!mString) { + mString.emplace(); + } + return *mString; + } + + bool HasStringBuffer() const + { + MOZ_ASSERT(!mString || !mStringBuffer, + "Shouldn't have both present!"); + MOZ_ASSERT(!mIsNull, "Caller should have checked IsNull() first"); + return !mString; + } + + // Get the stringbuffer. This can only be called if HasStringBuffer() + // returned true and StringBufferLength() is nonzero. 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(!mIsNull, "Caller should have checked IsNull() first"); + MOZ_ASSERT(HasStringBuffer(), + "Don't ask for the stringbuffer if we don't have it"); + MOZ_ASSERT(StringBufferLength() != 0, "Why are you asking for this?"); + MOZ_ASSERT(mStringBuffer, + "If our length is nonzero, we better have a stringbuffer."); + 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 (mStringBufferOwned) { + // Just hand that ref over. + mStringBufferOwned = false; + } else { + // Caller should end up holding a ref. + mStringBuffer->AddRef(); + } + } + + // 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 SetStringBuffer(nsStringBuffer* aStringBuffer, uint32_t aLength) + { + MOZ_ASSERT(mString.isNothing(), "We already have a string?"); + MOZ_ASSERT(!mIsNull, "We're already set as null"); + MOZ_ASSERT(!mStringBuffer, "Setting stringbuffer twice?"); + MOZ_ASSERT(aStringBuffer, "Why are we getting null?"); + mStringBuffer = aStringBuffer; + mLength = aLength; + } + + // Like SetStringBuffer, but holds a reference to the nsStringBuffer. + void SetEphemeralStringBuffer(nsStringBuffer* aStringBuffer, uint32_t aLength) + { + // We rely on SetStringBuffer to ensure our state invariants. + SetStringBuffer(aStringBuffer, aLength); + aStringBuffer->AddRef(); + mStringBufferOwned = true; + } + + void SetOwnedString(const nsAString& aString) + { + MOZ_ASSERT(mString.isNothing(), "We already have a string?"); + MOZ_ASSERT(!mIsNull, "We're already set as null"); + MOZ_ASSERT(!mStringBuffer, "Setting stringbuffer twice?"); + nsStringBuffer* buf = nsStringBuffer::FromString(aString); + if (buf) { + SetStringBuffer(buf, aString.Length()); + } else if (aString.IsVoid()) { + SetNull(); + } else if (!aString.IsEmpty()) { + AsAString() = aString; + } + } + + enum NullHandling + { + eTreatNullAsNull, + eTreatNullAsEmpty, + eNullNotExpected + }; + + void SetOwnedAtom(nsIAtom* aAtom, NullHandling aNullHandling) + { + MOZ_ASSERT(mString.isNothing(), "We already have a string?"); + MOZ_ASSERT(!mIsNull, "We're already set as null"); + MOZ_ASSERT(!mStringBuffer, "Setting stringbuffer twice?"); + MOZ_ASSERT(aAtom || aNullHandling != eNullNotExpected); + if (aNullHandling == eNullNotExpected || aAtom) { + SetStringBuffer(aAtom->GetStringBuffer(), aAtom->GetLength()); + } 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"); + mIsNull = true; + } + + bool IsNull() const + { + MOZ_ASSERT(!mStringBuffer || mString.isNothing(), + "How could we have a stringbuffer and a nonempty string?"); + return mIsNull || (mString && mString->IsVoid()); + } + + void ToString(nsAString& aString) + { + if (IsNull()) { + SetDOMStringToNull(aString); + } else if (HasStringBuffer()) { + if (StringBufferLength() == 0) { + aString.Truncate(); + } else { + // 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 { + aString = AsAString(); + } + } + +private: + // We need to be able to act like a string as needed + Maybe<nsAutoString> mString; + + // For callees that know we exist, we can be a stringbuffer/length/null-flag + // triple. + nsStringBuffer* MOZ_UNSAFE_REF("The ways in which this can be safe are " + "documented above and enforced through " + "assertions") mStringBuffer; + uint32_t mLength; + bool mIsNull; + bool mStringBufferOwned; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_DOMString_h diff --git a/dom/bindings/Date.cpp b/dom/bindings/Date.cpp new file mode 100644 index 000000000..8c8d50b33 --- /dev/null +++ b/dom/bindings/Date.cpp @@ -0,0 +1,49 @@ +/* -*- 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/Date.h" + +#include "jsapi.h" // for JS_ObjectIsDate +#include "jsfriendapi.h" // for DateGetMsecSinceEpoch +#include "js/Date.h" // for JS::NewDateObject, JS::ClippedTime, JS::TimeClip +#include "js/RootingAPI.h" // for Rooted, MutableHandle +#include "js/Value.h" // for Value +#include "mozilla/FloatingPoint.h" // for IsNaN, UnspecifiedNaN + +namespace mozilla { +namespace dom { + +bool +Date::SetTimeStamp(JSContext* aCx, JSObject* aObject) +{ + JS::Rooted<JSObject*> obj(aCx, aObject); + + double msecs; + if (!js::DateGetMsecSinceEpoch(aCx, obj, &msecs)) { + return false; + } + + JS::ClippedTime time = JS::TimeClip(msecs); + MOZ_ASSERT(NumbersAreIdentical(msecs, time.toDouble())); + + mMsecSinceEpoch = time; + return true; +} + +bool +Date::ToDateObject(JSContext* aCx, JS::MutableHandle<JS::Value> aRval) const +{ + JSObject* obj = JS::NewDateObject(aCx, mMsecSinceEpoch); + if (!obj) { + return false; + } + + aRval.setObject(*obj); + return true; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/bindings/Date.h b/dom/bindings/Date.h new file mode 100644 index 000000000..66c893e4d --- /dev/null +++ b/dom/bindings/Date.h @@ -0,0 +1,61 @@ +/* -*- 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/. */ + +/* Representation for dates. */ + +#ifndef mozilla_dom_Date_h +#define mozilla_dom_Date_h + +#include "js/Date.h" +#include "js/TypeDecls.h" + +namespace mozilla { +namespace dom { + +class Date +{ +public: + Date() {} + explicit Date(JS::ClippedTime aMilliseconds) + : mMsecSinceEpoch(aMilliseconds) + {} + + bool IsUndefined() const + { + return !mMsecSinceEpoch.isValid(); + } + + JS::ClippedTime TimeStamp() const + { + return mMsecSinceEpoch; + } + + // Returns an integer in the range [-8.64e15, +8.64e15] (-0 excluded), *or* + // returns NaN. DO NOT ASSUME THIS IS FINITE! + double ToDouble() const + { + return mMsecSinceEpoch.toDouble(); + } + + void SetTimeStamp(JS::ClippedTime aMilliseconds) + { + mMsecSinceEpoch = aMilliseconds; + } + + // Can return false if CheckedUnwrap fails. This will NOT throw; + // callers should do it as needed. + bool SetTimeStamp(JSContext* aCx, JSObject* aObject); + + bool ToDateObject(JSContext* aCx, JS::MutableHandle<JS::Value> aRval) const; + +private: + JS::ClippedTime mMsecSinceEpoch; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_Date_h diff --git a/dom/bindings/ErrorIPCUtils.h b/dom/bindings/ErrorIPCUtils.h new file mode 100644 index 000000000..0e60d8174 --- /dev/null +++ b/dom/bindings/ErrorIPCUtils.h @@ -0,0 +1,84 @@ +/* -*- 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 "ipc/IPCMessageUtils.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/Assertions.h" +#include "mozilla/Move.h" + +#ifndef IPC_ErrorIPCUtils_h +#define IPC_ErrorIPCUtils_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(Message* aMsg, 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(aMsg, aParam.mResult); + WriteParam(aMsg, aParam.IsErrorWithMessage()); + WriteParam(aMsg, aParam.IsDOMException()); + if (aParam.IsErrorWithMessage()) { + aParam.SerializeMessage(aMsg); + } else if (aParam.IsDOMException()) { + aParam.SerializeDOMExceptionInfo(aMsg); + } + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + paramType readValue; + if (!ReadParam(aMsg, aIter, &readValue.mResult)) { + return false; + } + bool hasMessage = false; + if (!ReadParam(aMsg, aIter, &hasMessage)) { + return false; + } + bool hasDOMExceptionInfo = false; + if (!ReadParam(aMsg, aIter, &hasDOMExceptionInfo)) { + return false; + } + if (hasMessage && hasDOMExceptionInfo) { + // Shouldn't have both! + return false; + } + if (hasMessage && !readValue.DeserializeMessage(aMsg, aIter)) { + return false; + } else if (hasDOMExceptionInfo && + !readValue.DeserializeDOMExceptionInfo(aMsg, aIter)) { + return false; + } + *aResult = Move(readValue); + return true; + } +}; + +} // namespace IPC + +#endif diff --git a/dom/bindings/ErrorResult.h b/dom/bindings/ErrorResult.h new file mode 100644 index 000000000..c45e7ea3b --- /dev/null +++ b/dom/bindings/ErrorResult.h @@ -0,0 +1,587 @@ +/* -*- 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 "js/GCAnnotations.h" +#include "js/Value.h" +#include "nscore.h" +#include "nsStringGlue.h" +#include "mozilla/Assertions.h" +#include "mozilla/Move.h" +#include "nsTArray.h" +#include "nsISupportsImpl.h" + +namespace IPC { +class Message; +template <typename> struct ParamTraits; +} // namespace IPC +class PickleIterator; + +namespace mozilla { + +namespace dom { + +enum ErrNum { +#define MSG_DEF(_name, _argc, _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, _exn, _str) \ + _argc, +#include "mozilla/dom/Errors.msg" +#undef MSG_DEF +}; +#endif + +uint16_t +GetErrorArgCount(const ErrNum aErrorNumber); + +namespace binding_detail { +void ThrowErrorMessage(JSContext* aCx, const unsigned aErrorNumber, ...); +} // namespace binding_detail + +template<typename... Ts> +inline bool +ThrowErrorMessage(JSContext* aCx, const ErrNum aErrorNumber, Ts&&... aArgs) +{ + binding_detail::ThrowErrorMessage(aCx, static_cast<const unsigned>(aErrorNumber), + mozilla::Forward<Ts>(aArgs)...); + return false; +} + +struct StringArrayAppender +{ + static void Append(nsTArray<nsString>& aArgs, uint16_t aCount) + { + MOZ_RELEASE_ASSERT(aCount == 0, "Must give at least as many string arguments as are required by the ErrNum."); + } + + template<typename... Ts> + static void Append(nsTArray<nsString>& aArgs, uint16_t aCount, const nsAString& 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, Forward<Ts>(aOtherArgs)...); + } +}; + +} // namespace dom + +class ErrorResult; + +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 = Move(aRHS); + } + TErrorResult& operator=(TErrorResult&& aRHS); + + explicit TErrorResult(nsresult aRv) + : TErrorResult() + { + AssignErrorCode(aRv); + } + + operator ErrorResult&(); + + void 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_TYPE_ERR || + rv == NS_ERROR_RANGE_ERR || + rv == NS_ERROR_DOM_JS_EXCEPTION || + rv == NS_ERROR_DOM_DOMEXCEPTION) { + // What about NS_ERROR_DOM_EXCEPTION_ON_JSCONTEXT? I guess that can be + // legitimately passed on through.... + // 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. + bool MaybeSetPendingException(JSContext* cx) + { + WouldReportJSException(); + if (!Failed()) { + return false; + } + + SetPendingException(cx); + 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 ThrowTypeError(Ts&&... messageArgs) + { + ThrowErrorWithMessage<errorNumber>(NS_ERROR_TYPE_ERR, + Forward<Ts>(messageArgs)...); + } + + template<dom::ErrNum errorNumber, typename... Ts> + void ThrowRangeError(Ts&&... messageArgs) + { + ThrowErrorWithMessage<errorNumber>(NS_ERROR_RANGE_ERR, + Forward<Ts>(messageArgs)...); + } + + bool IsErrorWithMessage() const { return ErrorCode() == NS_ERROR_TYPE_ERR || ErrorCode() == NS_ERROR_RANGE_ERR; } + + // 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 ThrowJSException(JSContext* cx, JS::Handle<JS::Value> exn); + bool IsJSException() const { return ErrorCode() == NS_ERROR_DOM_JS_EXCEPTION; } + + // Facilities for throwing a DOMException. If an empty message string is + // passed to ThrowDOMException, the default message string for the given + // nsresult will be used. The passed-in string must be UTF-8. The nsresult + // passed in must be one we create DOMExceptions for; otherwise you may get an + // XPConnect Exception. + void ThrowDOMException(nsresult rv, const nsACString& message = EmptyCString()); + bool IsDOMException() const { return ErrorCode() == NS_ERROR_DOM_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 NoteJSContextException(JSContext* aCx); + + // Check whether the TErrorResult says to just throw whatever is on + // the JSContext already. + bool IsJSContextException() { + return ErrorCode() == NS_ERROR_DOM_EXCEPTION_ON_JSCONTEXT; + } + + // Support for uncatchable exceptions. + void 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. + 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()); + } + +protected: + nsresult ErrorCode() const { + return mResult; + } + +private: +#ifdef DEBUG + enum UnionState { + HasMessage, + HasDOMExceptionInfo, + HasJSException, + HasNothing + }; +#endif // DEBUG + + friend struct IPC::ParamTraits<TErrorResult>; + friend struct IPC::ParamTraits<ErrorResult>; + void SerializeMessage(IPC::Message* aMsg) const; + bool DeserializeMessage(const IPC::Message* aMsg, PickleIterator* aIter); + + void SerializeDOMExceptionInfo(IPC::Message* aMsg) const; + bool DeserializeDOMExceptionInfo(const IPC::Message* aMsg, PickleIterator* aIter); + + // Helper method that creates a new Message for this TErrorResult, + // and returns the arguments array from that Message. + nsTArray<nsString>& CreateErrorMessageHelper(const dom::ErrNum errorNumber, nsresult errorType); + + 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), + "Pass in the right number of arguments"); +#endif + + ClearUnionData(); + + nsTArray<nsString>& messageArgsArray = CreateErrorMessageHelper(errorNumber, errorType); + uint16_t argCount = dom::GetErrorArgCount(errorNumber); + dom::StringArrayAppender::Append(messageArgsArray, argCount, + Forward<Ts>(messageArgs)...); +#ifdef DEBUG + mUnionState = HasMessage; +#endif // DEBUG + } + + MOZ_ALWAYS_INLINE void AssertInOwningThread() const { +#ifdef DEBUG + NS_ASSERT_OWNINGTHREAD(TErrorResult); +#endif + } + + void AssignErrorCode(nsresult aRv) { + MOZ_ASSERT(aRv != NS_ERROR_TYPE_ERR, "Use ThrowTypeError()"); + MOZ_ASSERT(aRv != NS_ERROR_RANGE_ERR, "Use ThrowRangeError()"); + MOZ_ASSERT(!IsErrorWithMessage(), "Don't overwrite errors with message"); + MOZ_ASSERT(aRv != NS_ERROR_DOM_JS_EXCEPTION, "Use ThrowJSException()"); + MOZ_ASSERT(!IsJSException(), "Don't overwrite JS exceptions"); + MOZ_ASSERT(aRv != NS_ERROR_DOM_DOMEXCEPTION, "Use ThrowDOMException()"); + 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_DOM_EXCEPTION_ON_JSCONTEXT, + "Use NoteJSContextException"); + // Don't trust people anyway, though. + if (aRv == NS_ERROR_TYPE_ERR || + aRv == NS_ERROR_RANGE_ERR || + aRv == NS_ERROR_DOM_JS_EXCEPTION || + aRv == NS_ERROR_DOM_DOMEXCEPTION) { + mResult = NS_ERROR_UNEXPECTED; + } else { + mResult = aRv; + } + } + + void ClearMessage(); + void ClearDOMExceptionInfo(); + + // ClearUnionData will try to clear the data in our + // mMessage/mJSException/mDOMExceptionInfo union. After this the union may be + // in an uninitialized state (e.g. mMessage or mDOMExceptionInfo may be + // pointing to deleted memory) 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. + void SetPendingException(JSContext* cx); + + // Methods for setting various specific kinds of pending exceptions. + void SetPendingExceptionWithMessage(JSContext* cx); + void SetPendingJSException(JSContext* cx); + void SetPendingDOMException(JSContext* cx); + 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_TYPE_ERR -- ThrowTypeError() called on us. + // NS_ERROR_RANGE_ERR -- ThrowRangeError() called on us. + // NS_ERROR_DOM_JS_EXCEPTION -- ThrowJSException() called on us. + // NS_ERROR_UNCATCHABLE_EXCEPTION -- ThrowUncatchableException called on us. + // NS_ERROR_DOM_DOMEXCEPTION -- ThrowDOMException() called on us. + nsresult mResult; + + struct Message; + struct DOMExceptionInfo; + // mMessage is set by ThrowErrorWithMessage and reported (and deallocated) by + // SetPendingExceptionWithMessage. + // mJSException is set (and rooted) by ThrowJSException and reported + // (and unrooted) by SetPendingJSException. + // mDOMExceptionInfo is set by ThrowDOMException and reported + // (and deallocated) by SetPendingDOMException. + union { + Message* mMessage; // valid when IsErrorWithMessage() + JS::Value mJSException; // valid when IsJSException() + DOMExceptionInfo* mDOMExceptionInfo; // valid when IsDOMException() + }; + +#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; +}; + +struct JustAssertCleanupPolicy { + static const bool assertHandled = true; + static const bool suppress = false; +}; + +struct AssertAndSuppressCleanupPolicy { + static const bool assertHandled = true; + static const bool suppress = true; +}; + +struct JustSuppressCleanupPolicy { + static const bool assertHandled = false; + static const bool suppress = true; +}; + +} // 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() + : BaseErrorResult() + {} + + ErrorResult(ErrorResult&& aRHS) + : BaseErrorResult(Move(aRHS)) + {} + + explicit ErrorResult(nsresult aRv) + : BaseErrorResult(aRv) + {} + + void operator=(nsresult rv) + { + BaseErrorResult::operator=(rv); + } + + ErrorResult& operator=(ErrorResult&& aRHS) + { + BaseErrorResult::operator=(Move(aRHS)); + return *this; + } + +private: + // Not to be implemented, to make sure people always pass this by + // reference, not by value. + ErrorResult(const ErrorResult&) = delete; + void operator=(const ErrorResult&) = delete; +}; + +template<typename CleanupPolicy> +binding_danger::TErrorResult<CleanupPolicy>::operator ErrorResult&() +{ + return *static_cast<ErrorResult*>( + reinterpret_cast<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> +{ +}; + +/****************************************************************************** + ** 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 000000000..142ccfdd6 --- /dev/null +++ b/dom/bindings/Errors.msg @@ -0,0 +1,107 @@ +/* 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>, <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. + * + * <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, 3, JSEXN_TYPEERR, "{0} '{1}' is not a valid value for enumeration {2}.") +MSG_DEF(MSG_MISSING_ARGUMENTS, 1, JSEXN_TYPEERR, "Not enough arguments to {0}.") +MSG_DEF(MSG_NOT_OBJECT, 1, JSEXN_TYPEERR, "{0} is not an object.") +MSG_DEF(MSG_NOT_CALLABLE, 1, JSEXN_TYPEERR, "{0} is not callable.") +MSG_DEF(MSG_NOT_CONSTRUCTOR, 1, JSEXN_TYPEERR, "{0} is not a constructor.") +MSG_DEF(MSG_DOES_NOT_IMPLEMENT_INTERFACE, 2, JSEXN_TYPEERR, "{0} does not implement interface {1}.") +MSG_DEF(MSG_METHOD_THIS_DOES_NOT_IMPLEMENT_INTERFACE, 2, JSEXN_TYPEERR, "'{0}' called on an object that does not implement interface {1}.") +MSG_DEF(MSG_METHOD_THIS_UNWRAPPING_DENIED, 1, JSEXN_TYPEERR, "Permission to call '{0}' denied.") +MSG_DEF(MSG_THIS_DOES_NOT_IMPLEMENT_INTERFACE, 1, JSEXN_TYPEERR, "\"this\" object does not implement interface {0}.") +MSG_DEF(MSG_NOT_IN_UNION, 2, JSEXN_TYPEERR, "{0} could not be converted to any of: {1}.") +MSG_DEF(MSG_ILLEGAL_CONSTRUCTOR, 0, JSEXN_TYPEERR, "Illegal constructor.") +MSG_DEF(MSG_CONSTRUCTOR_WITHOUT_NEW, 1, JSEXN_TYPEERR, "Constructor {0} requires 'new'") +MSG_DEF(MSG_ENFORCE_RANGE_NON_FINITE, 1, JSEXN_TYPEERR, "Non-finite value is out of range for {0}.") +MSG_DEF(MSG_ENFORCE_RANGE_OUT_OF_RANGE, 1, JSEXN_TYPEERR, "Value is out of range for {0}.") +MSG_DEF(MSG_NOT_SEQUENCE, 1, JSEXN_TYPEERR, "{0} can't be converted to a sequence.") +MSG_DEF(MSG_NOT_DICTIONARY, 1, JSEXN_TYPEERR, "{0} can't be converted to a dictionary.") +MSG_DEF(MSG_OVERLOAD_RESOLUTION_FAILED, 3, JSEXN_TYPEERR, "Argument {0} is not valid for any of the {1}-argument overloads of {2}.") +MSG_DEF(MSG_GLOBAL_NOT_NATIVE, 0, JSEXN_TYPEERR, "Global is not a native object.") +MSG_DEF(MSG_ENCODING_NOT_SUPPORTED, 1, JSEXN_RANGEERR, "The given encoding '{0}' is not supported.") +MSG_DEF(MSG_DOM_ENCODING_NOT_UTF, 0, JSEXN_RANGEERR, "The encoding must be utf-8, utf-16, or utf-16be.") +MSG_DEF(MSG_DOM_DECODING_FAILED, 0, JSEXN_TYPEERR, "Decoding failed.") +MSG_DEF(MSG_NOT_FINITE, 1, JSEXN_TYPEERR, "{0} is not a finite floating-point value.") +MSG_DEF(MSG_INVALID_VERSION, 0, JSEXN_TYPEERR, "0 (Zero) is not a valid database version.") +MSG_DEF(MSG_INVALID_BYTESTRING, 2, JSEXN_TYPEERR, "Cannot convert string to ByteString because the character" + " at index {0} has value {1} which is greater than 255.") +MSG_DEF(MSG_NOT_DATE, 1, JSEXN_TYPEERR, "{0} is not a date.") +MSG_DEF(MSG_INVALID_ADVANCE_COUNT, 0, JSEXN_TYPEERR, "0 (Zero) is not a valid advance count.") +MSG_DEF(MSG_DEFINEPROPERTY_ON_GSP, 0, JSEXN_TYPEERR, "Not allowed to define a property on the named properties object.") +MSG_DEF(MSG_INVALID_URL, 1, JSEXN_TYPEERR, "{0} is not a valid URL.") +MSG_DEF(MSG_URL_HAS_CREDENTIALS, 1, JSEXN_TYPEERR, "{0} is an url with embedded credentials.") +MSG_DEF(MSG_METADATA_NOT_CONFIGURED, 0, JSEXN_TYPEERR, "Either size or lastModified should be true.") +MSG_DEF(MSG_INVALID_READ_SIZE, 0, JSEXN_TYPEERR, "0 (Zero) is not a valid read size.") +MSG_DEF(MSG_HEADERS_IMMUTABLE, 0, JSEXN_TYPEERR, "Headers are immutable and cannot be modified.") +MSG_DEF(MSG_INVALID_HEADER_NAME, 1, JSEXN_TYPEERR, "{0} is an invalid header name.") +MSG_DEF(MSG_INVALID_HEADER_VALUE, 1, JSEXN_TYPEERR, "{0} is an invalid header value.") +MSG_DEF(MSG_INVALID_HEADER_SEQUENCE, 0, JSEXN_TYPEERR, "Headers require name/value tuples when being initialized by a sequence.") +MSG_DEF(MSG_PERMISSION_DENIED_TO_PASS_ARG, 1, JSEXN_TYPEERR, "Permission denied to pass cross-origin object as {0}.") +MSG_DEF(MSG_MISSING_REQUIRED_DICTIONARY_MEMBER, 1, JSEXN_TYPEERR, "Missing required {0}.") +MSG_DEF(MSG_REQUEST_INTEGRITY_METADATA_NOT_EMPTY, 0, JSEXN_TYPEERR, "Request integrity metadata should be an empty string when in no-cors mode.") +MSG_DEF(MSG_INVALID_REQUEST_METHOD, 1, JSEXN_TYPEERR, "Invalid request method {0}.") +MSG_DEF(MSG_INVALID_REQUEST_MODE, 1, JSEXN_TYPEERR, "Invalid request mode {0}.") +MSG_DEF(MSG_INVALID_REFERRER_URL, 1, JSEXN_TYPEERR, "Invalid referrer URL {0}.") +MSG_DEF(MSG_CROSS_ORIGIN_REFERRER_URL, 2, JSEXN_TYPEERR, "Referrer URL {0} cannot be cross-origin to the entry settings object ({1}).") +MSG_DEF(MSG_FETCH_BODY_CONSUMED_ERROR, 0, JSEXN_TYPEERR, "Body has already been consumed.") +MSG_DEF(MSG_RESPONSE_INVALID_STATUSTEXT_ERROR, 0, JSEXN_TYPEERR, "Response statusText may not contain newline or carriage return.") +MSG_DEF(MSG_FETCH_FAILED, 0, JSEXN_TYPEERR, "NetworkError when attempting to fetch resource.") +MSG_DEF(MSG_NO_BODY_ALLOWED_FOR_GET_AND_HEAD, 0, JSEXN_TYPEERR, "HEAD or GET Request cannot have a body.") +MSG_DEF(MSG_RESPONSE_NULL_STATUS_WITH_BODY, 0, JSEXN_TYPEERR, "Response body is given with a null body status.") +MSG_DEF(MSG_DEFINE_NON_CONFIGURABLE_PROP_ON_WINDOW, 0, JSEXN_TYPEERR, "Not allowed to define a non-configurable property on the WindowProxy object") +MSG_DEF(MSG_INVALID_ZOOMANDPAN_VALUE_ERROR, 0, JSEXN_RANGEERR, "Invalid zoom and pan value.") +MSG_DEF(MSG_INVALID_TRANSFORM_ANGLE_ERROR, 0, JSEXN_RANGEERR, "Invalid transform angle.") +MSG_DEF(MSG_INVALID_RESPONSE_STATUSCODE_ERROR, 0, JSEXN_RANGEERR, "Invalid response status code.") +MSG_DEF(MSG_INVALID_REDIRECT_STATUSCODE_ERROR, 0, JSEXN_RANGEERR, "Invalid redirect status code.") +MSG_DEF(MSG_INVALID_URL_SCHEME, 2, JSEXN_TYPEERR, "{0} URL {1} must be either http:// or https://.") +MSG_DEF(MSG_RESPONSE_URL_IS_NULL, 0, JSEXN_TYPEERR, "Cannot set Response.finalURL when Response.url is null.") +MSG_DEF(MSG_RESPONSE_HAS_VARY_STAR, 0, JSEXN_TYPEERR, "Invalid Response object with a 'Vary: *' header.") +MSG_DEF(MSG_BAD_FORMDATA, 0, JSEXN_TYPEERR, "Could not parse content as FormData.") +MSG_DEF(MSG_NO_ACTIVE_WORKER, 1, JSEXN_TYPEERR, "No active worker for scope {0}.") +MSG_DEF(MSG_NOTIFICATION_PERMISSION_DENIED, 0, JSEXN_TYPEERR, "Permission to show Notification denied.") +MSG_DEF(MSG_NOTIFICATION_NO_CONSTRUCTOR_IN_SERVICEWORKER, 0, JSEXN_TYPEERR, "Notification constructor cannot be used in ServiceWorkerGlobalScope. Use registration.showNotification() instead.") +MSG_DEF(MSG_INVALID_SCOPE, 2, JSEXN_TYPEERR, "Invalid scope trying to resolve {0} with base URL {1}.") +MSG_DEF(MSG_INVALID_KEYFRAME_OFFSETS, 0, JSEXN_TYPEERR, "Keyframes with specified offsets must be in order and all be in the range [0, 1].") +MSG_DEF(MSG_ILLEGAL_PROMISE_CONSTRUCTOR, 0, JSEXN_TYPEERR, "Non-constructor value passed to NewPromiseCapability.") +MSG_DEF(MSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY, 0, JSEXN_TYPEERR, "GetCapabilitiesExecutor function already invoked with non-undefined values.") +MSG_DEF(MSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE, 0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the resolve function.") +MSG_DEF(MSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE, 0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the reject function.") +MSG_DEF(MSG_PROMISE_ARG_NOT_ITERABLE, 1, JSEXN_TYPEERR, "{0} is not iterable") +MSG_DEF(MSG_IS_NOT_PROMISE, 1, JSEXN_TYPEERR, "{0} is not a Promise") +MSG_DEF(MSG_SW_INSTALL_ERROR, 2, JSEXN_TYPEERR, "ServiceWorker script at {0} for scope {1} encountered an error during installation.") +MSG_DEF(MSG_SW_SCRIPT_THREW, 2, JSEXN_TYPEERR, "ServiceWorker script at {0} for scope {1} threw an exception during script evaluation.") +MSG_DEF(MSG_TYPEDARRAY_IS_SHARED, 1, JSEXN_TYPEERR, "{0} can't be a typed array on SharedArrayBuffer") +MSG_DEF(MSG_CACHE_ADD_FAILED_RESPONSE, 3, JSEXN_TYPEERR, "Cache got {0} response with bad status {1} while trying to add request {2}") +MSG_DEF(MSG_SW_UPDATE_BAD_REGISTRATION, 2, JSEXN_TYPEERR, "Failed to update the ServiceWorker for scope {0} because the registration has been {1} since the update was scheduled.") +MSG_DEF(MSG_INVALID_DURATION_ERROR, 1, JSEXN_TYPEERR, "Invalid duration '{0}'.") +MSG_DEF(MSG_INVALID_EASING_ERROR, 1, JSEXN_TYPEERR, "Invalid easing '{0}'.") +MSG_DEF(MSG_INVALID_SPACING_MODE_ERROR, 1, JSEXN_TYPEERR, "Invalid spacing '{0}'.") +MSG_DEF(MSG_USELESS_SETTIMEOUT, 1, JSEXN_TYPEERR, "Useless {0} call (missing quotes around argument?)") +MSG_DEF(MSG_TOKENLIST_NO_SUPPORTED_TOKENS, 2, JSEXN_TYPEERR, "{0} attribute of <{1}> does not define any supported tokens") +MSG_DEF(MSG_CACHE_STREAM_CLOSED, 0, JSEXN_TYPEERR, "Response body is a cache file stream that has already been closed.") +MSG_DEF(MSG_TIME_VALUE_OUT_OF_RANGE, 1, JSEXN_TYPEERR, "{0} is outside the supported range for time values.") +MSG_DEF(MSG_ONLY_IF_CACHED_WITHOUT_SAME_ORIGIN, 1, JSEXN_TYPEERR, "Request mode '{0}' was used, but request cache mode 'only-if-cached' can only be used with request mode 'same-origin'.") +MSG_DEF(MSG_THRESHOLD_RANGE_ERROR, 0, JSEXN_RANGEERR, "Threshold values must all be in the range [0, 1].") +MSG_DEF(MSG_CACHE_OPEN_FAILED, 0, JSEXN_TYPEERR, "CacheStorage.open() failed to access the storage system.") diff --git a/dom/bindings/Exceptions.cpp b/dom/bindings/Exceptions.cpp new file mode 100644 index 000000000..a3f807688 --- /dev/null +++ b/dom/bindings/Exceptions.cpp @@ -0,0 +1,709 @@ +/* -*- 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/GCAPI.h" +#include "js/TypeDecls.h" +#include "jsapi.h" +#include "jsprf.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsIProgrammingLanguage.h" +#include "nsPIDOMWindow.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "XPCWrapper.h" +#include "WorkerPrivate.h" +#include "nsContentUtils.h" + +namespace mozilla { +namespace 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, + nsIException* 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 " + "CheckedUnwrap thing to make sense"); + + if (js::CheckedUnwrap(exnObj)) { + // 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(aCx, 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, nsIException* aException) +{ + // See if we really have an Exception. + nsCOMPtr<Exception> exception = do_QueryInterface(aException); + if (exception) { + ThrowExceptionObject(aCx, exception); + return; + } + + // We only have an nsIException (probably an XPCWrappedJS). Fall back on old + // wrapping. + MOZ_ASSERT(NS_IsMainThread()); + + JS::Rooted<JS::Value> val(aCx); + if (!WrapObject(aCx, aException, &NS_GET_IID(nsIException), &val)) { + return; + } + + ThrowExceptionValueIfSafe(aCx, val, aException); +} + +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; + if (NS_SUCCEEDED(aException->GetResult(&exceptionResult)) && + 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(); + nsCOMPtr<nsIException> 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) { + nsresult nr; + if (NS_SUCCEEDED(existingException->GetResult(&nr)) && + aRv == nr) { + // Reuse the existing exception. + ThrowExceptionObject(aCx, existingException); + return false; + } + } + + RefPtr<Exception> finalException = CreateException(aCx, 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(JSContext* aCx, 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_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, EmptyCString(), nullptr, nullptr); + return exception.forget(); +} + +already_AddRefed<nsIStackFrame> +GetCurrentJSStack(int32_t aMaxDepth) +{ + // is there a current context available? + JSContext* cx = nsContentUtils::GetCurrentJSContextForThread(); + + if (!cx || !js::GetContextCompartment(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, mozilla::Move(captureMode)); +} + +namespace exceptions { + +class JSStackFrame : public nsIStackFrame +{ +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); + +protected: + int32_t GetLineno(JSContext* aCx); + + int32_t GetColNo(JSContext* aCx); + +private: + virtual ~JSStackFrame(); + + JS::Heap<JSObject*> mStack; + nsString mFormattedStack; + + nsCOMPtr<nsIStackFrame> mCaller; + nsCOMPtr<nsIStackFrame> mAsyncCaller; + nsString mFilename; + nsString mFunname; + nsString mAsyncCause; + int32_t mLineno; + int32_t mColNo; + + bool mFilenameInitialized; + bool mFunnameInitialized; + bool mLinenoInitialized; + bool mColNoInitialized; + bool mAsyncCauseInitialized; + bool mAsyncCallerInitialized; + bool mCallerInitialized; + bool mFormattedStackInitialized; +}; + +JSStackFrame::JSStackFrame(JS::Handle<JSObject*> aStack) + : mStack(aStack) + , mLineno(0) + , mColNo(0) + , mFilenameInitialized(false) + , mFunnameInitialized(false) + , mLinenoInitialized(false) + , mColNoInitialized(false) + , mAsyncCauseInitialized(false) + , mAsyncCallerInitialized(false) + , mCallerInitialized(false) + , mFormattedStackInitialized(false) +{ + MOZ_ASSERT(mStack); + + mozilla::HoldJSObjects(this); +} + +JSStackFrame::~JSStackFrame() +{ + mozilla::DropJSObjects(this); +} + +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->mStack = nullptr; +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_SCRIPT_OBJECTS +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 + +NS_IMETHODIMP JSStackFrame::GetLanguage(uint32_t* aLanguage) +{ + *aLanguage = nsIProgrammingLanguage::JAVASCRIPT; + return NS_OK; +} + +NS_IMETHODIMP JSStackFrame::GetLanguageName(nsACString& aLanguageName) +{ + aLanguageName.AssignLiteral("JavaScript"); + return NS_OK; +} + +// 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 access is happening +// over Xrays. +// +// @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*, + JS::Handle<JSObject*>, + GetterOutParamType, + JS::SavedFrameSelfHosted), + bool aIsCached, bool* aCanCache, bool* aUseCachedValue, + ReturnType aValue) +{ + MOZ_ASSERT(aStack); + + JS::Rooted<JSObject*> stack(aCx, aStack); + // Allow caching if aCx and stack are same-compartment. Otherwise take the + // slow path. + *aCanCache = js::GetContextCompartment(aCx) == js::GetObjectCompartment(stack); + if (*aCanCache && aIsCached) { + *aUseCachedValue = true; + return; + } + + *aUseCachedValue = false; + + aPropGetter(aCx, stack, aValue, JS::SavedFrameSelfHosted::Exclude); +} + +NS_IMETHODIMP JSStackFrame::GetFilename(JSContext* aCx, nsAString& aFilename) +{ + if (!mStack) { + aFilename.Truncate(); + return NS_OK; + } + + JS::Rooted<JSString*> filename(aCx); + bool canCache = false, useCachedValue = false; + GetValueIfNotCached(aCx, mStack, JS::GetSavedFrameSource, + mFilenameInitialized, + &canCache, &useCachedValue, &filename); + if (useCachedValue) { + aFilename = mFilename; + return NS_OK; + } + + nsAutoJSString str; + if (!str.init(aCx, filename)) { + JS_ClearPendingException(aCx); + aFilename.Truncate(); + return NS_OK; + } + aFilename = str; + + if (canCache) { + mFilename = str; + mFilenameInitialized = true; + } + + return NS_OK; +} + +NS_IMETHODIMP JSStackFrame::GetName(JSContext* aCx, nsAString& aFunction) +{ + if (!mStack) { + aFunction.Truncate(); + return NS_OK; + } + + JS::Rooted<JSString*> name(aCx); + bool canCache = false, useCachedValue = false; + GetValueIfNotCached(aCx, mStack, JS::GetSavedFrameFunctionDisplayName, + mFunnameInitialized, &canCache, &useCachedValue, + &name); + + if (useCachedValue) { + aFunction = mFunname; + return NS_OK; + } + + if (name) { + nsAutoJSString str; + if (!str.init(aCx, name)) { + JS_ClearPendingException(aCx); + aFunction.Truncate(); + return NS_OK; + } + aFunction = str; + } else { + aFunction.SetIsVoid(true); + } + + if (canCache) { + mFunname = aFunction; + mFunnameInitialized = true; + } + + return NS_OK; +} + +int32_t +JSStackFrame::GetLineno(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::GetLineNumber(JSContext* aCx, int32_t* aLineNumber) +{ + *aLineNumber = GetLineno(aCx); + return NS_OK; +} + +int32_t +JSStackFrame::GetColNo(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::GetColumnNumber(JSContext* aCx, + int32_t* aColumnNumber) +{ + *aColumnNumber = GetColNo(aCx); + return NS_OK; +} + +NS_IMETHODIMP JSStackFrame::GetSourceLine(nsACString& aSourceLine) +{ + aSourceLine.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP JSStackFrame::GetAsyncCause(JSContext* aCx, + nsAString& aAsyncCause) +{ + if (!mStack) { + aAsyncCause.Truncate(); + return NS_OK; + } + + JS::Rooted<JSString*> asyncCause(aCx); + bool canCache = false, useCachedValue = false; + GetValueIfNotCached(aCx, mStack, JS::GetSavedFrameAsyncCause, + mAsyncCauseInitialized, &canCache, &useCachedValue, + &asyncCause); + + if (useCachedValue) { + aAsyncCause = mAsyncCause; + return NS_OK; + } + + if (asyncCause) { + nsAutoJSString str; + if (!str.init(aCx, asyncCause)) { + JS_ClearPendingException(aCx); + aAsyncCause.Truncate(); + return NS_OK; + } + aAsyncCause = str; + } else { + aAsyncCause.SetIsVoid(true); + } + + if (canCache) { + mAsyncCause = aAsyncCause; + mAsyncCauseInitialized = true; + } + + return NS_OK; +} + +NS_IMETHODIMP JSStackFrame::GetAsyncCaller(JSContext* aCx, + nsIStackFrame** aAsyncCaller) +{ + if (!mStack) { + *aAsyncCaller = nullptr; + return NS_OK; + } + + JS::Rooted<JSObject*> asyncCallerObj(aCx); + bool canCache = false, useCachedValue = false; + GetValueIfNotCached(aCx, mStack, JS::GetSavedFrameAsyncParent, + mAsyncCallerInitialized, &canCache, &useCachedValue, + &asyncCallerObj); + + if (useCachedValue) { + NS_IF_ADDREF(*aAsyncCaller = mAsyncCaller); + return NS_OK; + } + + nsCOMPtr<nsIStackFrame> asyncCaller = + asyncCallerObj ? new JSStackFrame(asyncCallerObj) : nullptr; + asyncCaller.forget(aAsyncCaller); + + if (canCache) { + mAsyncCaller = *aAsyncCaller; + mAsyncCallerInitialized = true; + } + + return NS_OK; +} + +NS_IMETHODIMP JSStackFrame::GetCaller(JSContext* aCx, nsIStackFrame** aCaller) +{ + if (!mStack) { + *aCaller = nullptr; + return NS_OK; + } + + JS::Rooted<JSObject*> callerObj(aCx); + bool canCache = false, useCachedValue = false; + GetValueIfNotCached(aCx, mStack, JS::GetSavedFrameParent, mCallerInitialized, + &canCache, &useCachedValue, &callerObj); + + if (useCachedValue) { + NS_IF_ADDREF(*aCaller = mCaller); + return NS_OK; + } + + nsCOMPtr<nsIStackFrame> caller = + callerObj ? new JSStackFrame(callerObj) : nullptr; + caller.forget(aCaller); + + if (canCache) { + mCaller = *aCaller; + mCallerInitialized = true; + } + + return NS_OK; +} + +NS_IMETHODIMP JSStackFrame::GetFormattedStack(JSContext* aCx, nsAString& aStack) +{ + if (!mStack) { + aStack.Truncate(); + return NS_OK; + } + + // 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. + + // Allow caching if aCx and stack are same-compartment. Otherwise take the + // slow path. + bool canCache = + js::GetContextCompartment(aCx) == js::GetObjectCompartment(mStack); + if (canCache && mFormattedStackInitialized) { + aStack = mFormattedStack; + return NS_OK; + } + + JS::Rooted<JSObject*> stack(aCx, mStack); + + JS::Rooted<JSString*> formattedStack(aCx); + if (!JS::BuildStackString(aCx, stack, &formattedStack)) { + JS_ClearPendingException(aCx); + aStack.Truncate(); + return NS_OK; + } + + nsAutoJSString str; + if (!str.init(aCx, formattedStack)) { + JS_ClearPendingException(aCx); + aStack.Truncate(); + return NS_OK; + } + + aStack = str; + + if (canCache) { + mFormattedStack = str; + mFormattedStackInitialized = true; + } + + return NS_OK; +} + +NS_IMETHODIMP JSStackFrame::GetNativeSavedFrame(JS::MutableHandle<JS::Value> aSavedFrame) +{ + aSavedFrame.setObjectOrNull(mStack); + return NS_OK; +} + +NS_IMETHODIMP JSStackFrame::ToString(JSContext* aCx, nsACString& _retval) +{ + _retval.Truncate(); + + nsString filename; + nsresult rv = GetFilename(aCx, filename); + NS_ENSURE_SUCCESS(rv, rv); + + if (filename.IsEmpty()) { + filename.AssignLiteral("<unknown filename>"); + } + + nsString funname; + rv = GetName(aCx, funname); + NS_ENSURE_SUCCESS(rv, rv); + + if (funname.IsEmpty()) { + funname.AssignLiteral("<TOP_LEVEL>"); + } + + int32_t lineno = GetLineno(aCx); + + static const char format[] = "JS frame :: %s :: %s :: line %d"; + _retval.AppendPrintf(format, + NS_ConvertUTF16toUTF8(filename).get(), + NS_ConvertUTF16toUTF8(funname).get(), + lineno); + return NS_OK; +} + +already_AddRefed<nsIStackFrame> +CreateStack(JSContext* aCx, JS::StackCapture&& aCaptureMode) +{ + JS::Rooted<JSObject*> stack(aCx); + if (!JS::CaptureCurrentStack(aCx, &stack, mozilla::Move(aCaptureMode))) { + return nullptr; + } + + if (!stack) { + return nullptr; + } + + nsCOMPtr<nsIStackFrame> frame = new JSStackFrame(stack); + return frame.forget(); +} + +} // namespace exceptions +} // namespace dom +} // namespace mozilla diff --git a/dom/bindings/Exceptions.h b/dom/bindings/Exceptions.h new file mode 100644 index 000000000..521c550f5 --- /dev/null +++ b/dom/bindings/Exceptions.h @@ -0,0 +1,69 @@ +/* -*- 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 "nsIException.h" +#include "nsStringGlue.h" +#include "jsapi.h" + +class nsIStackFrame; +class nsPIDOMWindowInner; +template <class T> +struct already_AddRefed; + +namespace mozilla { +namespace 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 = EmptyCString()); + +// 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); +void +ThrowExceptionObject(JSContext* aCx, nsIException* aException); + +// Create an exception object for the given nsresult and message but don't set +// it pending on aCx. 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(JSContext* aCx, nsresult aRv, + const nsACString& aMessage = EmptyCString()); + +// 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); + +} // namespace exceptions +} // namespace dom +} // namespace mozilla + +#endif diff --git a/dom/bindings/FakeString.h b/dom/bindings/FakeString.h new file mode 100644 index 000000000..bd92a1bca --- /dev/null +++ b/dom/bindings/FakeString.h @@ -0,0 +1,160 @@ +/* -*- 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" + +namespace mozilla { +namespace dom { +namespace binding_detail { +// A struct that has the same layout as an nsString but much faster +// constructor and destructor behavior. FakeString uses inline storage +// for small strings and a nsStringBuffer for longer strings. +struct FakeString { + FakeString() : + mFlags(nsString::F_TERMINATED) + { + } + + ~FakeString() { + if (mFlags & nsString::F_SHARED) { + nsStringBuffer::FromData(mData)->Release(); + } + } + + void Rebind(const nsString::char_type* aData, nsString::size_type aLength) { + MOZ_ASSERT(mFlags == nsString::F_TERMINATED); + mData = const_cast<nsString::char_type*>(aData); + mLength = aLength; + } + + // 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 nsAString& aString) { + RefPtr<nsStringBuffer> sharedBuffer = nsStringBuffer::FromString(aString); + if (!sharedBuffer) { + Rebind(aString.Data(), aString.Length()); + } else { + AssignFromStringBuffer(sharedBuffer.forget()); + mLength = aString.Length(); + } + } + + void Truncate() { + MOZ_ASSERT(mFlags == nsString::F_TERMINATED); + mData = nsString::char_traits::sEmptyBuffer; + mLength = 0; + } + + void SetIsVoid(bool aValue) { + MOZ_ASSERT(aValue, + "We don't support SetIsVoid(false) on FakeString!"); + Truncate(); + mFlags |= nsString::F_VOIDED; + } + + const nsString::char_type* Data() const + { + return mData; + } + + nsString::char_type* BeginWriting() + { + return mData; + } + + nsString::size_type Length() const + { + return mLength; + } + + // Reserve space to write aLength chars, not including null-terminator. + bool SetLength(nsString::size_type aLength, mozilla::fallible_t const&) { + // Use mInlineStorage for small strings. + if (aLength < sInlineCapacity) { + SetData(mInlineStorage); + } else { + RefPtr<nsStringBuffer> buf = nsStringBuffer::Alloc((aLength + 1) * sizeof(nsString::char_type)); + if (MOZ_UNLIKELY(!buf)) { + return false; + } + + AssignFromStringBuffer(buf.forget()); + } + mLength = aLength; + mData[mLength] = char16_t(0); + return true; + } + + // If this ever changes, change the corresponding code in the + // Optional<nsAString> specialization as well. + const nsAString* ToAStringPtr() const { + return reinterpret_cast<const nsString*>(this); + } + +operator const nsAString& () const { + return *reinterpret_cast<const nsString*>(this); + } + +private: + nsAString* ToAStringPtr() { + return reinterpret_cast<nsString*>(this); + } + + nsString::char_type* mData; + nsString::size_type mLength; + uint32_t mFlags; + + static const size_t sInlineCapacity = 64; + nsString::char_type mInlineStorage[sInlineCapacity]; + + FakeString(const FakeString& other) = delete; + void operator=(const FakeString& other) = delete; + + void SetData(nsString::char_type* aData) { + MOZ_ASSERT(mFlags == nsString::F_TERMINATED); + mData = const_cast<nsString::char_type*>(aData); + } + void AssignFromStringBuffer(already_AddRefed<nsStringBuffer> aBuffer) { + SetData(static_cast<nsString::char_type*>(aBuffer.take()->Data())); + mFlags = nsString::F_SHARED | nsString::F_TERMINATED; + } + + friend class NonNull<nsAString>; + + // 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 nsString { + public: + static void StaticAsserts() { + static_assert(offsetof(FakeString, mInlineStorage) == + sizeof(nsString), + "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, mFlags) == + offsetof(StringAsserter, mFlags), + "Offset of mFlags should match"); + } + }; +}; +} // namespace binding_detail +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_FakeString_h__ */
\ No newline at end of file diff --git a/dom/bindings/GenerateCSS2PropertiesWebIDL.py b/dom/bindings/GenerateCSS2PropertiesWebIDL.py new file mode 100644 index 000000000..58ec60c29 --- /dev/null +++ b/dom/bindings/GenerateCSS2PropertiesWebIDL.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/. + +import sys +import string +import argparse +import subprocess +import buildconfig +from mozbuild import shellutil + +# 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 DOMString %s;\n" % (", ".join(extendedAttrs), + propName) +def generate(output, idlFilename, preprocessorHeader): + cpp = list(buildconfig.substs['CPP']) + cpp += shellutil.split(buildconfig.substs['ACDEFINES']) + cpp.append(preprocessorHeader) + preprocessed = subprocess.check_output(cpp) + + propList = eval(preprocessed) + props = "" + for [name, prop, id, flags, pref, proptype] in propList: + if "CSS_PROPERTY_INTERNAL" in flags: + continue + # Unfortunately, even some of the getters here are fallible + # (e.g. on nsComputedDOMStyle). + extendedAttrs = ["Throws", "TreatNullAs=EmptyString"] + if pref is not "": + extendedAttrs.append('Pref="%s"' % pref) + + # webkit properties get a capitalized "WebkitFoo" accessor (added here) + # as well as a camelcase "webkitFoo" accessor (added next). + if (prop.startswith("Webkit")): + props += generateLine(prop, extendedAttrs) + + # Generate a line with camelCase spelling of property-name (or capitalized, + # for Moz-prefixed properties): + if not prop.startswith("Moz"): + prop = prop[0].lower() + prop[1:] + props += generateLine(prop, extendedAttrs) + + # 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 above generatePropLine() call + # covered (3) and all of (1) except "float". If we now output attributes + # for all the cases where "name" doesn't match "prop", that will cover + # "float" and (2). + if prop != name: + extendedAttrs.append('BinaryName="%s"' % prop) + # Throw in a '_' before the attribute name, because some of these + # property names collide with IDL reserved words. + props += generateLine("_" + name, 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 000000000..041319638 --- /dev/null +++ b/dom/bindings/IterableIterator.cpp @@ -0,0 +1,35 @@ +/* -*- 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/IterableIterator.h" + +namespace mozilla { +namespace 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_COLLECTING_ADDREF(IterableIteratorBase) +NS_IMPL_CYCLE_COLLECTING_RELEASE(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 + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IterableIteratorBase) +NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +} +} diff --git a/dom/bindings/IterableIterator.h b/dom/bindings/IterableIterator.h new file mode 100644 index 000000000..1a56cec33 --- /dev/null +++ b/dom/bindings/IterableIterator.h @@ -0,0 +1,204 @@ +/* -*- 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/. */ + +/** + * 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 "nsISupports.h" +#include "nsWrapperCache.h" +#include "nsPIDOMWindow.h" +#include "nsCOMPtr.h" +#include "mozilla/dom/ToJSValue.h" +#include "jswrapper.h" +#include "mozilla/dom/IterableIteratorBinding.h" + +namespace mozilla { +namespace dom { + +class IterableIteratorBase : public nsISupports +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(IterableIteratorBase) + typedef enum { + Keys = 0, + Values, + Entries + } IterableIteratorType; + + IterableIteratorBase() {} + +protected: + virtual ~IterableIteratorBase() {} + virtual void UnlinkHelper() = 0; + virtual void TraverseHelper(nsCycleCollectionTraversalCallback& cb) = 0; +}; + +template <typename T> +class IterableIterator final : public IterableIteratorBase +{ +public: + typedef bool (*WrapFunc)(JSContext* aCx, + IterableIterator<T>* aObject, + JS::Handle<JSObject*> aGivenProto, + JS::MutableHandle<JSObject*> aReflector); + + explicit IterableIterator(T* aIterableObj, + IterableIteratorType aIteratorType, + WrapFunc aWrapFunc) + : mIterableObj(aIterableObj) + , mIteratorType(aIteratorType) + , mWrapFunc(aWrapFunc) + , mIndex(0) + { + MOZ_ASSERT(mIterableObj); + MOZ_ASSERT(mWrapFunc); + } + + void + Next(JSContext* aCx, JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv) + { + JS::Rooted<JS::Value> value(aCx, JS::UndefinedValue()); + if (mIndex >= this->mIterableObj->GetIterableLength()) { + DictReturn(aCx, aResult, true, value, aRv); + return; + } + switch (mIteratorType) { + case IterableIteratorType::Keys: + { + if (!ToJSValue(aCx, this->mIterableObj->GetKeyAtIndex(mIndex), &value)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + DictReturn(aCx, aResult, false, value, aRv); + break; + } + case IterableIteratorType::Values: + { + if (!ToJSValue(aCx, this->mIterableObj->GetValueAtIndex(mIndex), &value)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + DictReturn(aCx, aResult, false, value, aRv); + break; + } + case IterableIteratorType::Entries: + { + JS::Rooted<JS::Value> key(aCx); + if (!ToJSValue(aCx, this->mIterableObj->GetKeyAtIndex(mIndex), &key)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + if (!ToJSValue(aCx, this->mIterableObj->GetValueAtIndex(mIndex), &value)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + KeyAndValueReturn(aCx, key, value, aResult, aRv); + break; + } + default: + MOZ_CRASH("Invalid iterator type!"); + } + ++mIndex; + } + + bool + WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aObj) + { + return (*mWrapFunc)(aCx, this, aGivenProto, aObj); + } + +protected: + static void + DictReturn(JSContext* aCx, JS::MutableHandle<JSObject*> 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.toObject()); + } + + static 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()); + } + +protected: + virtual ~IterableIterator() {} + + // Since we're templated on a binding, we need to possibly CC it, but can't do + // that through macros. So it happens here. + virtual 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. + IterableIteratorType mIteratorType; + // Function pointer to binding-type-specific Wrap() call for this iterator. + WrapFunc mWrapFunc; + // Current index of iteration. + uint32_t mIndex; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_IterableIterator_h diff --git a/dom/bindings/JSSlots.h b/dom/bindings/JSSlots.h new file mode 100644 index 000000000..5e19957d6 --- /dev/null +++ b/dom/bindings/JSSlots.h @@ -0,0 +1,34 @@ +/* -*- 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 + +#endif /* mozilla_dom_DOMSlots_h */ diff --git a/dom/bindings/Makefile.in b/dom/bindings/Makefile.in new file mode 100644 index 000000000..95f267397 --- /dev/null +++ b/dom/bindings/Makefile.in @@ -0,0 +1,70 @@ +# 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 + +# 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 + +# TODO This list should be emitted to a .pp file via +# GenerateCSS2PropertiesWebIDL.py (bug 1281614) +css2properties_dependencies = \ + $(topsrcdir)/layout/style/nsCSSPropList.h \ + $(topsrcdir)/layout/style/nsCSSPropAliasList.h \ + $(webidl_base)/CSS2Properties.webidl.in \ + $(topsrcdir)/layout/style/PythonCSSProps.h \ + $(srcdir)/GenerateCSS2PropertiesWebIDL.py \ + $(GLOBAL_DEPS) \ + $(NULL) + +CSS2Properties.webidl: $(css2properties_dependencies) + +# 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) + +include codegen.pp + +codegen.pp: $(codegen_dependencies) + $(call py_action,webidl,$(srcdir)) + @$(TOUCH) $@ + +.PHONY: compiletests +compiletests: + $(call SUBMAKE,libs,test) + +endif + +GARBAGE += \ + codegen.pp \ + codegen.json \ + parser.out \ + WebIDLGrammar.pkl \ + $(wildcard *.h) \ + $(wildcard *Binding.cpp) \ + $(wildcard *Event.cpp) \ + $(wildcard *-event.cpp) \ + $(wildcard *.webidl) \ + $(NULL) + +DIST_GARBAGE += \ + file-lists.json \ + $(NULL) diff --git a/dom/bindings/MozMap.h b/dom/bindings/MozMap.h new file mode 100644 index 000000000..1e920c098 --- /dev/null +++ b/dom/bindings/MozMap.h @@ -0,0 +1,121 @@ +/* -*- 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 MozMap arguments. This is an nsTHashtable + * under the hood, but we don't want to leak that implementation + * detail. + */ + +#ifndef mozilla_dom_MozMap_h +#define mozilla_dom_MozMap_h + +#include "nsTHashtable.h" +#include "nsHashKeys.h" +#include "nsStringGlue.h" +#include "nsTArray.h" +#include "mozilla/Attributes.h" +#include "mozilla/Move.h" + +namespace mozilla { +namespace dom { + +namespace binding_detail { +template<typename DataType> +class MozMapEntry : public nsStringHashKey +{ +public: + explicit MozMapEntry(const nsAString* aKeyTypePointer) + : nsStringHashKey(aKeyTypePointer) + { + } + + // Move constructor so we can do MozMaps of MozMaps. + MozMapEntry(MozMapEntry<DataType>&& aOther) + : nsStringHashKey(aOther), + mData(Move(aOther.mData)) + { + } + + DataType mData; +}; + +} // namespace binding_detail + +template<typename DataType> +class MozMap : protected nsTHashtable<binding_detail::MozMapEntry<DataType>> +{ +public: + typedef typename binding_detail::MozMapEntry<DataType> EntryType; + typedef nsTHashtable<EntryType> Base; + typedef MozMap<DataType> SelfType; + + MozMap() + { + } + + // Move constructor so we can do MozMap of MozMap. + MozMap(SelfType&& aOther) : + Base(Move(aOther)) + { + } + + // The return value is only safe to use until an AddEntry call. + const DataType& Get(const nsAString& aKey) const + { + const EntryType* ent = this->GetEntry(aKey); + MOZ_ASSERT(ent, "Why are you using a key we didn't claim to have?"); + return ent->mData; + } + + DataType& Get(const nsAString& aKey) + { + EntryType* ent = this->GetEntry(aKey); + MOZ_ASSERT(ent, "Why are you using a key we didn't claim to have?"); + return ent->mData; + } + + // The return value is only safe to use until an AddEntry call. + const DataType* GetIfExists(const nsAString& aKey) const + { + const EntryType* ent = this->GetEntry(aKey); + if (!ent) { + return nullptr; + } + return &ent->mData; + } + + void GetKeys(nsTArray<nsString>& aKeys) const { + for (auto iter = this->ConstIter(); !iter.Done(); iter.Next()) { + aKeys.AppendElement(iter.Get()->GetKey()); + } + } + + // XXXbz we expose this generic enumerator for tracing. Otherwise we'd end up + // with a dependency on BindingUtils.h here for the SequenceTracer bits. + typedef void (* Enumerator)(DataType* aValue, void* aClosure); + void EnumerateValues(Enumerator aEnumerator, void *aClosure) + { + for (auto iter = this->Iter(); !iter.Done(); iter.Next()) { + aEnumerator(&iter.Get()->mData, aClosure); + } + } + + MOZ_MUST_USE + DataType* AddEntry(const nsAString& aKey) + { + EntryType* ent = this->PutEntry(aKey, fallible); + if (!ent) { + return nullptr; + } + return &ent->mData; + } +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_MozMap_h diff --git a/dom/bindings/NonRefcountedDOMObject.h b/dom/bindings/NonRefcountedDOMObject.h new file mode 100644 index 000000000..2aef9ce4a --- /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 { +namespace 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 is 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: + NonRefcountedDOMObject() + { + MOZ_COUNT_CTOR(NonRefcountedDOMObject); + } + ~NonRefcountedDOMObject() + { + MOZ_COUNT_DTOR(NonRefcountedDOMObject); + } +}; + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_NonRefcountedDOMObject_h__ */ diff --git a/dom/bindings/Nullable.h b/dom/bindings/Nullable.h new file mode 100644 index 000000000..8d7d46905 --- /dev/null +++ b/dom/bindings/Nullable.h @@ -0,0 +1,140 @@ +/* -*- 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 "mozilla/Assertions.h" +#include "nsTArrayForwardDeclare.h" +#include "mozilla/Move.h" +#include "mozilla/Maybe.h" + +class nsCycleCollectionTraversalCallback; + +namespace mozilla { +namespace 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(mozilla::Move(aValue)); + } + + Nullable(Nullable<T>&& aOther) + : mValue(mozilla::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(mozilla::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); + } +}; + + +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 dom +} // namespace mozilla + +#endif /* mozilla_dom_Nullable_h */ diff --git a/dom/bindings/PrimitiveConversions.h b/dom/bindings/PrimitiveConversions.h new file mode 100644 index 000000000..3da58a877 --- /dev/null +++ b/dom/bindings/PrimitiveConversions.h @@ -0,0 +1,359 @@ +/* -*- 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 "jsapi.h" +#include "js/Conversions.h" +#include "mozilla/Assertions.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/FloatingPoint.h" + +namespace mozilla { +namespace 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, + 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, + 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, + 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, + 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, bool (*Enforce)(JSContext* cx, const double& d, T* retval)> +struct PrimitiveConversionTraits_ToCheckedIntHelper { + typedef T jstype; + typedef T intermediateType; + + static inline bool converter(JSContext* cx, JS::Handle<JS::Value> v, + jstype* retval) { + double intermediate; + if (!JS::ToNumber(cx, v, &intermediate)) { + return false; + } + + return Enforce(cx, intermediate, retval); + } +}; + +template<typename T> +inline bool +PrimitiveConversionTraits_EnforceRange(JSContext* cx, const double& d, T* retval) +{ + static_assert(std::numeric_limits<T>::is_integer, + "This can only be applied to integers!"); + + if (!mozilla::IsFinite(d)) { + return ThrowErrorMessage(cx, MSG_ENFORCE_RANGE_NON_FINITE, 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 ThrowErrorMessage(cx, MSG_ENFORCE_RANGE_OUT_OF_RANGE, TypeName<T>::value()); + } + + *retval = static_cast<T>(rounded); + return true; +} + +template<typename T> +struct PrimitiveConversionTraits<T, eEnforceRange> : + public PrimitiveConversionTraits_ToCheckedIntHelper<T, PrimitiveConversionTraits_EnforceRange<T> > { +}; + +template<typename T> +inline bool +PrimitiveConversionTraits_Clamp(JSContext* cx, const double& d, T* retval) +{ + static_assert(std::numeric_limits<T>::is_integer, + "This can only be applied to integers!"); + + if (mozilla::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(mozilla::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, 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, + 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, + 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> +bool ValueToPrimitive(JSContext* cx, JS::Handle<JS::Value> v, T* retval) +{ + typename PrimitiveConversionTraits<T, B>::jstype t; + if (!PrimitiveConversionTraits<T, B>::converter(cx, v, &t)) + return false; + + *retval = static_cast<T>( + static_cast<typename PrimitiveConversionTraits<T, B>::intermediateType>(t)); + return true; +} + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_PrimitiveConversions_h */ diff --git a/dom/bindings/RootedDictionary.h b/dom/bindings/RootedDictionary.h new file mode 100644 index 000000000..cf4adaaef --- /dev/null +++ b/dom/bindings/RootedDictionary.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/. */ + +#ifndef mozilla_dom_RootedDictionary_h__ +#define mozilla_dom_RootedDictionary_h__ + +#include "mozilla/GuardObjects.h" +#include "mozilla/dom/Nullable.h" +#include "jsapi.h" + +namespace mozilla { +namespace dom { + +template<typename T> +class MOZ_RAII RootedDictionary final : public T, + private JS::CustomAutoRooter +{ +public: + template <typename CX> + explicit RootedDictionary(const CX& cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : + T(), + JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT) + { + } + + 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 MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : + Nullable<T>(), + JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT) + { + } + + virtual void trace(JSTracer *trc) override + { + if (!this->IsNull()) { + this->Value().TraceDictionary(trc); + } + } +}; + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_RootedDictionary_h__ */ diff --git a/dom/bindings/RootedOwningNonNull.h b/dom/bindings/RootedOwningNonNull.h new file mode 100644 index 000000000..890afd74f --- /dev/null +++ b/dom/bindings/RootedOwningNonNull.h @@ -0,0 +1,73 @@ +/* -*- 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/RootingAPI.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); + } + } +}; +} // namespace JS + +namespace js { +template<typename T> +struct RootedBase<mozilla::OwningNonNull<T>> +{ + typedef mozilla::OwningNonNull<T> SmartPtrType; + + operator SmartPtrType& () const + { + auto& self = *static_cast<const JS::Rooted<SmartPtrType>*>(this); + return self.get(); + } + + operator T& () const + { + auto& self = *static_cast<const JS::Rooted<SmartPtrType>*>(this); + return self.get(); + } +}; +} // namespace js + +#endif /* mozilla_RootedOwningNonNull_h__ */ diff --git a/dom/bindings/RootedRefPtr.h b/dom/bindings/RootedRefPtr.h new file mode 100644 index 000000000..e27361b24 --- /dev/null +++ b/dom/bindings/RootedRefPtr.h @@ -0,0 +1,59 @@ +/* -*- 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/RootingAPI.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); + } + } +}; +} // namespace JS + +namespace js { +template<typename T> +struct RootedBase<RefPtr<T>> +{ + operator RefPtr<T>& () const + { + auto& self = *static_cast<const JS::Rooted<RefPtr<T>>*>(this); + return self.get(); + } + + operator T*() const + { + auto& self = *static_cast<const JS::Rooted<RefPtr<T>>*>(this); + return self.get(); + } +}; +} // namespace js + +#endif /* mozilla_RootedRefPtr_h__ */ diff --git a/dom/bindings/SimpleGlobalObject.cpp b/dom/bindings/SimpleGlobalObject.cpp new file mode 100644 index 000000000..6ac397019 --- /dev/null +++ b/dom/bindings/SimpleGlobalObject.cpp @@ -0,0 +1,176 @@ +/* -*- 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 "nsJSPrincipals.h" +#include "nsNullPrincipal.h" +#include "nsThreadUtils.h" +#include "nsContentUtils.h" + +#include "xpcprivate.h" + +#include "mozilla/dom/ScriptSettings.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_CLASS(SimpleGlobalObject) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SimpleGlobalObject) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER + tmp->UnlinkHostObjectURIs(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SimpleGlobalObject) + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS + tmp->TraverseHostObjectURIs(cb); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(SimpleGlobalObject) + +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 void +SimpleGlobal_finalize(js::FreeOp *fop, JSObject *obj) +{ + SimpleGlobalObject* globalObject = + static_cast<SimpleGlobalObject*>(JS_GetPrivate(obj)); + NS_RELEASE(globalObject); +} + +static void +SimpleGlobal_moved(JSObject *obj, const JSObject *old) +{ + SimpleGlobalObject* globalObject = + static_cast<SimpleGlobalObject*>(JS_GetPrivate(obj)); + globalObject->UpdateWrapper(obj, old); +} + +static const js::ClassOps SimpleGlobalClassOps = { + nullptr, + nullptr, + nullptr, + nullptr, + JS_EnumerateStandardClasses, + JS_ResolveStandardClass, + JS_MayResolveStandardClass, + SimpleGlobal_finalize, + nullptr, + nullptr, + nullptr, + JS_GlobalObjectTraceHook, +}; + +static const js::ClassExtension SimpleGlobalClassExtension = { + nullptr, + SimpleGlobal_moved +}; + +const js::Class SimpleGlobalClass = { + "", + JSCLASS_GLOBAL_FLAGS | + JSCLASS_HAS_PRIVATE | + JSCLASS_PRIVATE_IS_NSISUPPORTS | + JSCLASS_FOREGROUND_FINALIZE, + &SimpleGlobalClassOps, + JS_NULL_CLASS_SPEC, + &SimpleGlobalClassExtension, + JS_NULL_OBJECT_OPS +}; + +// 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::CompartmentOptions options; + options.creationOptions().setInvisibleToDebugger(true); + + if (NS_IsMainThread()) { + nsCOMPtr<nsIPrincipal> principal = nsNullPrincipal::Create(); + options.creationOptions().setTrace(xpc::TraceXPCGlobal); + global = xpc::CreateGlobalObject(cx, js::Jsvalify(&SimpleGlobalClass), + nsJSPrincipals::get(principal), + options); + } else { + global = JS_NewGlobalObject(cx, js::Jsvalify(&SimpleGlobalClass), + nullptr, + JS::DontFireOnNewGlobalHook, options); + } + + if (!global) { + jsapi.ClearException(); + return nullptr; + } + + JSAutoCompartment ac(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_SetPrivate(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_SplicePrototype(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::GetObjectClass(obj) != &SimpleGlobalClass) { + return SimpleGlobalObject::GlobalType::NotSimpleGlobal; + } + + SimpleGlobalObject* globalObject = + static_cast<SimpleGlobalObject*>(JS_GetPrivate(obj)); + return globalObject->Type(); +} + +} // namespace mozilla +} // namespace dom diff --git a/dom/bindings/SimpleGlobalObject.h b/dom/bindings/SimpleGlobalObject.h new file mode 100644 index 000000000..9781fbfaa --- /dev/null +++ b/dom/bindings/SimpleGlobalObject.h @@ -0,0 +1,99 @@ +/* -*- 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 "nsIGlobalObject.h" +#include "nsWrapperCache.h" +#include "js/TypeDecls.h" +#include "nsISupportsImpl.h" +#include "nsCycleCollectionParticipant.h" + +namespace mozilla { +namespace 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_SCRIPT_HOLDER_CLASS_AMBIGUOUS(SimpleGlobalObject, + nsIGlobalObject) + + // 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); + + virtual JSObject *GetGlobalJSObject() override + { + return GetWrapper(); + } + + virtual JSObject* WrapObject(JSContext* cx, + JS::Handle<JSObject*> aGivenProto) override + { + MOZ_CRASH("SimpleGlobalObject doesn't use DOM bindings!"); + } + +private: + SimpleGlobalObject(JSObject *global, GlobalType type) + : mType(type) + { + SetWrapper(global); + } + + virtual ~SimpleGlobalObject() + { + ClearWrapper(); + } + + const GlobalType mType; +}; + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_SimpleGlobalObject_h__ */ diff --git a/dom/bindings/StructuredClone.cpp b/dom/bindings/StructuredClone.cpp new file mode 100644 index 000000000..71b4f5c74 --- /dev/null +++ b/dom/bindings/StructuredClone.cpp @@ -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/. */ + +#include "mozilla/dom/StructuredClone.h" + +#include "js/StructuredClone.h" +#include "mozilla/dom/ImageData.h" +#include "mozilla/dom/StructuredCloneTags.h" + +namespace mozilla { +namespace dom { + +JSObject* +ReadStructuredCloneImageData(JSContext* aCx, JSStructuredCloneReader* aReader) +{ + // Read the information out of the stream. + uint32_t width, height; + JS::Rooted<JS::Value> dataArray(aCx); + if (!JS_ReadUint32Pair(aReader, &width, &height) || + !JS_ReadTypedArray(aReader, &dataArray)) { + return nullptr; + } + MOZ_ASSERT(dataArray.isObject()); + + // Protect the result from a moving GC in ~nsRefPtr. + JS::Rooted<JSObject*> result(aCx); + { + // Construct the ImageData. + RefPtr<ImageData> imageData = new ImageData(width, height, + dataArray.toObject()); + // Wrap it in a JS::Value. + if (!imageData->WrapObject(aCx, nullptr, &result)) { + return nullptr; + } + } + return result; +} + +bool +WriteStructuredCloneImageData(JSContext* aCx, JSStructuredCloneWriter* aWriter, + ImageData* aImageData) +{ + uint32_t width = aImageData->Width(); + uint32_t height = aImageData->Height(); + JS::Rooted<JSObject*> dataArray(aCx, aImageData->GetDataObject()); + + JSAutoCompartment ac(aCx, dataArray); + JS::Rooted<JS::Value> arrayValue(aCx, JS::ObjectValue(*dataArray)); + return JS_WriteUint32Pair(aWriter, SCTAG_DOM_IMAGEDATA, 0) && + JS_WriteUint32Pair(aWriter, width, height) && + JS_WriteTypedArray(aWriter, arrayValue); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/bindings/StructuredClone.h b/dom/bindings/StructuredClone.h new file mode 100644 index 000000000..bfd7700c6 --- /dev/null +++ b/dom/bindings/StructuredClone.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/. */ + +class JSObject; +struct JSContext; +struct JSStructuredCloneReader; +struct JSStructuredCloneWriter; + +namespace mozilla { +namespace dom { + +class ImageData; + +JSObject* +ReadStructuredCloneImageData(JSContext* aCx, JSStructuredCloneReader* aReader); + +bool +WriteStructuredCloneImageData(JSContext* aCx, JSStructuredCloneWriter* aWriter, + ImageData* aImageData); + +} // namespace dom +} // namespace mozilla diff --git a/dom/bindings/ToJSValue.cpp b/dom/bindings/ToJSValue.cpp new file mode 100644 index 000000000..d84428fb3 --- /dev/null +++ b/dom/bindings/ToJSValue.cpp @@ -0,0 +1,80 @@ +/* -*- 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" +#ifdef SPIDERMONKEY_PROMISE +#include "mozilla/dom/Promise.h" +#endif // SPIDERMONKEY_PROMISE +#include "nsAString.h" +#include "nsContentUtils.h" +#include "nsStringBuffer.h" +#include "xpcpublic.h" + +namespace mozilla { +namespace 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, + nsresult aArgument, + JS::MutableHandle<JS::Value> aValue) +{ + RefPtr<Exception> exception = CreateException(aCx, 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!"); + DebugOnly<bool> throwResult = aArgument.MaybeSetPendingException(aCx); + MOZ_ASSERT(throwResult); + DebugOnly<bool> getPendingResult = JS_GetPendingException(aCx, aValue); + MOZ_ASSERT(getPendingResult); + JS_ClearPendingException(aCx); + return true; +} + +#ifdef SPIDERMONKEY_PROMISE +bool +ToJSValue(JSContext* aCx, Promise& aArgument, + JS::MutableHandle<JS::Value> aValue) +{ + aValue.setObject(*aArgument.PromiseObj()); + return true; +} +#endif // SPIDERMONKEY_PROMISE + +} // namespace dom +} // namespace mozilla diff --git a/dom/bindings/ToJSValue.h b/dom/bindings/ToJSValue.h new file mode 100644 index 000000000..2021c0b4c --- /dev/null +++ b/dom/bindings/ToJSValue.h @@ -0,0 +1,377 @@ +/* -*- 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 "mozilla/TypeTraits.h" +#include "mozilla/Assertions.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/TypedArray.h" +#include "jsapi.h" +#include "nsISupports.h" +#include "nsTArray.h" +#include "nsWrapperCache.h" + +namespace mozilla { +namespace dom { + +class Promise; + +// If ToJSValue returns false, it must set an exception on the +// JSContext. + +// Accept strings. +MOZ_MUST_USE bool +ToJSValue(JSContext* aCx, + const nsAString& 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> +MOZ_MUST_USE +typename EnableIf<IsSame<T, bool>::value, bool>::Type +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.setNumber(aArgument); + return true; +} + +// Accept CallbackObjects +MOZ_MUST_USE 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.setObject(*aArgument.Callback()); + + return MaybeWrapValue(aCx, aValue); +} + +// Accept objects that inherit from nsWrapperCache (e.g. most +// DOM objects). +template <class T> +MOZ_MUST_USE +typename EnableIf<IsBaseOf<nsWrapperCache, T>::value, bool>::Type +ToJSValue(JSContext* aCx, + T& aArgument, + JS::MutableHandle<JS::Value> aValue) +{ + // Make sure we're called in a compartment + MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx)); + // Make sure non-webidl objects don't sneak in here + MOZ_ASSERT(aArgument.IsDOMBinding()); + + return GetOrCreateDOMReflector(aCx, aArgument, aValue); +} + +// Accept typed arrays built from appropriate nsTArray values +template<typename T> +MOZ_MUST_USE +typename EnableIf<IsBaseOf<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; +} + +// Accept objects that inherit from nsISupports but not nsWrapperCache (e.g. +// DOM File). +template <class T> +MOZ_MUST_USE +typename EnableIf<!IsBaseOf<nsWrapperCache, T>::value && + !IsBaseOf<CallbackObject, T>::value && + IsBaseOf<nsISupports, T>::value, bool>::Type +ToJSValue(JSContext* aCx, + T& aArgument, + JS::MutableHandle<JS::Value> aValue) +{ + // Make sure we're called in a compartment + MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx)); + + qsObjectHelper helper(ToSupports(&aArgument), nullptr); + JS::Rooted<JSObject*> scope(aCx, JS::CurrentGlobalOrNull(aCx)); + return XPCOMObjectToJsval(aCx, scope, helper, nullptr, true, aValue); +} + +// Accept nsRefPtr/nsCOMPtr +template <typename T> +MOZ_MUST_USE bool +ToJSValue(JSContext* aCx, + const nsCOMPtr<T>& aArgument, + JS::MutableHandle<JS::Value> aValue) +{ + return ToJSValue(aCx, *aArgument.get(), aValue); +} + +template <typename T> +MOZ_MUST_USE bool +ToJSValue(JSContext* aCx, + const RefPtr<T>& aArgument, + JS::MutableHandle<JS::Value> aValue) +{ + return ToJSValue(aCx, *aArgument.get(), aValue); +} + +template <typename T> +MOZ_MUST_USE bool +ToJSValue(JSContext* aCx, + const NonNull<T>& aArgument, + JS::MutableHandle<JS::Value> aValue) +{ + return ToJSValue(aCx, *aArgument.get(), aValue); +} + +// Accept WebIDL dictionaries +template <class T> +MOZ_MUST_USE +typename EnableIf<IsBaseOf<DictionaryBase, T>::value, bool>::Type +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 +MOZ_MUST_USE 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 +MOZ_MUST_USE 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 +MOZ_MUST_USE 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). +MOZ_MUST_USE 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. +MOZ_MUST_USE 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. +MOZ_MUST_USE bool +ToJSValue(JSContext* aCx, + ErrorResult& aArgument, + JS::MutableHandle<JS::Value> aValue); + +// Accept owning WebIDL unions. +template <typename T> +MOZ_MUST_USE +typename EnableIf<IsBaseOf<AllOwningUnionBase, T>::value, bool>::Type +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> +MOZ_MUST_USE +typename EnableIf<IsPointer<T>::value, bool>::Type +ToJSValue(JSContext* aCx, + T aArgument, + JS::MutableHandle<JS::Value> aValue) +{ + return ToJSValue(aCx, *aArgument, aValue); +} + +#ifdef SPIDERMONKEY_PROMISE +// Accept Promise objects, which need special handling. +MOZ_MUST_USE bool +ToJSValue(JSContext* aCx, + Promise& aArgument, + JS::MutableHandle<JS::Value> aValue); +#endif // SPIDERMONKEY_PROMISE + +// Accept arrays of other things we accept +template <typename T> +MOZ_MUST_USE 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::AutoValueVector 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; +} + +template <typename T> +MOZ_MUST_USE bool +ToJSValue(JSContext* aCx, + const nsTArray<T>& aArgument, + JS::MutableHandle<JS::Value> aValue) +{ + return ToJSValue(aCx, aArgument.Elements(), + aArgument.Length(), aValue); +} + +template <typename T> +MOZ_MUST_USE 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> +MOZ_MUST_USE bool +ToJSValue(JSContext* aCx, + const T(&aArgument)[N], + JS::MutableHandle<JS::Value> aValue) +{ + return ToJSValue(aCx, aArgument, N, aValue); +} + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_ToJSValue_h */ diff --git a/dom/bindings/TypedArray.h b/dom/bindings/TypedArray.h new file mode 100644 index 000000000..a86abcd9d --- /dev/null +++ b/dom/bindings/TypedArray.h @@ -0,0 +1,441 @@ +/* -*- 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 "jsapi.h" +#include "jsfriendapi.h" +#include "js/RootingAPI.h" +#include "js/TracingAPI.h" +#include "mozilla/Attributes.h" +#include "mozilla/Move.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "nsWrapperCache.h" + +namespace mozilla { +namespace dom { + +/* + * Class that just handles the JSObject storage and tracing for typed arrays + */ +struct TypedArrayObjectStorage : AllTypedArraysBase { +protected: + JSObject* mTypedObj; + JSObject* mWrappedObj; + + TypedArrayObjectStorage() + : mTypedObj(nullptr), + mWrappedObj(nullptr) + { + } + + TypedArrayObjectStorage(TypedArrayObjectStorage&& aOther) + : mTypedObj(aOther.mTypedObj), + mWrappedObj(aOther.mWrappedObj) + { + aOther.mTypedObj = nullptr; + aOther.mWrappedObj = nullptr; + } + +public: + inline void TraceSelf(JSTracer* trc) + { + JS::UnsafeTraceRoot(trc, &mTypedObj, "TypedArray.mTypedObj"); + JS::UnsafeTraceRoot(trc, &mWrappedObj, "TypedArray.mWrappedObj"); + } + +private: + TypedArrayObjectStorage(const TypedArrayObjectStorage&) = delete; +}; + +/* + * 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<typename T, + JSObject* UnwrapArray(JSObject*), + void GetLengthAndDataAndSharedness(JSObject*, uint32_t*, bool*, T**)> +struct TypedArray_base : public TypedArrayObjectStorage { + typedef T element_type; + + TypedArray_base() + : mData(nullptr), + mLength(0), + mShared(false), + mComputed(false) + { + } + + TypedArray_base(TypedArray_base&& aOther) + : TypedArrayObjectStorage(Move(aOther)), + mData(aOther.mData), + mLength(aOther.mLength), + mShared(aOther.mShared), + mComputed(aOther.mComputed) + { + aOther.mData = nullptr; + aOther.mLength = 0; + aOther.mShared = false; + aOther.mComputed = false; + } + +private: + mutable T* mData; + mutable uint32_t mLength; + mutable bool mShared; + mutable bool mComputed; + +public: + inline bool Init(JSObject* obj) + { + MOZ_ASSERT(!inited()); + mTypedObj = mWrappedObj = UnwrapArray(obj); + return inited(); + } + + inline bool inited() const { + return !!mTypedObj; + } + + // About shared memory: + // + // Any DOM TypedArray as well as any DOM ArrayBufferView that does + // not represent a JS DataView can map the memory of either a JS + // ArrayBuffer or a JS SharedArrayBuffer. (DataView cannot view + // shared memory.) 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 T *Data() const { + MOZ_ASSERT(mComputed); + if (mShared) + return nullptr; + return mData; + } + + inline T *DataAllowShared() const { + MOZ_ASSERT(mComputed); + return mData; + } + + inline uint32_t Length() const { + MOZ_ASSERT(mComputed); + if (mShared) + return 0; + return mLength; + } + + inline uint32_t LengthAllowShared() const { + MOZ_ASSERT(mComputed); + return mLength; + } + + inline JSObject *Obj() const { + MOZ_ASSERT(inited()); + return mWrappedObj; + } + + inline bool WrapIntoNewCompartment(JSContext* cx) + { + return JS_WrapObject(cx, + JS::MutableHandle<JSObject*>::fromMarkedLocation(&mWrappedObj)); + } + + inline void ComputeLengthAndData() const + { + MOZ_ASSERT(inited()); + MOZ_ASSERT(!mComputed); + GetLengthAndDataAndSharedness(mTypedObj, &mLength, &mShared, &mData); + mComputed = true; + } + +private: + TypedArray_base(const TypedArray_base&) = delete; +}; + +template<typename T, + JSObject* UnwrapArray(JSObject*), + T* GetData(JSObject*, bool* isShared, const JS::AutoCheckCannotGC&), + void GetLengthAndDataAndSharedness(JSObject*, uint32_t*, bool*, T**), + JSObject* CreateNew(JSContext*, uint32_t)> +struct TypedArray + : public TypedArray_base<T, UnwrapArray, GetLengthAndDataAndSharedness> +{ +private: + typedef TypedArray_base<T, UnwrapArray, GetLengthAndDataAndSharedness> Base; + +public: + TypedArray() + : Base() + {} + + TypedArray(TypedArray&& aOther) + : Base(Move(aOther)) + { + } + + static inline JSObject* + Create(JSContext* cx, nsWrapperCache* creator, uint32_t length, + const T* data = nullptr) { + JS::Rooted<JSObject*> creatorWrapper(cx); + Maybe<JSAutoCompartment> ac; + if (creator && (creatorWrapper = creator->GetWrapperPreserveColor())) { + ac.emplace(cx, creatorWrapper); + } + + return CreateCommon(cx, length, data); + } + + static inline JSObject* + Create(JSContext* cx, uint32_t length, const T* data = nullptr) { + return CreateCommon(cx, length, data); + } + +private: + static inline JSObject* + CreateCommon(JSContext* cx, uint32_t length, const T* data) { + JSObject* obj = CreateNew(cx, length); + if (!obj) { + return nullptr; + } + if (data) { + JS::AutoCheckCannotGC nogc; + bool isShared; + T* buf = static_cast<T*>(GetData(obj, &isShared, nogc)); + // Data will not be shared, until a construction protocol exists + // for constructing shared data. + MOZ_ASSERT(!isShared); + memcpy(buf, data, length*sizeof(T)); + } + return obj; + } + + TypedArray(const TypedArray&) = delete; +}; + +template<JSObject* UnwrapArray(JSObject*), + void GetLengthAndDataAndSharedness(JSObject*, uint32_t*, bool*, + uint8_t**), + js::Scalar::Type GetViewType(JSObject*)> +struct ArrayBufferView_base + : public TypedArray_base<uint8_t, UnwrapArray, GetLengthAndDataAndSharedness> +{ +private: + typedef TypedArray_base<uint8_t, UnwrapArray, GetLengthAndDataAndSharedness> + Base; + +public: + ArrayBufferView_base() + : Base() + { + } + + ArrayBufferView_base(ArrayBufferView_base&& aOther) + : Base(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; + } +}; + +typedef TypedArray<int8_t, js::UnwrapInt8Array, JS_GetInt8ArrayData, + js::GetInt8ArrayLengthAndData, JS_NewInt8Array> + Int8Array; +typedef TypedArray<uint8_t, js::UnwrapUint8Array, JS_GetUint8ArrayData, + js::GetUint8ArrayLengthAndData, JS_NewUint8Array> + Uint8Array; +typedef TypedArray<uint8_t, js::UnwrapUint8ClampedArray, JS_GetUint8ClampedArrayData, + js::GetUint8ClampedArrayLengthAndData, JS_NewUint8ClampedArray> + Uint8ClampedArray; +typedef TypedArray<int16_t, js::UnwrapInt16Array, JS_GetInt16ArrayData, + js::GetInt16ArrayLengthAndData, JS_NewInt16Array> + Int16Array; +typedef TypedArray<uint16_t, js::UnwrapUint16Array, JS_GetUint16ArrayData, + js::GetUint16ArrayLengthAndData, JS_NewUint16Array> + Uint16Array; +typedef TypedArray<int32_t, js::UnwrapInt32Array, JS_GetInt32ArrayData, + js::GetInt32ArrayLengthAndData, JS_NewInt32Array> + Int32Array; +typedef TypedArray<uint32_t, js::UnwrapUint32Array, JS_GetUint32ArrayData, + js::GetUint32ArrayLengthAndData, JS_NewUint32Array> + Uint32Array; +typedef TypedArray<float, js::UnwrapFloat32Array, JS_GetFloat32ArrayData, + js::GetFloat32ArrayLengthAndData, JS_NewFloat32Array> + Float32Array; +typedef TypedArray<double, js::UnwrapFloat64Array, JS_GetFloat64ArrayData, + js::GetFloat64ArrayLengthAndData, JS_NewFloat64Array> + Float64Array; +typedef ArrayBufferView_base<js::UnwrapArrayBufferView, + js::GetArrayBufferViewLengthAndData, + JS_GetArrayBufferViewType> + ArrayBufferView; +typedef TypedArray<uint8_t, js::UnwrapArrayBuffer, JS_GetArrayBufferData, + js::GetArrayBufferLengthAndData, JS_NewArrayBuffer> + ArrayBuffer; + +typedef TypedArray<uint8_t, js::UnwrapSharedArrayBuffer, JS_GetSharedArrayBufferData, + js::GetSharedArrayBufferLengthAndData, JS_NewSharedArrayBuffer> + SharedArrayBuffer; + +// 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 Promise::ArgumentToJSValue. +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; +}; + +// A class for rooting an existing TypedArray struct +template<typename ArrayType> +class MOZ_RAII TypedArrayRooter : private JS::CustomAutoRooter +{ +public: + template <typename CX> + TypedArrayRooter(const CX& cx, + ArrayType* aArray MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : + JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT), + mArray(aArray) + { + } + + virtual void trace(JSTracer* trc) override + { + mArray->TraceSelf(trc); + } + +private: + TypedArrayObjectStorage* const mArray; +}; + +// And a specialization for dealing with nullable typed arrays +template<typename Inner> struct Nullable; +template<typename ArrayType> +class MOZ_RAII TypedArrayRooter<Nullable<ArrayType> > : + private JS::CustomAutoRooter +{ +public: + template <typename CX> + TypedArrayRooter(const CX& cx, + Nullable<ArrayType>* aArray MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : + JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT), + mArray(aArray) + { + } + + virtual void trace(JSTracer* trc) override + { + if (!mArray->IsNull()) { + mArray->Value().TraceSelf(trc); + } + } + +private: + Nullable<ArrayType>* const mArray; +}; + +// Class for easily setting up a rooted typed array object on the stack +template<typename ArrayType> +class MOZ_RAII RootedTypedArray final : public ArrayType, + private TypedArrayRooter<ArrayType> +{ +public: + template <typename CX> + explicit RootedTypedArray(const CX& cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : + ArrayType(), + TypedArrayRooter<ArrayType>(cx, this + MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT) + { + } + + template <typename CX> + RootedTypedArray(const CX& cx, JSObject* obj MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : + ArrayType(obj), + TypedArrayRooter<ArrayType>(cx, this + MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT) + { + } +}; + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_TypedArray_h */ diff --git a/dom/bindings/UnionMember.h b/dom/bindings/UnionMember.h new file mode 100644 index 000000000..3842c6eb0 --- /dev/null +++ b/dom/bindings/UnionMember.h @@ -0,0 +1,59 @@ +/* -*- 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" + +namespace mozilla { +namespace 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; + +public: + T& SetValue() + { + new (mStorage.addr()) T(); + return *mStorage.addr(); + } + template <typename T1> + T& SetValue(const T1& aValue) + { + new (mStorage.addr()) T(aValue); + return *mStorage.addr(); + } + template<typename T1, typename T2> + T& SetValue(const T1& aValue1, const T2& aValue2) + { + new (mStorage.addr()) T(aValue1, aValue2); + return *mStorage.addr(); + } + T& Value() + { + return *mStorage.addr(); + } + const T& Value() const + { + return *mStorage.addr(); + } + void Destroy() + { + mStorage.addr()->~T(); + } +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_UnionMember_h diff --git a/dom/bindings/WebIDLGlobalNameHash.cpp b/dom/bindings/WebIDLGlobalNameHash.cpp new file mode 100644 index 000000000..7477b52e7 --- /dev/null +++ b/dom/bindings/WebIDLGlobalNameHash.cpp @@ -0,0 +1,324 @@ +/* -*- 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/GCAPI.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/Maybe.h" +#include "mozilla/dom/DOMJSProxyHandler.h" +#include "mozilla/dom/RegisterBindings.h" +#include "nsIMemoryReporter.h" +#include "nsTHashtable.h" + +namespace mozilla { +namespace dom { + +struct MOZ_STACK_CLASS WebIDLNameTableKey +{ + explicit WebIDLNameTableKey(JSFlatString* aJSString) + : mLength(js::GetFlatStringLength(aJSString)) + { + mNogc.emplace(); + JSLinearString* jsString = js::FlatStringToLinearString(aJSString); + if (js::LinearStringHasLatin1Chars(jsString)) { + mLatin1String = reinterpret_cast<const char*>( + js::GetLatin1LinearStringChars(*mNogc, jsString)); + mTwoBytesString = nullptr; + mHash = mLatin1String ? HashString(mLatin1String, mLength) : 0; + } else { + mLatin1String = nullptr; + mTwoBytesString = js::GetTwoByteLinearStringChars(*mNogc, jsString); + mHash = mTwoBytesString ? HashString(mTwoBytesString, mLength) : 0; + } + } + explicit WebIDLNameTableKey(const char* aString, size_t aLength) + : mLatin1String(aString), + mTwoBytesString(nullptr), + mLength(aLength), + mHash(HashString(aString, aLength)) + { + MOZ_ASSERT(aString[aLength] == '\0'); + } + + Maybe<JS::AutoCheckCannotGC> mNogc; + const char* mLatin1String; + const char16_t* mTwoBytesString; + size_t mLength; + uint32_t mHash; +}; + +struct WebIDLNameTableEntry : public PLDHashEntryHdr +{ + typedef const WebIDLNameTableKey& KeyType; + typedef const WebIDLNameTableKey* KeyTypePointer; + + explicit WebIDLNameTableEntry(KeyTypePointer aKey) + : mNameOffset(0), + mNameLength(0), + mDefine(nullptr), + mEnabled(nullptr) + {} + WebIDLNameTableEntry(WebIDLNameTableEntry&& aEntry) + : mNameOffset(aEntry.mNameOffset), + mNameLength(aEntry.mNameLength), + mDefine(aEntry.mDefine), + mEnabled(aEntry.mEnabled) + {} + ~WebIDLNameTableEntry() + {} + + bool KeyEquals(KeyTypePointer aKey) const + { + if (mNameLength != aKey->mLength) { + return false; + } + + const char* name = WebIDLGlobalNameHash::sNames + mNameOffset; + + if (aKey->mLatin1String) { + return PodEqual(aKey->mLatin1String, name, aKey->mLength); + } + + return nsCharTraits<char16_t>::compareASCII(aKey->mTwoBytesString, name, + aKey->mLength) == 0; + } + + static KeyTypePointer KeyToPointer(KeyType aKey) + { + return &aKey; + } + + static PLDHashNumber HashKey(KeyTypePointer aKey) + { + return aKey->mHash; + } + + enum { ALLOW_MEMMOVE = true }; + + uint16_t mNameOffset; + uint16_t mNameLength; + WebIDLGlobalNameHash::DefineGlobalName mDefine; + // May be null if enabled unconditionally + WebIDLGlobalNameHash::ConstructorEnabled* mEnabled; +}; + +static nsTHashtable<WebIDLNameTableEntry>* sWebIDLGlobalNames; + +class WebIDLGlobalNamesHashReporter final : public nsIMemoryReporter +{ + MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) + + ~WebIDLGlobalNamesHashReporter() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + int64_t amount = + sWebIDLGlobalNames ? + sWebIDLGlobalNames->ShallowSizeOfIncludingThis(MallocSizeOf) : 0; + + MOZ_COLLECT_REPORT( + "explicit/dom/webidl-globalnames", KIND_HEAP, UNITS_BYTES, amount, + "Memory used by the hash table for WebIDL's global names."); + + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(WebIDLGlobalNamesHashReporter, nsIMemoryReporter) + +/* static */ +void +WebIDLGlobalNameHash::Init() +{ + sWebIDLGlobalNames = new nsTHashtable<WebIDLNameTableEntry>(sCount); + RegisterWebIDLGlobalNames(); + + RegisterStrongMemoryReporter(new WebIDLGlobalNamesHashReporter()); +} + +/* static */ +void +WebIDLGlobalNameHash::Shutdown() +{ + delete sWebIDLGlobalNames; +} + +/* static */ +void +WebIDLGlobalNameHash::Register(uint16_t aNameOffset, uint16_t aNameLength, + DefineGlobalName aDefine, + ConstructorEnabled* aEnabled) +{ + const char* name = sNames + aNameOffset; + WebIDLNameTableKey key(name, aNameLength); + WebIDLNameTableEntry* entry = sWebIDLGlobalNames->PutEntry(key); + entry->mNameOffset = aNameOffset; + entry->mNameLength = aNameLength; + entry->mDefine = aDefine; + entry->mEnabled = aEnabled; +} + +/* static */ +void +WebIDLGlobalNameHash::Remove(const char* aName, uint32_t aLength) +{ + WebIDLNameTableKey key(aName, aLength); + sWebIDLGlobalNames->RemoveEntry(key); +} + +/* static */ +bool +WebIDLGlobalNameHash::DefineIfEnabled(JSContext* aCx, + JS::Handle<JSObject*> aObj, + JS::Handle<jsid> aId, + JS::MutableHandle<JS::PropertyDescriptor> aDesc, + bool* aFound) +{ + MOZ_ASSERT(JSID_IS_STRING(aId), "Check for string id before calling this!"); + + const WebIDLNameTableEntry* entry; + { + WebIDLNameTableKey key(JSID_TO_FLAT_STRING(aId)); + // Rooting analysis thinks nsTHashtable<...>::GetEntry may GC because it + // ends up calling through PLDHashTableOps' matchEntry function pointer, but + // we know WebIDLNameTableEntry::KeyEquals can't cause a GC. + JS::AutoSuppressGCAnalysis suppress; + entry = sWebIDLGlobalNames->GetEntry(key); + } + + if (!entry) { + *aFound = false; + return true; + } + + *aFound = true; + + ConstructorEnabled* checkEnabledForScope = entry->mEnabled; + // We do the enabled check on the current compartment 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. + JS::Rooted<JSObject*> global(aCx, + js::CheckedUnwrap(aObj, /* 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<nsGlobalWindow*> win; + MOZ_ASSERT(NS_SUCCEEDED(UNWRAP_OBJECT(Window, &temp, win))); +#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*> interfaceObject(aCx); + { + JSAutoCompartment ac(aCx, global); + interfaceObject = entry->mDefine(aCx, global, aId, false); + } + if (NS_WARN_IF(!interfaceObject)) { + return Throw(aCx, NS_ERROR_FAILURE); + } + if (!JS_WrapObject(aCx, &interfaceObject)) { + return Throw(aCx, NS_ERROR_FAILURE); + } + + FillPropertyDescriptor(aDesc, aObj, 0, JS::ObjectValue(*interfaceObject)); + return true; + } + + JS::Rooted<JSObject*> interfaceObject(aCx, + entry->mDefine(aCx, aObj, aId, 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. It + // doesn't matter what we pass for the "readonly" argument here. + FillPropertyDescriptor(aDesc, aObj, JS::UndefinedValue(), false); + + return true; +} + +/* static */ +bool +WebIDLGlobalNameHash::MayResolve(jsid aId) +{ + WebIDLNameTableKey key(JSID_TO_FLAT_STRING(aId)); + // Rooting analysis thinks nsTHashtable<...>::Contains may GC because it ends + // up calling through PLDHashTableOps' matchEntry function pointer, but we + // know WebIDLNameTableEntry::KeyEquals can't cause a GC. + JS::AutoSuppressGCAnalysis suppress; + return sWebIDLGlobalNames->Contains(key); +} + +/* static */ +void +WebIDLGlobalNameHash::GetNames(JSContext* aCx, JS::Handle<JSObject*> aObj, + nsTArray<nsString>& aNames) +{ + for (auto iter = sWebIDLGlobalNames->Iter(); !iter.Done(); iter.Next()) { + const WebIDLNameTableEntry* entry = iter.Get(); + if (!entry->mEnabled || entry->mEnabled(aCx, aObj)) { + AppendASCIItoUTF16(nsDependentCString(sNames + entry->mNameOffset, + entry->mNameLength), + *aNames.AppendElement()); + } + } +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/bindings/WebIDLGlobalNameHash.h b/dom/bindings/WebIDLGlobalNameHash.h new file mode 100644 index 000000000..bbe015395 --- /dev/null +++ b/dom/bindings/WebIDLGlobalNameHash.h @@ -0,0 +1,70 @@ +/* -*- 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" + +namespace mozilla { +namespace dom { + +struct WebIDLNameTableEntry; + +class WebIDLGlobalNameHash +{ +public: + static void Init(); + static void Shutdown(); + + typedef JSObject* + (*DefineGlobalName)(JSContext* cx, JS::Handle<JSObject*> global, + JS::Handle<jsid> id, bool defineOnGlobal); + + // 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. + // This is a typedef for the function type itself, not the function + // pointer, so it's more obvious that pointers to a ConstructorEnabled + // can be null. + typedef bool + (ConstructorEnabled)(JSContext* cx, JS::Handle<JSObject*> obj); + + static void Register(uint16_t aNameOffset, uint16_t aNameLength, + DefineGlobalName aDefine, ConstructorEnabled* aEnabled); + + static void Remove(const char* aName, uint32_t aLength); + + // 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<JS::PropertyDescriptor> aDesc, + bool* aFound); + + static bool MayResolve(jsid aId); + + static void GetNames(JSContext* aCx, JS::Handle<JSObject*> aObj, + nsTArray<nsString>& aNames); + +private: + friend struct WebIDLNameTableEntry; + + // The total number of names that we will add to the hash. + // The value of sCount is generated by Codegen.py in RegisterBindings.cpp. + static const uint32_t sCount; + + // The names that will be registered in the hash, concatenated as one big + // string with \0 as a separator between names. + // The value of sNames is generated by Codegen.py in RegisterBindings.cpp. + static const char sNames[]; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_WebIDLGlobalNameHash_h__ diff --git a/dom/bindings/XrayExpandoClass.h b/dom/bindings/XrayExpandoClass.h new file mode 100644 index 000000000..af5c1e9ba --- /dev/null +++ b/dom/bindings/XrayExpandoClass.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 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 { +namespace dom { + +extern const JSClass DefaultXrayExpandoObjectClass; + +} // namespace mozilla +} // namespace 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 000000000..af46069c7 --- /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 000000000..4b80242ec --- /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 000000000..84d6dbc08 --- /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, false); + +</script> diff --git a/dom/bindings/crashtests/822340-1.html b/dom/bindings/crashtests/822340-1.html new file mode 100644 index 000000000..4c8f6ae46 --- /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 000000000..e938c91aa --- /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 000000000..c565ad00f --- /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 000000000..5008e5739 --- /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 000000000..565a729c4 --- /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 000000000..1b31775a9 --- /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 000000000..768871ad9 --- /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 000000000..dedb4dd4d --- /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 000000000..7f20085fe --- /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 000000000..3b517dd88 --- /dev/null +++ b/dom/bindings/crashtests/crashtests.list @@ -0,0 +1,12 @@ +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 diff --git a/dom/bindings/docs/index.rst b/dom/bindings/docs/index.rst new file mode 100644 index 000000000..f6fedbd16 --- /dev/null +++ b/dom/bindings/docs/index.rst @@ -0,0 +1,123 @@ +.. _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``, + 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 000000000..eec68c907 --- /dev/null +++ b/dom/bindings/mach_commands.py @@ -0,0 +1,55 @@ +# 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 absolute_import, unicode_literals + +import os +import sys + +from mach.decorators import ( + CommandArgument, + CommandProvider, + Command, +) + +from mozbuild.base import MachCommandBase + +def get_test_parser(): + import runtests + return runtests.get_parser + +@CommandProvider +class WebIDLProvider(MachCommandBase): + @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(self, interface): + from mozwebidlcodegen import BuildSystemWebIDL + + manager = self._spawn(BuildSystemWebIDL).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(self, **kwargs): + sys.path.insert(0, os.path.join(self.topsrcdir, 'other-licenses', + 'ply')) + + # Make sure we drop our cached grammar bits in the objdir, not + # wherever we happen to be running from. + os.chdir(self.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, self.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 000000000..fadaac69b --- /dev/null +++ b/dom/bindings/moz.build @@ -0,0 +1,161 @@ +# -*- 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/. + +TEST_DIRS += ['test'] + +EXPORTS.ipc += [ + 'ErrorIPCUtils.h', +] + +EXPORTS.mozilla += [ + 'ErrorResult.h', + 'RootedOwningNonNull.h', + 'RootedRefPtr.h', +] + +EXPORTS.mozilla.dom += [ + 'AtomList.h', + 'BindingDeclarations.h', + 'BindingUtils.h', + 'CallbackFunction.h', + 'CallbackInterface.h', + 'CallbackObject.h', + 'Date.h', + 'DOMJSClass.h', + 'DOMJSProxyHandler.h', + 'DOMString.h', + 'Errors.msg', + 'Exceptions.h', + 'FakeString.h', + 'IterableIterator.h', + 'JSSlots.h', + 'MozMap.h', + 'NonRefcountedDOMObject.h', + 'Nullable.h', + 'PrimitiveConversions.h', + 'RootedDictionary.h', + 'SimpleGlobalObject.h', + 'StructuredClone.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/webspeech/recognition', + '/dom/svg', + '/dom/workers', + '/dom/xbl', + '/dom/xml', + '/dom/xslt/base', + '/dom/xslt/xpath', + '/dom/xul', + '/js/xpconnect/src', + '/js/xpconnect/wrappers', + '/layout/generic', + '/layout/style', + '/layout/xul/tree', + '/media/mtransport', + '/media/webrtc/', + '/media/webrtc/signaling/src/common/time_profiling', + '/media/webrtc/signaling/src/peerconnection', +] + +UNIFIED_SOURCES += [ + 'BindingUtils.cpp', + 'CallbackInterface.cpp', + 'CallbackObject.cpp', + 'Date.cpp', + 'DOMJSProxyHandler.cpp', + 'Exceptions.cpp', + 'IterableIterator.cpp', + 'SimpleGlobalObject.cpp', + 'ToJSValue.cpp', + 'WebIDLGlobalNameHash.cpp', +] + +SOURCES += [ + 'StructuredClone.cpp', +] + +# Tests for maplike and setlike require bindings to be built, which means they +# must be included in libxul. This breaks the "no test classes are exported" +# rule stated in the test/ directory, but it's the only way this will work. +# Test classes are only built in debug mode, and all tests requiring use of +# them are only run in debug mode. +if CONFIG['MOZ_DEBUG']: + EXPORTS.mozilla.dom += [ + "test/TestFunctions.h", + "test/TestInterfaceIterableDouble.h", + "test/TestInterfaceIterableDoubleUnion.h", + "test/TestInterfaceIterableSingle.h", + "test/TestInterfaceMaplike.h", + "test/TestInterfaceMaplikeObject.h", + "test/TestInterfaceSetlike.h", + "test/TestInterfaceSetlikeNode.h" + ] + UNIFIED_SOURCES += [ + "test/TestFunctions.cpp", + "test/TestInterfaceIterableDouble.cpp", + "test/TestInterfaceIterableDoubleUnion.cpp", + "test/TestInterfaceIterableSingle.cpp", + "test/TestInterfaceMaplike.cpp", + "test/TestInterfaceMaplikeObject.cpp", + "test/TestInterfaceSetlike.cpp", + "test/TestInterfaceSetlikeNode.cpp", + ] + +include('/ipc/chromium/chromium-config.mozbuild') + +if CONFIG['MOZ_AUDIO_CHANNEL_MANAGER']: + LOCAL_INCLUDES += [ + '/dom/system/gonk', + ] + +FINAL_LIBRARY = 'xul' + +SPHINX_TREES['webidl'] = 'docs' +SPHINX_PYTHON_PACKAGE_DIRS += ['mozwebidlcodegen'] + +if CONFIG['MOZ_BUILD_APP'] in ['browser', 'mobile/android', 'xulrunner']: + # This is needed for Window.webidl + DEFINES['HAVE_SIDEBAR'] = True + + +PYTHON_UNIT_TESTS += [ + 'mozwebidlcodegen/test/test_mozwebidlcodegen.py', +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] + +if CONFIG['COMPILE_ENVIRONMENT']: + GENERATED_FILES += ['CSS2Properties.webidl'] + css_props = GENERATED_FILES['CSS2Properties.webidl'] + css_props.script = 'GenerateCSS2PropertiesWebIDL.py:generate' + css_props.inputs = [ + '/dom/webidl/CSS2Properties.webidl.in', + '/layout/style/PythonCSSProps.h', + ] diff --git a/dom/bindings/mozwebidlcodegen/__init__.py b/dom/bindings/mozwebidlcodegen/__init__.py new file mode 100644 index 000000000..b6e351e52 --- /dev/null +++ b/dom/bindings/mozwebidlcodegen/__init__.py @@ -0,0 +1,583 @@ +# 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. + +from __future__ import unicode_literals + +import errno +import hashlib +import json +import logging +import os + +from copy import deepcopy + +from mach.mixin.logging import LoggingMixin + +from mozbuild.base import MozbuildObject +from mozbuild.makeutil import Makefile +from mozbuild.pythonutil import iter_modules_in_path +from mozbuild.util import FileAvoidWrite + +import mozpack.path as mozpath + +# 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. + """ + + VERSION = 1 + + 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']) + + 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']) + + 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 = { + 'GeneratedAtomList.h', + 'GeneratedEventList.h', + 'PrototypeList.h', + 'RegisterBindings.h', + 'RegisterWorkerBindings.h', + 'RegisterWorkerDebuggerBindings.h', + 'RegisterWorkletBindings.h', + 'ResolveSystemBinding.h', + 'UnionConversions.h', + 'UnionTypes.h', + } + + # Global parser derived definition files. + GLOBAL_DEFINE_FILES = { + 'RegisterBindings.cpp', + 'RegisterWorkerBindings.cpp', + 'RegisterWorkerDebuggerBindings.cpp', + 'RegisterWorkletBindings.cpp', + 'ResolveSystemBinding.cpp', + 'UnionTypes.cpp', + 'PrototypeList.cpp', + } + + def __init__(self, config_path, 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._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 open(state_path, 'rb') 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 + + # 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(global_hashes.keys()) + codegen_rule.add_dependencies(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 %s" % 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) + + for path in sorted(self._input_paths): + with open(path, 'rb') as fh: + data = fh.read() + hashes[path] = hashlib.sha1(data).hexdigest() + parser.parse(data, path) + + self._parser_results = parser.finish() + self._config = Configuration(self._config_path, 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']) + + # 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 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 open(self._state_path, 'wb') 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, topobjdir, dist_dir): + """Create a WebIDLCodegenManager for use by the build system.""" + src_dir = os.path.join(topsrcdir, 'dom', 'bindings') + obj_dir = os.path.join(topobjdir, 'dom', 'bindings') + + with open(os.path.join(obj_dir, 'file-lists.json'), 'rb') 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'), + 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='codegen.pp', + ) + + +class BuildSystemWebIDL(MozbuildObject): + @property + def manager(self): + if not hasattr(self, '_webidl_manager'): + self._webidl_manager = create_build_system_manager( + self.topsrcdir, self.topobjdir, self.distdir) + + return self._webidl_manager diff --git a/dom/bindings/mozwebidlcodegen/test/Child.webidl b/dom/bindings/mozwebidlcodegen/test/Child.webidl new file mode 100644 index 000000000..f7d0a76c9 --- /dev/null +++ b/dom/bindings/mozwebidlcodegen/test/Child.webidl @@ -0,0 +1,3 @@ +interface Child : Parent { + void ChildBaz(); +}; diff --git a/dom/bindings/mozwebidlcodegen/test/ExampleBinding.webidl b/dom/bindings/mozwebidlcodegen/test/ExampleBinding.webidl new file mode 100644 index 000000000..34794993f --- /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 000000000..423f364ae --- /dev/null +++ b/dom/bindings/mozwebidlcodegen/test/Parent.webidl @@ -0,0 +1,3 @@ +interface Parent { + void MethodFoo(); +}; diff --git a/dom/bindings/mozwebidlcodegen/test/TestEvent.webidl b/dom/bindings/mozwebidlcodegen/test/TestEvent.webidl new file mode 100644 index 000000000..db0856291 --- /dev/null +++ b/dom/bindings/mozwebidlcodegen/test/TestEvent.webidl @@ -0,0 +1,13 @@ +interface EventTarget { + void addEventListener(); +}; + +interface Event {}; + +callback EventHandlerNonNull = any (Event event); +typedef EventHandlerNonNull? EventHandler; + +[NoInterfaceObject] +interface TestEvent : EventTarget { + attribute EventHandler onfoo; +}; diff --git a/dom/bindings/mozwebidlcodegen/test/test_mozwebidlcodegen.py b/dom/bindings/mozwebidlcodegen/test/test_mozwebidlcodegen.py new file mode 100644 index 000000000..ce3315e88 --- /dev/null +++ b/dom/bindings/mozwebidlcodegen/test/test_mozwebidlcodegen.py @@ -0,0 +1,298 @@ +# 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 unicode_literals + +import imp +import json +import os +import shutil +import sys +import tempfile +import unittest + +import mozpack.path as mozpath + +from mozwebidlcodegen import ( + WebIDLCodegenManager, + WebIDLCodegenManagerState, +) + +from mozfile import NamedTemporaryFile + +from mozunit import ( + MockedOpen, + main, +) + + +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, + 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 open(p, 'wb') 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 open(manager._state_path, 'rb') as fh: + state = json.load(fh) + self.assertEqual(state['version'], 1) + 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'], 'c41527cad3bc161fa6e7909e48fa11f9eca0468b') + + 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 = open(child_path, 'rb').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 = open(parent_path, 'rb').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 open(fake_path, 'wt') 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/parser/README b/dom/bindings/parser/README new file mode 100644 index 000000000..94b64b884 --- /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 000000000..7ac589937 --- /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 000000000..f668d7d62 --- /dev/null +++ b/dom/bindings/parser/WebIDL.py @@ -0,0 +1,6874 @@ +# 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. """ + +from ply import lex, yacc +import re +import os +import traceback +import math +import string +from collections import defaultdict + +# 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 + + +# Magic for creating enums +def M_add_class_attribs(attribs, start): + def foo(name, bases, dict_): + for v, k in enumerate(attribs): + dict_[k] = start + v + assert 'length' not in dict_ + dict_['length'] = start + len(attribs) + return type(name, bases, dict_) + return foo + + +def enum(*names, **kw): + if len(kw) == 1: + base = kw['base'].__class__ + start = base.length + else: + assert len(kw) == 0 + base = object + start = 0 + + class Foo(base): + __metaclass__ = M_add_class_attribs(names, start) + + def __setattr__(self, name, value): # this makes it read-only + raise NotImplementedError + return Foo() + + +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 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) + self.primaryGlobalAttr = None + self.primaryGlobalName = None + + def __str__(self): + return self.QName() + + def QName(self): + if self._name: + return self._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 + + 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 NamedConstructors and we need to + # detect conflicts in those across interfaces. See also the comment in + # IDLInterface.addExtendedAttributes for "NamedConstructor". + if (isinstance(originalObject, IDLMethod) and + isinstance(newObject, IDLMethod)): + return originalObject.addOverload(newObject) + + # Default to throwing, derived classes can override. + conflictdesc = "\n\t%s at %s\n\t%s at %s" % (originalObject, + originalObject.location, + newObject, + newObject.location) + + raise 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) + + +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 name != "__content" and not allowDoubleUnderscore: + raise WebIDLError("Identifiers beginning with __ are reserved", + [location]) + if name[0] == '_' and not allowDoubleUnderscore: + name = name[1:] + # TODO: Bug 872377, Restore "toJSON" to below list. + # We sometimes need custom serialization, so allow toJSON for now. + 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) + + self.treatNullAs = "Default" + + def resolve(self, parentScope): + assert isinstance(parentScope, IDLScope) + assert isinstance(self.identifier, IDLUnresolvedIdentifier) + self.identifier.resolve(parentScope, self) + + def checkForStringHandlingExtendedAttributes(self, attrs, + isDictionaryMember=False, + isOptional=False): + """ + A helper function to deal with TreatNullAs. Returns the list + of attrs it didn't handle itself. + """ + assert isinstance(self, IDLArgument) or isinstance(self, IDLAttribute) + unhandledAttrs = list() + for attr in attrs: + if not attr.hasValue(): + unhandledAttrs.append(attr) + continue + + identifier = attr.identifier() + value = attr.value() + if identifier == "TreatNullAs": + if not self.type.isDOMString() or self.type.nullable(): + raise WebIDLError("[TreatNullAs] is only allowed on " + "arguments or attributes whose type is " + "DOMString", + [self.location]) + if isDictionaryMember: + raise WebIDLError("[TreatNullAs] is not allowed for " + "dictionary members", [self.location]) + if value != 'EmptyString': + raise WebIDLError("[TreatNullAs] must take the identifier " + "'EmptyString', not '%s'" % value, + [self.location]) + self.treatNullAs = value + else: + unhandledAttrs.append(attr) + + return unhandledAttrs + + +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: + 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 + + # 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]) + + if len(self._exposureGlobalNames) == 0: + self._exposureGlobalNames.add(scope.primaryGlobalName) + + globalNameSetToExposureSet(scope, self._exposureGlobalNames, + self.exposureSet) + + def isExposedInWindow(self): + return 'Window' in self.exposureSet + + def isExposedOnMainThread(self): + return (self.isExposedInWindow() or + self.isExposedInSystemGlobals()) + + 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 isExposedInSystemGlobals(self): + return 'BackstagePass' in self.exposureSet + + 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 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, IDLExposureMixins): + def __init__(self, location, parentScope, identifier): + assert isinstance(identifier, IDLUnresolvedIdentifier) + assert isinstance(parentScope, IDLScope) + self.parent = None + IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier) + IDLExposureMixins.__init__(self, location) + IDLObjectWithIdentifier.resolve(self, parentScope) + + def finish(self, scope): + IDLExposureMixins.finish(self, scope) + pass + + def validate(self): + pass + + def isIteratorInterface(self): + return False + + def isExternal(self): + return True + + def isInterface(self): + return True + + def isConsequential(self): + return False + + def addExtendedAttributes(self, attrs): + assert len(attrs) == 0 + + def resolve(self, parentScope): + pass + + def getJSImplementation(self): + return None + + def isJSImplemented(self): + return False + + def isProbablyShortLivingObject(self): + return False + + def isNavigatorProperty(self): + return False + + def _getDependentObjects(self): + return set() + + +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.addPartialInterface(self) + + def addExtendedAttributes(self, attrs): + for attr in attrs: + identifier = attr.identifier() + + if identifier in ["Constructor", "NamedConstructor"]: + 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]) + + +class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins): + def __init__(self, location, parentScope, name, parent, members, + isKnownNonPartial): + assert isinstance(parentScope, IDLScope) + assert isinstance(name, IDLUnresolvedIdentifier) + assert isKnownNonPartial or not parent + assert isKnownNonPartial or len(members) == 0 + + self.parent = None + self._callback = False + self._finished = False + self.members = [] + self.maplikeOrSetlikeOrIterable = None + self._partialInterfaces = [] + self._extendedAttrDict = {} + # namedConstructors needs deterministic ordering because bindings code + # outputs the constructs in the order that namedConstructors enumerates + # them. + self.namedConstructors = list() + self.implementedInterfaces = set() + self._consequential = False + self._isKnownNonPartial = False + # self.interfacesBasedOnSelf is the set of interfaces that inherit from + # self or have self as a consequential interface, including self itself. + # Used for distinguishability checking. + self.interfacesBasedOnSelf = set([self]) + # self.interfacesImplementingSelf is the set of interfaces that directly + # have self as a consequential interface + self.interfacesImplementingSelf = set() + self._hasChildInterfaces = False + self._isOnGlobalProtoChain = 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 + + IDLObjectWithScope.__init__(self, location, parentScope, name) + IDLExposureMixins.__init__(self, location) + + if isKnownNonPartial: + self.setNonPartial(location, parent, members) + + def ctor(self): + identifier = IDLUnresolvedIdentifier(self.location, "constructor", + allowForbidden=True) + try: + return self._lookupIdentifier(identifier) + except: + return None + + def isIterable(self): + return (self.maplikeOrSetlikeOrIterable and + self.maplikeOrSetlikeOrIterable.isIterable()) + + def isIteratorInterface(self): + return self.iterableInterface is not None + + def resolveIdentifierConflict(self, scope, identifier, originalObject, newObject): + assert isinstance(scope, IDLScope) + assert isinstance(originalObject, IDLInterfaceMember) + assert isinstance(newObject, IDLInterfaceMember) + + retval = IDLScope.resolveIdentifierConflict(self, scope, identifier, + originalObject, newObject) + + # Might be a ctor, which isn't in self.members + if newObject in self.members: + self.members.remove(newObject) + return retval + + def finish(self, scope): + if self._finished: + return + + self._finished = True + + if not self._isKnownNonPartial: + raise WebIDLError("Interface %s does not have a non-partial " + "declaration" % self.identifier.name, + [self.location]) + + IDLExposureMixins.finish(self, scope) + + # Now go ahead and merge in our partial interfaces. + for partial in self._partialInterfaces: + partial.finish(scope) + self.addExtendedAttributes(partial.propagatedExtendedAttrs) + self.members.extend(partial.members) + + # 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(): + # 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, self.isJSImplemented()) + + # Now that we've merged in our partial interfaces, 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 that defined them" + # and "partial interfaces default to interface they're a partial for" + # rules from the spec. + for m in self.members: + # If m, or the partial interface 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) + + 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]) + assert not parent or isinstance(parent, IDLInterface) + + 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] or [PrimaryGlobal] must not + # have anything inherit from them + if (self.parent.getExtendedAttribute("Global") or + self.parent.getExtendedAttribute("PrimaryGlobal")): + # 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 or inherit from + # anything that has consequential interfaces. + # XXXbz Can non-callbacks inherit from callbacks? Spec issue pending. + # XXXbz Can callbacks have consequential interfaces? 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 [NoInterfaceObject] interfaces. + if (self.parent.getExtendedAttribute("NoInterfaceObject") and + not self.getExtendedAttribute("NoInterfaceObject")): + raise WebIDLError("Interface %s does not have " + "[NoInterfaceObject] 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 iface in self.implementedInterfaces: + iface.finish(scope) + + cycleInGraph = self.findInterfaceLoopPoint(self) + if cycleInGraph: + raise WebIDLError("Interface %s has itself as ancestor or " + "implemented interface" % self.identifier.name, + [self.location, cycleInGraph.location]) + + if self.isCallback(): + # "implements" should have made sure we have no + # consequential interfaces. + assert len(self.getConsequentialInterfaces()) == 0 + # And that we're not consequential. + assert not self.isConsequential() + + # Now resolve() and finish() our members before importing the + # ones from our implemented interfaces. + + # 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 member has larger exposure set " + "than the interface itself", + [member.location, self.location]) + + ctor = self.ctor() + if ctor is not None: + assert len(ctor._exposureGlobalNames) == 0 + ctor._exposureGlobalNames.update(self._exposureGlobalNames) + ctor.finish(scope) + + for ctor in self.namedConstructors: + 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) + + # Import everything from our consequential interfaces into + # self.members. Sort our consequential interfaces by name + # just so we have a consistent order. + for iface in sorted(self.getConsequentialInterfaces(), + cmp=cmp, + key=lambda x: x.identifier.name): + # Flag the interface as being someone's consequential interface + iface.setIsConsequentialInterfaceOf(self) + # Verify that we're not exposed somewhere where iface is not exposed + if not self.exposureSet.issubset(iface.exposureSet): + raise WebIDLError("Interface %s is exposed in globals where its " + "consequential interface %s is not exposed." % + (self.identifier.name, iface.identifier.name), + [self.location, iface.location]) + + # If we have a maplike or setlike, and the consequential interface + # also does, throw an error. + if iface.maplikeOrSetlikeOrIterable and self.maplikeOrSetlikeOrIterable: + raise WebIDLError("Maplike/setlike/iterable interface %s cannot have " + "maplike/setlike/iterable interface %s as a " + "consequential interface" % + (self.identifier.name, + iface.identifier.name), + [self.maplikeOrSetlikeOrIterable.location, + iface.maplikeOrSetlikeOrIterable.location]) + additionalMembers = iface.originalMembers + for additionalMember in additionalMembers: + for member in self.members: + if additionalMember.identifier.name == member.identifier.name: + raise WebIDLError( + "Multiple definitions of %s on %s coming from 'implements' statements" % + (member.identifier.name, self), + [additionalMember.location, member.location]) + self.members.extend(additionalMembers) + iface.interfacesImplementingSelf.add(self) + + 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]) + for ancestorConsequential in ancestor.getConsequentialInterfaces(): + ancestorConsequential.interfacesBasedOnSelf.add(self) + + # Deal with interfaces marked [Unforgeable], 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("Unforgeable"): + # 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("Unforgeable interface %s does not have a " + "stringifier" % self.identifier.name, + [self.location]) + + for m in self.members: + if ((m.isMethod() and m.isJsonifier()) or + m.identifier.name == "toJSON"): + raise WebIDLError("Unforgeable interface %s has a " + "jsonifier 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("Unforgeable 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.isUnforgeable() and + not hasattr(member, "originatingInterface")): + member.originatingInterface = self + + # 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.isMaplikeOrSetlike()): + 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 [Unforgeable] attributes on + # our ancestor interfaces. We don't have to worry about + # consequential interfaces 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.isUnforgeable()): + 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 [Unforgeable] " + "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,creator,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/creator 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.isCreator(): + memberType = "creators" + elif member.isDeleter(): + memberType = "deleters" + elif member.isStringifier(): + memberType = "stringifiers" + elif member.isJsonifier(): + memberType = "jsonifiers" + elif member.isLegacycaller(): + memberType = "legacycallers" + else: + continue + + if (memberType != "stringifiers" and memberType != "legacycallers" and + memberType != "jsonifiers"): + 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, creators, or deleters + for memberType in ["setter", "creator", "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 [OverrideBuiltins] + if self.getExtendedAttribute("OverrideBuiltins"): + raise WebIDLError("Interface with [Global] also has " + "[OverrideBuiltins]", + [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 [OverrideBuiltins] + if parent.getExtendedAttribute("OverrideBuiltins"): + raise WebIDLError("Interface with [Global] inherits from " + "interface with [OverrideBuiltins]", + [self.location, parent.location]) + parent._isOnGlobalProtoChain = True + parent = parent.parent + + def validate(self): + # We don't support consequential unforgeable interfaces. Need to check + # this here, because in finish() an interface might not know yet that + # it's consequential. + if self.getExtendedAttribute("Unforgeable") and self.isConsequential(): + raise WebIDLError( + "%s is an unforgeable consequential interface" % + self.identifier.name, + [self.location] + + list(i.location for i in + (self.interfacesBasedOnSelf - {self}))) + + # We also don't support inheriting from unforgeable interfaces. + if self.getExtendedAttribute("Unforgeable") 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) + + 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("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.isUnforgeable(): + raise WebIDLError("[Alias] must not be used on an " + "[Unforgeable] operation", + [member.location]) + for m in self.members: + if m.identifier.name == alias: + raise WebIDLError("[Alias=%s] has same name as " + "interface member" % alias, + [member.location, m.location]) + if m.isMethod() and m != member and alias in m.aliases: + raise WebIDLError("duplicate [Alias=%s] definitions" % + alias, + [member.location, m.location]) + + if (self.getExtendedAttribute("Pref") and + self._exposureGlobalNames != set([self.parentScope.primaryGlobalName])): + raise WebIDLError("[Pref] used on an interface that is not %s-only" % + self.parentScope.primaryGlobalName, + [self.location]) + + # Conditional exposure makes no sense for interfaces with no + # interface object, unless they're navigator properties. + # 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() and + not self.isNavigatorProperty()): + 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]) + + 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]) + else: + assert iterableDecl.isPairIterator() + if indexedGetter: + raise WebIDLError("Interface with pair iterator supports " + "indexed properties", + [self.location, iterableDecl.location, + indexedGetter.location]) + + def isExternal(self): + return False + + def setIsConsequentialInterfaceOf(self, other): + self._consequential = True + self.interfacesBasedOnSelf.add(other) + + def isConsequential(self): + return self._consequential + + 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 consequential interfaces + len(self.getConsequentialInterfaces()) == 0 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") + + def hasInterfacePrototypeObject(self): + return (not self.isCallback() and not self.isNamespace() + and self.getUserData('hasConcreteDescendant', False)) + + def addImplementedInterface(self, implementedInterface): + assert(isinstance(implementedInterface, IDLInterface)) + self.implementedInterfaces.add(implementedInterface) + + 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 getConsequentialInterfaces(self): + assert(self._finished) + # The interfaces we implement directly + consequentialInterfaces = set(self.implementedInterfaces) + + # And their inherited interfaces + for iface in self.implementedInterfaces: + consequentialInterfaces |= set(iface.getInheritedInterfaces()) + + # And now collect up the consequential interfaces of all of those + temp = set() + for iface in consequentialInterfaces: + temp |= iface.getConsequentialInterfaces() + + return consequentialInterfaces | temp + + def findInterfaceLoopPoint(self, otherInterface): + """ + Finds an interface, amongst our ancestors and consequential interfaces, + that inherits from otherInterface or implements otherInterface + directly. 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 + if otherInterface in self.implementedInterfaces: + return self + for iface in self.implementedInterfaces: + loopPoint = iface.findInterfaceLoopPoint(otherInterface) + if loopPoint: + return loopPoint + return None + + def getExtendedAttribute(self, name): + return self._extendedAttrDict.get(name, None) + + def setNonPartial(self, location, parent, members): + assert not parent or isinstance(parent, IDLIdentifierPlaceholder) + if self._isKnownNonPartial: + raise WebIDLError("Two non-partial definitions for the " + "same %s" % + ("interface" if self.isInterface() + else "namespace"), + [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 + assert not self.parent + self.parent = parent + # Put the new members at the beginning + self.members = members + self.members + + def addPartialInterface(self, partial): + assert self.identifier.name == partial.identifier.name + self._partialInterfaces.append(partial) + + 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 isProbablyShortLivingObject(self): + current = self + while current: + if current.getExtendedAttribute("ProbablyShortLivingObject"): + return True + current = current.parent + return False + + def isNavigatorProperty(self): + naviProp = self.getExtendedAttribute("NavigatorProperty") + if not naviProp: + return False + assert len(naviProp) == 1 + assert isinstance(naviProp, list) + assert len(naviProp[0]) != 0 + return True + + def getNavigatorProperty(self): + naviProp = self.getExtendedAttribute("NavigatorProperty") + if not naviProp: + return None + assert len(naviProp) == 1 + assert isinstance(naviProp, list) + assert len(naviProp[0]) != 0 + conditionExtendedAttributes = self._extendedAttrDict.viewkeys() & IDLInterfaceOrNamespace.conditionExtendedAttributes + attr = IDLAttribute(self.location, + IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"), naviProp[0]), + IDLUnresolvedType(self.location, IDLUnresolvedIdentifier(self.location, self.identifier.name)), + True, + extendedAttrDict={ a: self._extendedAttrDict[a] for a in conditionExtendedAttributes }, + navigatorObjectGetter=True) + attr._exposureGlobalNames = self._exposureGlobalNames + # We're abusing Constant a little bit here, because we need Cached. The + # getter will create a new object every time, but we're never going to + # clear the cached value. + extendedAttrs = [ IDLExtendedAttribute(self.location, ("Throws", )), + IDLExtendedAttribute(self.location, ("Cached", )), + IDLExtendedAttribute(self.location, ("Constant", )) ] + attr.addExtendedAttributes(extendedAttrs) + return attr + + def hasChildInterfaces(self): + return self._hasChildInterfaces + + def isOnGlobalProtoChain(self): + return self._isOnGlobalProtoChain + + def _getDependentObjects(self): + deps = set(self.members) + deps.update(self.implementedInterfaces) + if self.parent: + deps.add(self.parent) + return deps + + def hasMembersInSlots(self): + return self._ownMembersInSlots != 0 + + conditionExtendedAttributes = [ "Pref", "ChromeOnly", "Func", "AvailableIn", + "SecureContext", + "CheckAnyPermissions", + "CheckAllPermissions" ] + def isExposedConditionally(self, exclusions=[]): + return any(((not a in exclusions) and self.getExtendedAttribute(a)) for a in self.conditionExtendedAttributes) + +class IDLInterface(IDLInterfaceOrNamespace): + def __init__(self, location, parentScope, name, parent, members, + isKnownNonPartial): + IDLInterfaceOrNamespace.__init__(self, location, parentScope, name, + parent, members, isKnownNonPartial) + + def __str__(self): + return "Interface '%s'" % self.identifier.name + + def isInterface(self): + return True + + 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 == "TreatNonObjectAsNull": + raise WebIDLError("TreatNonObjectAsNull cannot be specified on interfaces", + [attr.location, self.location]) + elif identifier == "NoInterfaceObject": + if not attr.noArguments(): + raise WebIDLError("[NoInterfaceObject] must take no arguments", + [attr.location]) + + if self.ctor(): + raise WebIDLError("Constructor and NoInterfaceObject are incompatible", + [self.location]) + + self._noInterfaceObject = True + elif identifier == "Constructor" or identifier == "NamedConstructor" or identifier == "ChromeConstructor": + if identifier == "Constructor" and not self.hasInterfaceObject(): + raise WebIDLError(str(identifier) + " and NoInterfaceObject are incompatible", + [self.location]) + + if identifier == "NamedConstructor" and not attr.hasValue(): + raise WebIDLError("NamedConstructor must either take an identifier or take a named argument list", + [attr.location]) + + if identifier == "ChromeConstructor" and not self.hasInterfaceObject(): + raise WebIDLError(str(identifier) + " and NoInterfaceObject are incompatible", + [self.location]) + + args = attr.args() if attr.hasArgs() else [] + + if self.identifier.name == "Promise": + promiseType = BuiltinTypes[IDLBuiltinType.Types.any] + else: + promiseType = None + retType = IDLWrapperType(self.location, self, promiseType) + + if identifier == "Constructor" or identifier == "ChromeConstructor": + name = "constructor" + allowForbidden = True + else: + name = attr.value() + allowForbidden = False + + methodIdentifier = IDLUnresolvedIdentifier(self.location, name, + allowForbidden=allowForbidden) + + method = IDLMethod(self.location, methodIdentifier, retType, + args, static=True) + # Constructors are always NewObject and are always + # assumed to be able to throw (since there's no way to + # indicate otherwise) and never have any other + # extended attributes. + method.addExtendedAttributes( + [IDLExtendedAttribute(self.location, ("NewObject",)), + IDLExtendedAttribute(self.location, ("Throws",))]) + if identifier == "ChromeConstructor": + method.addExtendedAttributes( + [IDLExtendedAttribute(self.location, ("ChromeOnly",))]) + + if identifier == "Constructor" or identifier == "ChromeConstructor": + method.resolve(self) + else: + # We need to detect conflicts for NamedConstructors across + # interfaces. We first call resolve on the parentScope, + # which will merge all NamedConstructors 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 NamedConstructor 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 + # NamedConstructors. + newMethod = self.parentScope.lookupIdentifier(method.identifier) + if newMethod == method: + self.namedConstructors.append(method) + elif newMethod not in self.namedConstructors: + raise WebIDLError("NamedConstructor conflicts with a NamedConstructor of a different interface", + [method.location, newMethod.location]) + elif (identifier == "ArrayClass"): + if not attr.noArguments(): + raise WebIDLError("[ArrayClass] must take no arguments", + [attr.location]) + if self.parent: + raise WebIDLError("[ArrayClass] must not be specified on " + "an interface with inherited interfaces", + [attr.location, self.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.globalNames.update(self.globalNames) + for globalName in self.globalNames: + self.parentScope.globalNameMapping[globalName].add(self.identifier.name) + self._isOnGlobalProtoChain = True + elif identifier == "PrimaryGlobal": + if not attr.noArguments(): + raise WebIDLError("[PrimaryGlobal] must take no arguments", + [attr.location]) + if self.parentScope.primaryGlobalAttr is not None: + raise WebIDLError( + "[PrimaryGlobal] specified twice", + [attr.location, + self.parentScope.primaryGlobalAttr.location]) + self.parentScope.primaryGlobalAttr = attr + self.parentScope.primaryGlobalName = self.identifier.name + self.parentScope.globalNames.add(self.identifier.name) + self.parentScope.globalNameMapping[self.identifier.name].add(self.identifier.name) + self._isOnGlobalProtoChain = True + 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 == "OverrideBuiltins" or + identifier == "ChromeOnly" or + identifier == "Unforgeable" or + identifier == "UnsafeInPrerendering" or + identifier == "LegacyEventInit" or + identifier == "ProbablyShortLivingObject" or + identifier == "LegacyUnenumerableNamedProperties" or + identifier == "NonOrdinaryGetPrototypeOf"): + # 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 == "NavigatorProperty" or + identifier == "Func" 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]) + else: + raise WebIDLError("Unknown extended attribute %s on interface" % identifier, + [attr.location]) + + attrlist = attr.listValue() + self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True + + +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": + if not attr.noArguments(): + raise WebIDLError("[%s] must not have arguments" % 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 + + +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) + + 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) + + 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 + self.members.sort(cmp=cmp, 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.isMozMap()): + 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) + + def addExtendedAttributes(self, attrs): + assert len(attrs) == 0 + + def _getDependentObjects(self): + deps = set(self.members) + if (self.parent): + deps.add(self.parent) + return deps + + +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): + assert len(attrs) == 0 + + 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', + 'domstring', + 'bytestring', + 'usvstring', + 'object', + 'date', + 'void', + # Funny stuff + 'interface', + 'dictionary', + 'enum', + 'callback', + 'union', + 'sequence', + 'mozmap' + ) + + def __init__(self, location, name): + IDLObject.__init__(self, location) + self.name = name + self.builtin = False + + def __eq__(self, other): + return other and self.builtin == other.builtin and self.name == other.name + + def __ne__(self, other): + return not self == other + + def __str__(self): + 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 isVoid(self): + return self.name == "Void" + + def isSequence(self): + return False + + def isMozMap(self): + return False + + def isArrayBuffer(self): + return False + + def isArrayBufferView(self): + return False + + def isSharedArrayBuffer(self): + return False + + def isTypedArray(self): + return False + + 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. At the moment, this + only returns true for the types from the TypedArray spec. """ + return self.isInterface() and (self.isArrayBuffer() or + self.isArrayBufferView() or + self.isSharedArrayBuffer() or + self.isTypedArray()) + + def isDictionary(self): + return False + + def isInterface(self): + return False + + def isAny(self): + return self.tag() == IDLType.Tags.any + + def isDate(self): + return self.tag() == IDLType.Tags.date + + 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 isSerializable(self): + return False + + 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 addExtendedAttributes(self, attrs): + assert len(attrs) == 0 + + 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, promiseInnerType=None): + IDLType.__init__(self, location, name) + self._promiseInnerType = promiseInnerType + + def isComplete(self): + return False + + def complete(self, scope): + obj = None + try: + obj = scope._lookupIdentifier(self.name) + except: + raise WebIDLError("Unresolved type '%s'." % self.name, + [self.location]) + + assert obj + if obj.isType(): + print 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) + elif obj.isCallback() and not obj.isInterface(): + assert self.name.name == obj.identifier.name + return IDLCallbackType(self.location, obj) + + if self._promiseInnerType and not self._promiseInnerType.isComplete(): + self._promiseInnerType = self._promiseInnerType.complete(scope) + + name = self.name.resolve(scope, None) + return IDLWrapperType(self.location, obj, self._promiseInnerType) + + def isDistinguishableFrom(self, other): + raise TypeError("Can't tell whether an unresolved type is or is not " + "distinguishable from other things") + + +class IDLParameterizedType(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(IDLParameterizedType): + def __init__(self, location, innerType): + assert not innerType.isVoid() + assert not innerType == BuiltinTypes[IDLBuiltinType.Types.any] + + name = innerType.name + if innerType.isComplete(): + name += "OrNull" + IDLParameterizedType.__init__(self, location, name, innerType) + + def __eq__(self, other): + return isinstance(other, IDLNullableType) and self.inner == other.inner + + def __str__(self): + return self.inner.__str__() + "OrNull" + + 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 isFloat(self): + return self.inner.isFloat() + + def isUnrestricted(self): + return self.inner.isUnrestricted() + + def isInteger(self): + return self.inner.isInteger() + + def isVoid(self): + return False + + def isSequence(self): + return self.inner.isSequence() + + def isMozMap(self): + return self.inner.isMozMap() + + def isArrayBuffer(self): + return self.inner.isArrayBuffer() + + def isArrayBufferView(self): + return self.inner.isArrayBufferView() + + def isSharedArrayBuffer(self): + return self.inner.isSharedArrayBuffer() + + def isTypedArray(self): + return self.inner.isTypedArray() + + def isDictionary(self): + return self.inner.isDictionary() + + def isInterface(self): + return self.inner.isInterface() + + def isPromise(self): + return self.inner.isPromise() + + 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 isSerializable(self): + return self.inner.isSerializable() + + def tag(self): + return self.inner.tag() + + def complete(self, scope): + self.inner = self.inner.complete(scope) + 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]) + + self.name = self.inner.name + "OrNull" + return self + + def isDistinguishableFrom(self, other): + if (other.nullable() or (other.isUnion() and other.hasNullableType) or + other.isDictionary()): + # Can't tell which type null should become + return False + return self.inner.isDistinguishableFrom(other) + + +class IDLSequenceType(IDLParameterizedType): + def __init__(self, location, parameterType): + assert not parameterType.isVoid() + + IDLParameterizedType.__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 __eq__(self, other): + return isinstance(other, IDLSequenceType) and self.inner == other.inner + + def __str__(self): + return self.inner.__str__() + "Sequence" + + def nullable(self): + return False + + def isPrimitive(self): + return False + + def isString(self): + return False + + def isByteString(self): + return False + + def isDOMString(self): + return False + + def isUSVString(self): + return False + + def isVoid(self): + return False + + def isSequence(self): + return True + + def isDictionary(self): + return False + + def isInterface(self): + return False + + def isEnum(self): + return False + + def isSerializable(self): + return self.inner.isSerializable() + + def tag(self): + return IDLType.Tags.sequence + + def complete(self, scope): + 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.isPrimitive() or other.isString() or other.isEnum() or + other.isDate() or other.isInterface() or + other.isDictionary() or + other.isCallback() or other.isMozMap()) + + +class IDLMozMapType(IDLParameterizedType): + def __init__(self, location, parameterType): + assert not parameterType.isVoid() + + IDLParameterizedType.__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 + "MozMap" + + def __eq__(self, other): + return isinstance(other, IDLMozMapType) and self.inner == other.inner + + def __str__(self): + return self.inner.__str__() + "MozMap" + + def isMozMap(self): + return True + + def tag(self): + return IDLType.Tags.mozmap + + def complete(self, scope): + self.inner = self.inner.complete(scope) + self.name = self.inner.name + "MozMap" + 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.isDate() or other.isNonCallbackInterface() or other.isSequence()) + + def isExposedInAllOf(self, exposureSet): + return self.inner.unroll().isExposedInAllOf(exposureSet) + + +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 isVoid(self): + return False + + def isUnion(self): + return True + + def isSerializable(self): + return all(m.isSerializable() 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) + 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] + 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 __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 isVoid(self): + return self.inner.isVoid() + + def isSequence(self): + return self.inner.isSequence() + + def isMozMap(self): + return self.inner.isMozMap() + + def isDictionary(self): + return self.inner.isDictionary() + + def isArrayBuffer(self): + return self.inner.isArrayBuffer() + + def isArrayBufferView(self): + return self.inner.isArrayBufferView() + + def isSharedArrayBuffer(self): + return self.inner.isSharedArrayBuffer() + + 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() + + +class IDLTypedef(IDLObjectWithIdentifier): + def __init__(self, location, parentScope, innerType, name): + identifier = IDLUnresolvedIdentifier(location, name) + IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier) + self.innerType = innerType + + 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): + assert len(attrs) == 0 + + def _getDependentObjects(self): + return self.innerType._getDependentObjects() + + +class IDLWrapperType(IDLType): + def __init__(self, location, inner, promiseInnerType=None): + IDLType.__init__(self, location, inner.identifier.name) + self.inner = inner + self._identifier = inner.identifier + self.builtin = False + assert not promiseInnerType or inner.identifier.name == "Promise" + self._promiseInnerType = promiseInnerType + + 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 nullable(self): + return False + + def isPrimitive(self): + return False + + def isString(self): + return False + + def isByteString(self): + return False + + def isDOMString(self): + return False + + def isUSVString(self): + return False + + def isVoid(self): + return False + + def isSequence(self): + return False + + 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 isPromise(self): + return (isinstance(self.inner, IDLInterface) and + self.inner.identifier.name == "Promise") + + def promiseInnerType(self): + assert self.isPromise() + return self._promiseInnerType + + def isSerializable(self): + if self.isInterface(): + if self.inner.isExternal(): + return False + return any(m.isMethod() and m.isJsonifier() for m in self.inner.members) + elif self.isEnum(): + return True + elif self.isDictionary(): + return all(m.type.isSerializable() for m in self.inner.members) + 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 self.isPromise(): + return False + 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.isPrimitive() or other.isInterface() or other.isObject() or + other.isCallback() or other.isDictionary() or + other.isSequence() or other.isMozMap() or other.isDate()) + if self.isDictionary() and other.nullable(): + return False + if (other.isPrimitive() or other.isString() or other.isEnum() or + other.isDate() 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.isDictionary() or other.isCallback() or + other.isMozMap()): + 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, though ideally we'd only do this when + # exposureSet contains the primary global's name. + return True + if (self.isPromise() and + # Check the internal type + not self.promiseInnerType().unroll().isExposedInAllOf(exposureSet)): + return False + 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 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', + 'domstring', + 'bytestring', + 'usvstring', + 'object', + 'date', + 'void', + # Funny stuff + 'ArrayBuffer', + 'ArrayBufferView', + 'SharedArrayBuffer', + '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.domstring: IDLType.Tags.domstring, + Types.bytestring: IDLType.Tags.bytestring, + Types.usvstring: IDLType.Tags.usvstring, + Types.object: IDLType.Tags.object, + Types.date: IDLType.Tags.date, + Types.void: IDLType.Tags.void, + Types.ArrayBuffer: IDLType.Tags.interface, + Types.ArrayBufferView: IDLType.Tags.interface, + Types.SharedArrayBuffer: 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 + } + + def __init__(self, location, name, type): + IDLType.__init__(self, location, name) + self.builtin = True + self._typeTag = type + + def isPrimitive(self): + return self._typeTag <= IDLBuiltinType.Types.double + + def isBoolean(self): + return self._typeTag == IDLBuiltinType.Types.boolean + + 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) + + 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 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 isSharedArrayBuffer(self): + return self._typeTag == IDLBuiltinType.Types.SharedArrayBuffer + + 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.isSharedArrayBuffer() 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 isSerializable(self): + return self.isPrimitive() or self.isString() or self.isDate() + + 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.isBoolean(): + return (other.isNumeric() or other.isString() or other.isEnum() or + other.isInterface() or other.isObject() or + other.isCallback() or other.isDictionary() or + other.isSequence() or other.isMozMap() or other.isDate()) + if self.isNumeric(): + return (other.isBoolean() or other.isString() or other.isEnum() or + other.isInterface() or other.isObject() or + other.isCallback() or other.isDictionary() or + other.isSequence() or other.isMozMap() or other.isDate()) + if self.isString(): + return (other.isPrimitive() or other.isInterface() or + other.isObject() or + other.isCallback() or other.isDictionary() or + other.isSequence() or other.isMozMap() or other.isDate()) + if self.isAny(): + # Can't tell "any" apart from anything + return False + if self.isObject(): + return other.isPrimitive() or other.isString() or other.isEnum() + if self.isDate(): + return (other.isPrimitive() or other.isString() or other.isEnum() or + other.isInterface() or other.isCallback() or + other.isDictionary() or other.isSequence() or + other.isMozMap()) + if self.isVoid(): + return not other.isVoid() + # Not much else we could be! + assert self.isSpiderMonkeyInterface() + # Like interfaces, but we know we're not a callback + return (other.isPrimitive() or other.isString() or other.isEnum() or + other.isCallback() or other.isDictionary() or + other.isSequence() or other.isMozMap() or other.isDate() 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 + (self.isSharedArrayBuffer() and not other.isSharedArrayBuffer()) 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() + +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.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.object: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Object", + IDLBuiltinType.Types.object), + IDLBuiltinType.Types.date: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Date", + IDLBuiltinType.Types.date), + IDLBuiltinType.Types.void: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Void", + IDLBuiltinType.Types.void), + IDLBuiltinType.Types.ArrayBuffer: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "ArrayBuffer", + IDLBuiltinType.Types.ArrayBuffer), + IDLBuiltinType.Types.ArrayBufferView: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "ArrayBufferView", + IDLBuiltinType.Types.ArrayBufferView), + IDLBuiltinType.Types.SharedArrayBuffer: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "SharedArrayBuffer", + IDLBuiltinType.Types.SharedArrayBuffer), + 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(): + # Allow ByteStrings 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) + + 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.isUnion() and type.hasDictionaryType()) and + not type.isDictionary() 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: + 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 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', + '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.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): + # We better be exposed _somewhere_. + if (len(self._exposureGlobalNames) == 0): + print self.identifier.name + assert len(self._exposureGlobalNames) != 0 + IDLExposureMixins.finish(self, scope) + + def validate(self): + if (self.getExtendedAttribute("Pref") and + self.exposureSet != set([self._globalScope.primaryGlobalName])): + raise WebIDLError("[Pref] used on an interface member that is not " + "%s-only" % self._globalScope.primaryGlobalName, + [self.location]) + + 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" % dependsOn, + [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) + + +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'] + 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 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.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. However, if the operation is static, + # then fail by way of creating the function, which will cause a + # naming conflict, per the spec. + 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: + method.addExtendedAttributes( + [IDLExtendedAttribute(self.location, ("Alias", "@@iterator"))]) + 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=None, scope=None): + 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, isJSImplemented): + """ + 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) + + # void forEach(callback(valueType, keyType), optional any thisArg) + self.addMethod("forEach", members, False, + BuiltinTypes[IDLBuiltinType.Types.void], + self.getForEachArguments()) + + def isValueIterator(self): + return not self.isPairIterator() + + 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, isJSImplemented): + """ + 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()) + + # void forEach(callback(valueType, keyType), thisVal) + self.addMethod("forEach", members, False, BuiltinTypes[IDLBuiltinType.Types.void], + 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: + # void clear() + self.addMethod("clear", members, True, BuiltinTypes[IDLBuiltinType.Types.void], + []) + # boolean delete(keyType key) + self.addMethod("delete", members, True, + BuiltinTypes[IDLBuiltinType.Types.boolean], [getKeyArg()]) + + # Always generate underscored functions (e.g. __add, __clear) for js + # implemented interfaces as convenience functions. + if isJSImplemented: + # void clear() + self.addMethod("clear", members, True, BuiltinTypes[IDLBuiltinType.Types.void], + [], chromeOnly=True) + # boolean delete(keyType key) + self.addMethod("delete", members, True, + BuiltinTypes[IDLBuiltinType.Types.boolean], [getKeyArg()], + chromeOnly=True) + + 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()]) + if isJSImplemented: + self.addMethod("add", members, True, + BuiltinTypes[IDLBuiltinType.Types.object], [getKeyArg()], + chromeOnly=True) + 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()]) + if isJSImplemented: + self.addMethod("set", members, True, BuiltinTypes[IDLBuiltinType.Types.object], + [getKeyArg(), getValueArg()], chromeOnly=True) + +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]) + 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: + 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 == "SecureContext"): + # 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, navigatorObjectGetter=False): + 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.lenientThis = False + self._unforgeable = False + self.stringifier = stringifier + self.enforceRange = False + self.clamp = False + self.slotIndices = None + assert maplikeOrSetlike is None or isinstance(maplikeOrSetlike, IDLMaplikeOrSetlike) + self.maplikeOrSetlike = maplikeOrSetlike + self.dependsOn = "Everything" + self.affects = "Everything" + self.navigatorObjectGetter = navigatorObjectGetter + + 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.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.isMozMap() and not self.getExtendedAttribute("Cached"): + raise WebIDLError("A non-cached attribute cannot be of a MozMap " + "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.isMozMap(): + 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 MozMap " + "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]) + + def validate(self): + def typeContainsChromeOnlyDictionaryMember(type): + if (type.nullable() or + type.isSequence() or + type.isMozMap()): + 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.isMozMap()): + raise WebIDLError("[Frozen] is only allowed on " + "sequence-valued, dictionary-valued, and " + "MozMap-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]) + + def handleExtendedAttribute(self, attr): + identifier = attr.identifier() + if identifier == "SetterThrows" and self.readonly: + raise WebIDLError("Readonly attributes must not be flagged as " + "[SetterThrows]", + [self.location]) + elif (((identifier == "Throws" or identifier == "GetterThrows") and + self.getExtendedAttribute("StoreInSlot")) or + (identifier == "StoreInSlot" and + (self.getExtendedAttribute("Throws") or + self.getExtendedAttribute("GetterThrows")))): + raise WebIDLError("Throwing things can't be [StoreInSlot]", + [attr.location]) + elif identifier == "LenientThis": + if not attr.noArguments(): + raise WebIDLError("[LenientThis] must take no arguments", + [attr.location]) + if self.isStatic(): + raise WebIDLError("[LenientThis] is only allowed on non-static " + "attributes", [attr.location, self.location]) + if self.getExtendedAttribute("CrossOriginReadable"): + raise WebIDLError("[LenientThis] is not allowed in combination " + "with [CrossOriginReadable]", + [attr.location, self.location]) + if self.getExtendedAttribute("CrossOriginWritable"): + raise WebIDLError("[LenientThis] is not allowed in combination " + "with [CrossOriginWritable]", + [attr.location, self.location]) + self.lenientThis = True + elif identifier == "Unforgeable": + if self.isStatic(): + raise WebIDLError("[Unforgeable] is only allowed on non-static " + "attributes", [attr.location, self.location]) + self._unforgeable = 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.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.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 == "LenientSetter": + if not attr.noArguments(): + raise WebIDLError("[LenientSetter] must take no arguments", + [attr.location]) + if not self.readonly: + raise WebIDLError("[LenientSetter] is only allowed on readonly " + "attributes", [attr.location, self.location]) + if self.isStatic(): + raise WebIDLError("[LenientSetter] is only allowed on non-static " + "attributes", [attr.location, self.location]) + if self.getExtendedAttribute("PutForwards") is not None: + raise WebIDLError("[LenientSetter] and [PutForwards] can't both " + "appear on the same attribute", + [attr.location, self.location]) + if self.getExtendedAttribute("Replaceable") is not None: + raise WebIDLError("[LenientSetter] 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 == "EnforceRange": + if self.readonly: + raise WebIDLError("[EnforceRange] used on a readonly attribute", + [attr.location, self.location]) + self.enforceRange = True + elif identifier == "Clamp": + if self.readonly: + raise WebIDLError("[Clamp] used on a readonly attribute", + [attr.location, self.location]) + self.clamp = True + 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() and identifier == "CrossOriginReadable": + 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("LenientThis"): + raise WebIDLError("[LenientThis] 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 == "Pref" or + identifier == "Deprecated" or + identifier == "SetterThrows" or + identifier == "Throws" or + identifier == "GetterThrows" or + identifier == "ChromeOnly" or + identifier == "Func" or + identifier == "SecureContext" or + identifier == "Frozen" or + identifier == "NewObject" or + identifier == "UnsafeInPrerendering" or + identifier == "NeedsSubjectPrincipal" or + identifier == "NeedsCallerType" or + identifier == "ReturnValueNeedsContainsHack" or + identifier == "BinaryName"): + # 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 addExtendedAttributes(self, attrs): + attrs = self.checkForStringHandlingExtendedAttributes(attrs) + IDLInterfaceMember.addExtendedAttributes(self, attrs) + + def hasLenientThis(self): + return self.lenientThis + + 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 isUnforgeable(self): + return self._unforgeable + + def _getDependentObjects(self): + return set([self.type]) + + +class IDLArgument(IDLObjectWithIdentifier): + def __init__(self, location, identifier, type, optional=False, defaultValue=None, variadic=False, dictionaryMember=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.enforceRange = False + self.clamp = False + self._allowTreatNonCallableAsNull = False + self._extendedAttrDict = {} + + assert not variadic or optional + assert not variadic or not defaultValue + + def addExtendedAttributes(self, attrs): + attrs = self.checkForStringHandlingExtendedAttributes( + attrs, + isDictionaryMember=self.dictionaryMember, + isOptional=self.optional) + for attribute in attrs: + identifier = attribute.identifier() + if identifier == "Clamp": + if not attribute.noArguments(): + raise WebIDLError("[Clamp] must take no arguments", + [attribute.location]) + if self.enforceRange: + raise WebIDLError("[EnforceRange] and [Clamp] are mutually exclusive", + [self.location]) + self.clamp = True + elif identifier == "EnforceRange": + if not attribute.noArguments(): + raise WebIDLError("[EnforceRange] must take no arguments", + [attribute.location]) + if self.clamp: + raise WebIDLError("[EnforceRange] and [Clamp] are mutually exclusive", + [self.location]) + self.enforceRange = True + elif identifier == "TreatNonCallableAsNull": + self._allowTreatNonCallableAsNull = True + elif (self.dictionaryMember and + (identifier == "ChromeOnly" or identifier == "Func")): + 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.isDictionary() or + self.type.isUnion() and self.type.unroll().hasDictionaryType()) and + self.optional and not self.defaultValue and not self.variadic): + # Default optional non-variadic dictionaries to null, + # for simplicity, so the codegen doesn't have to special-case this. + self.defaultValue = IDLNullValue(self.location) + elif 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) + + # 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): + 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 + + def isCallback(self): + return True + + 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): + pass + + def addExtendedAttributes(self, attrs): + unhandledAttrs = [] + for attr in attrs: + if attr.identifier() == "TreatNonCallableAsNull": + self._treatNonCallableAsNull = True + elif attr.identifier() == "TreatNonObjectAsNull": + self._treatNonObjectAsNull = True + else: + unhandledAttrs.append(attr) + if self._treatNonCallableAsNull and self._treatNonObjectAsNull: + raise WebIDLError("Cannot specify both [TreatNonCallableAsNull] " + "and [TreatNonObjectAsNull]", [self.location]) + if len(unhandledAttrs) != 0: + IDLType.addExtendedAttributes(self, unhandledAttrs) + + def _getDependentObjects(self): + return set([self._returnType] + self._arguments) + + +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.isPrimitive() or other.isString() or other.isEnum() or + other.isNonCallbackInterface() or other.isDate() 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 + + +class IDLMethod(IDLInterfaceMember, IDLScope): + + Special = enum( + 'Getter', + 'Setter', + 'Creator', + 'Deleter', + 'LegacyCaller', + base=IDLInterfaceMember.Special + ) + + NamedOrIndexed = enum( + 'Neither', + 'Named', + 'Indexed' + ) + + def __init__(self, location, identifier, returnType, arguments, + static=False, getter=False, setter=False, creator=False, + deleter=False, specialType=NamedOrIndexed.Neither, + legacycaller=False, stringifier=False, jsonifier=False, + maplikeOrSetlikeOrIterable=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(creator, bool) + self._creator = creator + assert isinstance(deleter, bool) + self._deleter = deleter + assert isinstance(legacycaller, bool) + self._legacycaller = legacycaller + assert isinstance(stringifier, bool) + self._stringifier = stringifier + assert isinstance(jsonifier, bool) + self._jsonifier = jsonifier + assert maplikeOrSetlikeOrIterable is None or isinstance(maplikeOrSetlikeOrIterable, IDLMaplikeOrSetlikeOrIterableBase) + self.maplikeOrSetlikeOrIterable = maplikeOrSetlikeOrIterable + self._specialType = specialType + self._unforgeable = 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.isVoid() + + if self._setter or self._creator: + 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 + assert overload.returnType == BuiltinTypes[IDLBuiltinType.Types.domstring] + + if self._jsonifier: + assert len(self._overloads) == 1 + overload = self._overloads[0] + assert len(overload.arguments) == 0 + assert overload.returnType == BuiltinTypes[IDLBuiltinType.Types.object] + + 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 isCreator(self): + return self._creator + + 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 isJsonifier(self): + return self._jsonifier + + 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.isCreator() or + self.isDeleter() or + self.isLegacycaller() or + self.isStringifier() or + self.isJsonifier()) + + 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: + 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.isCreator() + assert not method.isCreator() + assert not self.isDeleter() + assert not method.isDeleter() + assert not self.isStringifier() + assert not method.isStringifier() + assert not self.isJsonifier() + assert not method.isJsonifier() + + 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.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 or union " + "argument containing a dictionary " + "not followed by a required argument " + "must be optional", + [argument.location]) + + # An argument cannot be a Nullable Dictionary + if argument.type.nullable(): + 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]) + + 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": + raise WebIDLError("Methods must not be flagged as " + "[GetterThrows]", + [attr.location, self.location]) + elif identifier == "SetterThrows": + raise WebIDLError("Methods must not be flagged as " + "[SetterThrows]", + [attr.location, self.location]) + elif identifier == "Unforgeable": + if self.isStatic(): + raise WebIDLError("[Unforgeable] is only allowed on non-static " + "methods", [attr.location, self.location]) + self._unforgeable = 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 == "LenientSetter": + raise WebIDLError("Only attributes support [LenientSetter]", + [attr.location, self.location]) + elif identifier == "LenientFloat": + # This is called before we've done overload resolution + assert len(self.signatures()) == 1 + sig = self.signatures()[0] + if not sig[0].isVoid(): + raise WebIDLError("[LenientFloat] used on a non-void method", + [attr.location, self.location]) + if not any(arg.type.includesRestrictedFloat() for arg in sig[1]): + 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]) + 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 == "Throws" or + identifier == "NewObject" or + identifier == "ChromeOnly" or + identifier == "UnsafeInPrerendering" or + identifier == "Pref" or + identifier == "Deprecated" or + identifier == "Func" or + identifier == "SecureContext" or + identifier == "BinaryName" or + identifier == "NeedsSubjectPrincipal" or + identifier == "NeedsCallerType" or + identifier == "StaticClassOverride"): + # 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 isUnforgeable(self): + return self._unforgeable + + def _getDependentObjects(self): + deps = set() + for overload in self._overloads: + deps.update(overload._getDependentObjects()) + return deps + + +class IDLImplementsStatement(IDLObject): + def __init__(self, location, implementor, implementee): + IDLObject.__init__(self, location) + self.implementor = implementor + self.implementee = implementee + self._finished = False + + def finish(self, scope): + if self._finished: + return + assert(isinstance(self.implementor, IDLIdentifierPlaceholder)) + assert(isinstance(self.implementee, IDLIdentifierPlaceholder)) + implementor = self.implementor.finish(scope) + implementee = self.implementee.finish(scope) + # NOTE: we depend on not setting self.implementor and + # self.implementee here to keep track of the original + # locations. + if not isinstance(implementor, IDLInterface): + raise WebIDLError("Left-hand side of 'implements' is not an " + "interface", + [self.implementor.location]) + if implementor.isCallback(): + raise WebIDLError("Left-hand side of 'implements' is a callback " + "interface", + [self.implementor.location]) + if not isinstance(implementee, IDLInterface): + raise WebIDLError("Right-hand side of 'implements' is not an " + "interface", + [self.implementee.location]) + if implementee.isCallback(): + raise WebIDLError("Right-hand side of 'implements' is a callback " + "interface", + [self.implementee.location]) + implementor.addImplementedInterface(implementee) + self.implementor = implementor + self.implementee = implementee + + def validate(self): + pass + + def addExtendedAttributes(self, attrs): + assert len(attrs) == 0 + + +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: + 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-Z_a-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 = { + "module": "MODULE", + "interface": "INTERFACE", + "partial": "PARTIAL", + "dictionary": "DICTIONARY", + "exception": "EXCEPTION", + "enum": "ENUM", + "callback": "CALLBACK", + "typedef": "TYPEDEF", + "implements": "IMPLEMENTS", + "const": "CONST", + "null": "NULL", + "true": "TRUE", + "false": "FALSE", + "serializer": "SERIALIZER", + "stringifier": "STRINGIFIER", + "jsonifier": "JSONIFIER", + "unrestricted": "UNRESTRICTED", + "attribute": "ATTRIBUTE", + "readonly": "READONLY", + "inherit": "INHERIT", + "static": "STATIC", + "getter": "GETTER", + "setter": "SETTER", + "creator": "CREATOR", + "deleter": "DELETER", + "legacycaller": "LEGACYCALLER", + "optional": "OPTIONAL", + "...": "ELLIPSIS", + "::": "SCOPE", + "Date": "DATE", + "DOMString": "DOMSTRING", + "ByteString": "BYTESTRING", + "USVString": "USVSTRING", + "any": "ANY", + "boolean": "BOOLEAN", + "byte": "BYTE", + "double": "DOUBLE", + "float": "FLOAT", + "long": "LONG", + "object": "OBJECT", + "octet": "OCTET", + "Promise": "PROMISE", + "required": "REQUIRED", + "sequence": "SEQUENCE", + "MozMap": "MOZMAP", + "short": "SHORT", + "unsigned": "UNSIGNED", + "void": "VOID", + ":": "COLON", + ";": "SEMICOLON", + "{": "LBRACE", + "}": "RBRACE", + "(": "LPAREN", + ")": "RPAREN", + "[": "LBRACKET", + "]": "RBRACKET", + "?": "QUESTIONMARK", + ",": "COMMA", + "=": "EQUALS", + "<": "LT", + ">": "GT", + "ArrayBuffer": "ARRAYBUFFER", + "SharedArrayBuffer": "SHAREDARRAYBUFFER", + "or": "OR", + "maplike": "MAPLIKE", + "setlike": "SETLIKE", + "iterable": "ITERABLE", + "namespace": "NAMESPACE" + } + + 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, + outputdir=outputdir, + lextab='webidllex', + 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 : CallbackOrInterface + | Namespace + | Partial + | Dictionary + | Exception + | Enum + | Typedef + | ImplementsStatement + """ + p[0] = p[1] + assert p[1] # We might not have implemented something ... + + def p_CallbackOrInterfaceCallback(self, p): + """ + CallbackOrInterface : CALLBACK CallbackRestOrInterface + """ + if p[2].isInterface(): + assert isinstance(p[2], IDLInterface) + p[2].setCallback(True) + + p[0] = p[2] + + def p_CallbackOrInterfaceInterface(self, p): + """ + CallbackOrInterface : Interface + """ + p[0] = p[1] + + def p_CallbackRestOrInterface(self, p): + """ + CallbackRestOrInterface : CallbackRest + | Interface + """ + assert p[1] + p[0] = p[1] + + def handleNonPartialObject(self, location, identifier, constructor, + constructorArgs, nonPartialArgs): + """ + This handles non-partial objects (interfaces and namespaces) 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, ex: + if isinstance(ex, WebIDLError): + raise ex + pass + + # True for isKnownNonPartial + return constructor(*(constructorArgs + [True])) + + def p_Interface(self, p): + """ + Interface : INTERFACE IDENTIFIER Inheritance LBRACE InterfaceMembers RBRACE SEMICOLON + """ + location = self.getLocation(p, 1) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + members = p[5] + parent = p[3] + + p[0] = self.handleNonPartialObject( + location, identifier, IDLInterface, + [location, self.globalScope(), identifier, parent, members], + [location, parent, members]) + + def p_InterfaceForwardDecl(self, p): + """ + Interface : INTERFACE IDENTIFIER SEMICOLON + """ + location = self.getLocation(p, 1) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + + 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, ex: + if isinstance(ex, WebIDLError): + raise ex + pass + + p[0] = IDLExternalInterface(location, self.globalScope(), identifier) + + 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_PartialDefinition(self, p): + """ + PartialDefinition : PartialInterface + | PartialNamespace + """ + p[0] = p[1] + + def handlePartialObject(self, location, identifier, nonPartialConstructor, + nonPartialConstructorArgs, + partialConstructorArgs): + """ + This handles partial objects (interfaces and namespaces) by checking for + an existing non-partial object, and adding ourselves to it as needed. + The return value is our partial object. For now we just use + IDLPartialInterfaceOrNamespace for partial objects. + + nonPartialConstructorArgs are all the args for the non-partial + constructor except the last two: members and isKnownNonPartial. + + partialConstructorArgs are the arguments for the + IDLPartialInterfaceOrNamespace 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, ex: + if isinstance(ex, WebIDLError): + raise ex + pass + + if not nonPartialObject: + nonPartialObject = nonPartialConstructor( + # No members, False for isKnownNonPartial + *(nonPartialConstructorArgs + [[], False])) + partialInterface = IDLPartialInterfaceOrNamespace( + *(partialConstructorArgs + [nonPartialObject])) + return partialInterface + + def p_PartialInterface(self, p): + """ + PartialInterface : INTERFACE 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, IDLInterface, + [location, self.globalScope(), identifier, None], + [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_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]] if p[2] else [] + + 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 : Const + | AttributeOrOperationOrMaplikeOrSetlikeOrIterable + """ + 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 + # Add our extended attributes + p[2].addExtendedAttributes(p[1]) + p[0] = [p[2]] + p[0].extend(p[3]) + + def p_DictionaryMember(self, p): + """ + DictionaryMember : Required Type IDENTIFIER Default SEMICOLON + """ + # These quack a lot like optional arguments, so just treat them that way. + t = p[2] + assert isinstance(t, IDLType) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 3), p[3]) + defaultValue = p[4] + optional = not p[1] + + if not optional and defaultValue: + raise WebIDLError("Required dictionary members can't have a default value.", + [self.getLocation(p, 4)]) + + p[0] = IDLArgument(self.getLocation(p, 3), identifier, t, + optional=optional, + defaultValue=defaultValue, variadic=False, + dictionaryMember=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 + """ + if len(p) == 2: + p[0] = p[1] + else: + assert len(p) == 3 # Must be [] + p[0] = IDLEmptySequenceValue(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 ReturnType 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]) + + def p_ExceptionMembers(self, p): + """ + ExceptionMembers : ExtendedAttributeList ExceptionMember ExceptionMembers + | + """ + pass + + def p_Typedef(self, p): + """ + Typedef : TYPEDEF Type IDENTIFIER SEMICOLON + """ + typedef = IDLTypedef(self.getLocation(p, 1), self.globalScope(), + p[2], p[3]) + p[0] = typedef + + def p_ImplementsStatement(self, p): + """ + ImplementsStatement : ScopedName IMPLEMENTS ScopedName SEMICOLON + """ + assert(p[2] == "implements") + implementor = IDLIdentifierPlaceholder(self.getLocation(p, 1), p[1]) + implementee = IDLIdentifierPlaceholder(self.getLocation(p, 3), p[3]) + p[0] = IDLImplementsStatement(self.getLocation(p, 1), implementor, + implementee) + + 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_ConstValueNull(self, p): + """ + ConstValue : NULL + """ + p[0] = IDLNullValue(self.getLocation(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 + | Operation + """ + p[0] = p[1] + + def p_Iterable(self, p): + """ + Iterable : ITERABLE LT Type GT SEMICOLON + | ITERABLE LT Type COMMA Type 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_Setlike(self, p): + """ + Setlike : ReadOnly SETLIKE LT Type 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 Type COMMA Type 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 Type 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 + creator = True if IDLMethod.Special.Creator 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 or creator: + raise WebIDLError("getter and deleter are incompatible with setter and creator", + [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.isVoid(): + raise WebIDLError("getter cannot have void return type", + [self.getLocation(p, 2)]) + if setter or creator: + if len(arguments) != 2: + raise WebIDLError("%s has wrong number of arguments" % + ("setter" if setter else "creator"), + [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("%s has wrong argument type (must be DOMString or UnsignedLong)" % + ("setter" if setter else "creator"), + [arguments[0].location]) + if arguments[0].optional or arguments[0].variadic: + raise WebIDLError("%s cannot have %s argument" % + ("setter" if setter else "creator", + "optional" if arguments[0].optional else "variadic"), + [arguments[0].location]) + if arguments[1].optional or arguments[1].variadic: + raise WebIDLError("%s cannot have %s argument" % + ("setter" if setter else "creator", + "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 creator 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%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 "", + "creator" if creator 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, creator=creator, + 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_Jsonifier(self, p): + """ + Operation : JSONIFIER SEMICOLON + """ + identifier = IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"), + "__jsonifier", allowDoubleUnderscore=True) + method = IDLMethod(self.getLocation(p, 1), + identifier, + returnType=BuiltinTypes[IDLBuiltinType.Types.object], + arguments=[], + jsonifier=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_SpecialCreator(self, p): + """ + Special : CREATOR + """ + p[0] = IDLMethod.Special.Creator + + 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 : ReturnType 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 Optional Type Ellipsis ArgumentName Default + """ + t = p[3] + assert isinstance(t, IDLType) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 5), p[5]) + + optional = p[2] + variadic = p[4] + defaultValue = p[6] + + if not optional and defaultValue: + raise WebIDLError("Mandatory arguments can't have a default value.", + [self.getLocation(p, 6)]) + + # 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. + + if variadic: + if optional: + raise WebIDLError("Variadic arguments should not be marked optional.", + [self.getLocation(p, 2)]) + optional = variadic + + p[0] = IDLArgument(self.getLocation(p, 5), identifier, t, optional, defaultValue, variadic) + p[0].addExtendedAttributes(p[1]) + + def p_ArgumentName(self, p): + """ + ArgumentName : IDENTIFIER + | ATTRIBUTE + | CALLBACK + | CONST + | CREATOR + | DELETER + | DICTIONARY + | ENUM + | EXCEPTION + | GETTER + | IMPLEMENTS + | INHERIT + | INTERFACE + | ITERABLE + | LEGACYCALLER + | MAPLIKE + | PARTIAL + | REQUIRED + | SERIALIZER + | SETLIKE + | SETTER + | STATIC + | STRINGIFIER + | JSONIFIER + | TYPEDEF + | UNRESTRICTED + | NAMESPACE + """ + p[0] = p[1] + + def p_AttributeName(self, p): + """ + AttributeName : IDENTIFIER + | REQUIRED + """ + p[0] = p[1] + + def p_Optional(self, p): + """ + Optional : OPTIONAL + """ + p[0] = True + + def p_OptionalEmpty(self, p): + """ + Optional : + """ + p[0] = False + + def p_Required(self, p): + """ + Required : REQUIRED + """ + p[0] = True + + def p_RequiredEmpty(self, p): + """ + Required : + """ + p[0] = False + + 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 + | 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 + | DATE + | DOMSTRING + | BYTESTRING + | USVSTRING + | ANY + | ATTRIBUTE + | BOOLEAN + | BYTE + | LEGACYCALLER + | CONST + | CREATOR + | DELETER + | DOUBLE + | EXCEPTION + | FALSE + | FLOAT + | GETTER + | IMPLEMENTS + | INHERIT + | INTERFACE + | LONG + | MODULE + | NULL + | OBJECT + | OCTET + | OPTIONAL + | SEQUENCE + | MOZMAP + | SETTER + | SHORT + | STATIC + | STRINGIFIER + | JSONIFIER + | TRUE + | TYPEDEF + | UNSIGNED + | VOID + """ + 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_SingleTypeNonAnyType(self, p): + """ + SingleType : NonAnyType + """ + p[0] = p[1] + + def p_SingleTypeAnyType(self, p): + """ + SingleType : ANY + """ + p[0] = BuiltinTypes[IDLBuiltinType.Types.any] + + 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_UnionMemberTypeNonAnyType(self, p): + """ + UnionMemberType : NonAnyType + """ + p[0] = 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_NonAnyType(self, p): + """ + NonAnyType : PrimitiveOrStringType Null + | ARRAYBUFFER Null + | SHAREDARRAYBUFFER Null + | OBJECT Null + """ + if p[1] == "object": + type = BuiltinTypes[IDLBuiltinType.Types.object] + elif p[1] == "ArrayBuffer": + type = BuiltinTypes[IDLBuiltinType.Types.ArrayBuffer] + elif p[1] == "SharedArrayBuffer": + type = BuiltinTypes[IDLBuiltinType.Types.SharedArrayBuffer] + else: + type = BuiltinTypes[p[1]] + + p[0] = self.handleNullable(type, p[2]) + + def p_NonAnyTypeSequenceType(self, p): + """ + NonAnyType : SEQUENCE LT Type GT Null + """ + innerType = p[3] + type = IDLSequenceType(self.getLocation(p, 1), innerType) + p[0] = self.handleNullable(type, p[5]) + + # Note: Promise<void> is allowed, so we want to parametrize on + # ReturnType, not Type. Also, we want this to end up picking up + # the Promise interface for now, hence the games with IDLUnresolvedType. + def p_NonAnyTypePromiseType(self, p): + """ + NonAnyType : PROMISE LT ReturnType GT Null + """ + innerType = p[3] + promiseIdent = IDLUnresolvedIdentifier(self.getLocation(p, 1), "Promise") + type = IDLUnresolvedType(self.getLocation(p, 1), promiseIdent, p[3]) + p[0] = self.handleNullable(type, p[5]) + + def p_NonAnyTypeMozMapType(self, p): + """ + NonAnyType : MOZMAP LT Type GT Null + """ + innerType = p[3] + type = IDLMozMapType(self.getLocation(p, 1), innerType) + p[0] = self.handleNullable(type, p[5]) + + def p_NonAnyTypeScopedName(self, p): + """ + NonAnyType : 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: + pass + + type = IDLUnresolvedType(self.getLocation(p, 1), p[1]) + p[0] = self.handleNullable(type, p[2]) + + def p_NonAnyTypeDate(self, p): + """ + NonAnyType : DATE Null + """ + p[0] = self.handleNullable(BuiltinTypes[IDLBuiltinType.Types.date], + p[2]) + + def p_ConstType(self, p): + """ + ConstType : PrimitiveOrStringType Null + """ + type = BuiltinTypes[p[1]] + p[0] = self.handleNullable(type, p[2]) + + def p_ConstTypeIdentifier(self, p): + """ + ConstType : IDENTIFIER Null + """ + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1]) + + type = IDLUnresolvedType(self.getLocation(p, 1), identifier) + p[0] = self.handleNullable(type, p[2]) + + def p_PrimitiveOrStringTypeUint(self, p): + """ + PrimitiveOrStringType : UnsignedIntegerType + """ + p[0] = p[1] + + def p_PrimitiveOrStringTypeBoolean(self, p): + """ + PrimitiveOrStringType : BOOLEAN + """ + p[0] = IDLBuiltinType.Types.boolean + + def p_PrimitiveOrStringTypeByte(self, p): + """ + PrimitiveOrStringType : BYTE + """ + p[0] = IDLBuiltinType.Types.byte + + def p_PrimitiveOrStringTypeOctet(self, p): + """ + PrimitiveOrStringType : OCTET + """ + p[0] = IDLBuiltinType.Types.octet + + def p_PrimitiveOrStringTypeFloat(self, p): + """ + PrimitiveOrStringType : FLOAT + """ + p[0] = IDLBuiltinType.Types.float + + def p_PrimitiveOrStringTypeUnrestictedFloat(self, p): + """ + PrimitiveOrStringType : UNRESTRICTED FLOAT + """ + p[0] = IDLBuiltinType.Types.unrestricted_float + + def p_PrimitiveOrStringTypeDouble(self, p): + """ + PrimitiveOrStringType : DOUBLE + """ + p[0] = IDLBuiltinType.Types.double + + def p_PrimitiveOrStringTypeUnrestictedDouble(self, p): + """ + PrimitiveOrStringType : UNRESTRICTED DOUBLE + """ + p[0] = IDLBuiltinType.Types.unrestricted_double + + def p_PrimitiveOrStringTypeDOMString(self, p): + """ + PrimitiveOrStringType : DOMSTRING + """ + p[0] = IDLBuiltinType.Types.domstring + + def p_PrimitiveOrStringTypeBytestring(self, p): + """ + PrimitiveOrStringType : BYTESTRING + """ + p[0] = IDLBuiltinType.Types.bytestring + + def p_PrimitiveOrStringTypeUSVString(self, p): + """ + PrimitiveOrStringType : USVSTRING + """ + p[0] = IDLBuiltinType.Types.usvstring + + 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_ReturnTypeType(self, p): + """ + ReturnType : Type + """ + p[0] = p[1] + + def p_ReturnTypeVoid(self, p): + """ + ReturnType : VOID + """ + p[0] = BuiltinTypes[IDLBuiltinType.Types.void] + + 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_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]) + idents.insert(0, p[1]) + p[0] = idents + + def p_IdentifiersList(self, p): + """ + Identifiers : COMMA IDENTIFIER Identifiers + """ + idents = list(p[3]) + idents.insert(0, p[2]) + 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, + tabmodule='webidlyacc', + errorlog=logger + # 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) + # To make our test harness work, pretend like we have a primary global already. + # Note that we _don't_ set _globalScope.primaryGlobalAttr, + # so we'll still be able to detect multiple PrimaryGlobal extended attributes. + self._globalScope.primaryGlobalName = "FakeTestPrimaryGlobal" + self._globalScope.globalNames.add("FakeTestPrimaryGlobal") + self._globalScope.globalNameMapping["FakeTestPrimaryGlobal"].add("FakeTestPrimaryGlobal") + # And we add the special-cased "System" global name, which + # doesn't have any corresponding interfaces. + self._globalScope.globalNames.add("System") + self._globalScope.globalNameMapping["System"].add("BackstagePass") + 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) + + # xrange omits the last value. + for x in xrange(IDLBuiltinType.Types.ArrayBuffer, IDLBuiltinType.Types.Float64Array + 1): + builtin = BuiltinTypes[x] + name = builtin.name + typedef = 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) + if p.identifier.name == "Navigator": + navigatorInterface = p + + iterableIteratorIface = None + for iface in interfaceStatements: + navigatorProperty = iface.getNavigatorProperty() + if navigatorProperty: + # We're generating a partial interface to add a readonly + # property to the Navigator interface for every interface + # annotated with NavigatorProperty. + partialInterface = IDLPartialInterfaceOrNamespace( + iface.location, + IDLUnresolvedIdentifier(iface.location, "Navigator"), + [ navigatorProperty ], + navigatorInterface) + self._productions.append(partialInterface) + + 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): + iterable = m + break + if iterable and iterable.isPairIterator(): + def simpleExtendedAttr(str): + return IDLExtendedAttribute(iface.location, (str, )) + nextMethod = IDLMethod( + iface.location, + IDLUnresolvedIdentifier(iface.location, "next"), + BuiltinTypes[IDLBuiltinType.Types.object], []) + nextMethod.addExtendedAttributes([simpleExtendedAttr("Throws")]) + itr_ident = IDLUnresolvedIdentifier(iface.location, + iface.identifier.name + "Iterator") + itr_iface = IDLInterface(iface.location, self.globalScope(), + itr_ident, None, [nextMethod], + isKnownNonPartial=True) + itr_iface.addExtendedAttributes([simpleExtendedAttr("NoInterfaceObject")]) + # 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. + itr_iface.iterableInterface = iface + self._productions.append(itr_iface) + iterable.iteratorType = IDLWrapperType(iface.location, itr_iface) + + # Then, finish all the IDLImplementsStatements. In particular, we + # have to make sure we do those before we do the IDLInterfaces. + # XXX khuey hates this bit and wants to nuke it from orbit. + implementsStatements = [p for p in self._productions if + isinstance(p, IDLImplementsStatement)] + otherStatements = [p for p in self._productions if + not isinstance(p, IDLImplementsStatement)] + for production in implementsStatements: + 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 unsigned long long DOMTimeStamp; + 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, 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 000000000..33bb59f8a --- /dev/null +++ b/dom/bindings/parser/runtests.py @@ -0,0 +1,108 @@ +# 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, sys +import glob +import argparse +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 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, 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 + +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 + 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 000000000..e3b690bf6 --- /dev/null +++ b/dom/bindings/parser/tests/test_any_null.py @@ -0,0 +1,14 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface DoubleNull { + attribute any? foo; + }; + """) + + results = parser.finish() + except: + 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 000000000..eb1f6d3c9 --- /dev/null +++ b/dom/bindings/parser/tests/test_argument_identifier_conflicts.py @@ -0,0 +1,14 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface ArgumentIdentifierConflict { + void foo(boolean arg1, boolean arg1); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") 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 000000000..ef8c2229a --- /dev/null +++ b/dom/bindings/parser/tests/test_argument_novoid.py @@ -0,0 +1,14 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface VoidArgument1 { + void foo(void arg2); + }; + """) + + results = parser.finish() + except: + 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 000000000..4a96c0ff5 --- /dev/null +++ b/dom/bindings/parser/tests/test_arraybuffer.py @@ -0,0 +1,81 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + interface TestArrayBuffer { + attribute ArrayBuffer bufferAttr; + void bufferMethod(ArrayBuffer arg1, ArrayBuffer? arg2, sequence<ArrayBuffer> arg3); + + attribute ArrayBufferView viewAttr; + void viewMethod(ArrayBufferView arg1, ArrayBufferView? arg2, sequence<ArrayBufferView> arg3); + + attribute Int8Array int8ArrayAttr; + void int8ArrayMethod(Int8Array arg1, Int8Array? arg2, sequence<Int8Array> arg3); + + attribute Uint8Array uint8ArrayAttr; + void uint8ArrayMethod(Uint8Array arg1, Uint8Array? arg2, sequence<Uint8Array> arg3); + + attribute Uint8ClampedArray uint8ClampedArrayAttr; + void uint8ClampedArrayMethod(Uint8ClampedArray arg1, Uint8ClampedArray? arg2, sequence<Uint8ClampedArray> arg3); + + attribute Int16Array int16ArrayAttr; + void int16ArrayMethod(Int16Array arg1, Int16Array? arg2, sequence<Int16Array> arg3); + + attribute Uint16Array uint16ArrayAttr; + void uint16ArrayMethod(Uint16Array arg1, Uint16Array? arg2, sequence<Uint16Array> arg3); + + attribute Int32Array int32ArrayAttr; + void int32ArrayMethod(Int32Array arg1, Int32Array? arg2, sequence<Int32Array> arg3); + + attribute Uint32Array uint32ArrayAttr; + void uint32ArrayMethod(Uint32Array arg1, Uint32Array? arg2, sequence<Uint32Array> arg3); + + attribute Float32Array float32ArrayAttr; + void float32ArrayMethod(Float32Array arg1, Float32Array? arg2, sequence<Float32Array> arg3); + + attribute Float64Array float64ArrayAttr; + void 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.isVoid(), "Should have a void 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 000000000..ad7aabc19 --- /dev/null +++ b/dom/bindings/parser/tests/test_attr.py @@ -0,0 +1,177 @@ +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, x: + 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, x: + 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, x: + 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, x: + 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 000000000..fb1b97812 --- /dev/null +++ b/dom/bindings/parser/tests/test_attr_sequence_type.py @@ -0,0 +1,67 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface AttrSequenceType { + attribute sequence<object> foo; + }; + """) + + results = parser.finish() + except: + 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; + }; + """) + + results = parser.finish() + except: + 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; + }; + """) + + results = parser.finish() + except: + 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(""" + interface AttrUnionWithUnionWithSequenceType { + attribute ((sequence<object> or DOMString) or AttrUnionWithUnionWithSequenceType) foo; + }; + """) + + results = parser.finish() + except: + 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_builtin_filename.py b/dom/bindings/parser/tests/test_builtin_filename.py new file mode 100644 index 000000000..631e52eba --- /dev/null +++ b/dom/bindings/parser/tests/test_builtin_filename.py @@ -0,0 +1,11 @@ +import WebIDL + +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 000000000..f8563fc2d --- /dev/null +++ b/dom/bindings/parser/tests/test_builtins.py @@ -0,0 +1,41 @@ +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; + attribute DOMTimeStamp ts; + }; + """) + + 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), 10, "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(10): + 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 000000000..fa83e9e2d --- /dev/null +++ b/dom/bindings/parser/tests/test_bytestring.py @@ -0,0 +1,99 @@ +# -*- 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 { + void passByteString(optional ByteString arg = "hello"); + }; + """) + results2 = 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"; + }; + """) + results3 = 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"; + }; + """) + results4 = parser.finish() + except WebIDL.WebIDLError as e: + 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 000000000..4dfda1c3c --- /dev/null +++ b/dom/bindings/parser/tests/test_callback.py @@ -0,0 +1,34 @@ +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") 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 000000000..e4789dae1 --- /dev/null +++ b/dom/bindings/parser/tests/test_callback_interface.py @@ -0,0 +1,94 @@ +import WebIDL + +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; + }; + """) + results = parser.finish() + except: + 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; + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow callback parent of non-callback interface") + + parser = parser.reset() + parser.parse(""" + callback interface TestCallbackInterface1 { + void foo(); + }; + callback interface TestCallbackInterface2 { + void foo(DOMString arg); + void foo(TestCallbackInterface1 arg); + }; + callback interface TestCallbackInterface3 { + void foo(DOMString arg); + void foo(TestCallbackInterface1 arg); + static void bar(); + }; + callback interface TestCallbackInterface4 { + void foo(DOMString arg); + void foo(TestCallbackInterface1 arg); + static void bar(); + const long baz = 5; + }; + callback interface TestCallbackInterface5 { + static attribute boolean bool; + void foo(); + }; + callback interface TestCallbackInterface6 { + void foo(DOMString arg); + void foo(TestCallbackInterface1 arg); + void bar(); + }; + callback interface TestCallbackInterface7 { + static attribute boolean bool; + }; + callback interface TestCallbackInterface8 { + attribute boolean bool; + }; + callback interface TestCallbackInterface9 : TestCallbackInterface1 { + void foo(); + }; + callback interface TestCallbackInterface10 : TestCallbackInterface1 { + void 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_conditional_dictionary_member.py b/dom/bindings/parser/tests/test_conditional_dictionary_member.py new file mode 100644 index 000000000..433b7e501 --- /dev/null +++ b/dom/bindings/parser/tests/test_conditional_dictionary_member.py @@ -0,0 +1,110 @@ +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, exception: + pass + + 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, exception: + pass + + 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, exception: + pass + + 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 000000000..80b6fb0e9 --- /dev/null +++ b/dom/bindings/parser/tests/test_const.py @@ -0,0 +1,80 @@ +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::n", "n", "BooleanOrNull", None), + ("::TestConsts::nt", "nt", "BooleanOrNull", True), + ("::TestConsts::nf", "nf", "BooleanOrNull", 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 boolean? n = null; + const boolean? nt = true; + const boolean? nf = 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.") + diff --git a/dom/bindings/parser/tests/test_constructor.py b/dom/bindings/parser/tests/test_constructor.py new file mode 100644 index 000000000..348204c7d --- /dev/null +++ b/dom/bindings/parser/tests/test_constructor.py @@ -0,0 +1,109 @@ +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, creator=False, + deleter=False, legacycaller=False, stringifier=False, + chromeOnly=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.isCreator(), creator, "Method has the correct creator 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(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) + + parser.parse(""" + [Constructor] + interface TestConstructorNoArgs { + }; + + [Constructor(DOMString name)] + interface TestConstructorWithArgs { + }; + + [Constructor(object foo), Constructor(boolean bar)] + interface TestConstructorOverloads { + }; + """) + results = parser.finish() + 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)", [])]) + checkMethod(results[1].ctor(), "::TestConstructorWithArgs::constructor", + "constructor", + [("TestConstructorWithArgs (Wrapper)", + [("::TestConstructorWithArgs::constructor::name", "name", "String", False, False)])]) + 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)])]) + + parser = parser.reset() + parser.parse(""" + [ChromeConstructor()] + interface TestChromeConstructor { + }; + """) + 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(), "::TestChromeConstructor::constructor", + "constructor", [("TestChromeConstructor (Wrapper)", [])], + chromeOnly=True) + + parser = parser.reset() + threw = False + try: + parser.parse(""" + [Constructor(), + ChromeConstructor(DOMString a)] + interface TestChromeConstructor { + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Can't have both a Constructor and a ChromeConstructor") 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 000000000..2b09ae71e --- /dev/null +++ b/dom/bindings/parser/tests/test_constructor_no_interface_object.py @@ -0,0 +1,36 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + [Constructor, NoInterfaceObject] + interface TestConstructorNoInterfaceObject { + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + [NoInterfaceObject, Constructor] + interface TestConstructorNoInterfaceObject { + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + + parser.parse(""" + [NoInterfaceObject, NamedConstructor=FooBar] + interface TestNamedConstructorNoInterfaceObject { + }; + """) diff --git a/dom/bindings/parser/tests/test_date.py b/dom/bindings/parser/tests/test_date.py new file mode 100644 index 000000000..2bdfc95e1 --- /dev/null +++ b/dom/bindings/parser/tests/test_date.py @@ -0,0 +1,15 @@ +def WebIDLTest(parser, harness): + parser.parse(""" + interface WithDates { + attribute Date foo; + void bar(Date arg); + void baz(sequence<Date> arg); + }; + """) + + results = parser.finish() + harness.ok(results[0].members[0].type.isDate(), "Should have Date") + harness.ok(results[0].members[1].signatures()[0][1][0].type.isDate(), + "Should have Date argument") + harness.ok(not results[0].members[2].signatures()[0][1][0].type.isDate(), + "Should have non-Date argument") diff --git a/dom/bindings/parser/tests/test_deduplicate.py b/dom/bindings/parser/tests/test_deduplicate.py new file mode 100644 index 000000000..6249d36fb --- /dev/null +++ b/dom/bindings/parser/tests/test_deduplicate.py @@ -0,0 +1,15 @@ +import WebIDL + +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 000000000..2c0fa6123 --- /dev/null +++ b/dom/bindings/parser/tests/test_dictionary.py @@ -0,0 +1,555 @@ +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'") + + # Now reset our parser + parser = parser.reset() + threw = False + try: + parser.parse(""" + dictionary Dict { + long prop = 5; + long prop; + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow name duplication in a 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; + }; + """) + results = parser.finish() + except: + 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; + }; + """) + results = parser.finish() + except: + 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 {}; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow cycles in dictionary inheritance chains") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + dictionary A { + [TreatNullAs=EmptyString] DOMString foo; + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow [TreatNullAs] on dictionary members"); + + parser = parser.reset() + threw = False + try: + parser.parse(""" + dictionary A { + }; + interface X { + void doFoo(A arg); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Trailing dictionary arg must be optional") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + dictionary A { + }; + interface X { + void doFoo((A or DOMString) arg); + }; + """) + results = parser.finish() + except: + 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 { + void doFoo(A arg1, optional long arg2); + }; + """) + results = parser.finish() + except: + 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 { + void doFoo(A arg1, optional long arg2, long arg3); + }; + """) + results = parser.finish() + except: + 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 { + void doFoo((A or DOMString) arg1, optional long arg2); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Union arg containing dictionary followed by optional arg must " + "be optional") + + parser = parser.reset() + parser.parse(""" + dictionary A { + }; + interface X { + void doFoo(A arg1, long arg2); + }; + """) + results = 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 { + void doFoo(optional A? arg1); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Dictionary arg must not be nullable") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + dictionary A { + }; + interface X { + void doFoo(optional (A or long)? arg1); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Dictionary arg must not be in a nullable union") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + dictionary A { + }; + interface X { + void doFoo(optional (A or long?) arg1); + }; + """) + results = parser.finish() + except: + 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 { + void doFoo(optional (long? or A) arg1); + }; + """) + results = parser.finish() + except: + 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(); + }; + """) + results = parser.finish() + harness.ok(True, "Dictionary return value can be nullable") + + parser = parser.reset() + parser.parse(""" + dictionary A { + }; + interface X { + void doFoo(optional A arg); + }; + """) + results = parser.finish() + harness.ok(True, "Dictionary arg should actually parse") + + parser = parser.reset() + parser.parse(""" + dictionary A { + }; + interface X { + void doFoo(optional (A or DOMString) arg); + }; + """) + results = parser.finish() + harness.ok(True, "Union arg containing a dictionary should actually parse") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + dictionary Foo { + Foo foo; + }; + """) + results = parser.finish() + except: + 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; + }; + """) + results = parser.finish() + except: + 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; + }; + """) + results = parser.finish() + except: + 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; + }; + """) + results = parser.finish() + except: + 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; + }; + """) + results = parser.finish() + except: + 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; + }; + """) + results = parser.finish() + except: + 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; + }; + """) + results = parser.finish() + except: + 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; + }; + """) + results = parser.finish() + except: + 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; + }; + """) + results = 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; + }; + """) + results = parser.finish() + except: + 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; + }; + """) + results = parser.finish() + except: + 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; + }; + """) + results = parser.finish() + except: + 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; + }; + """) + results = parser.finish() + except: + 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; + }; + """) + results = parser.finish() + except: + 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; + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Only unrestricted values can be initialized to NaN") diff --git a/dom/bindings/parser/tests/test_distinguishability.py b/dom/bindings/parser/tests/test_distinguishability.py new file mode 100644 index 000000000..d7780c1ff --- /dev/null +++ b/dom/bindings/parser/tests/test_distinguishability.py @@ -0,0 +1,293 @@ +def firstArgType(method): + return method.signatures()[0][1][0].type + +def WebIDLTest(parser, harness): + parser.parse(""" + dictionary Dict { + }; + callback interface Foo { + }; + interface Bar { + // Bit of a pain to get things that have dictionary types + void passDict(optional Dict arg); + void passFoo(Foo arg); + void passNullableUnion((object? or DOMString) arg); + void 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 { + void passKid(Kid arg); + void passParent(Parent arg); + void passGrandparent(Grandparent arg); + void passImplemented(Implemented arg); + void passImplementedParent(ImplementedParent arg); + void passUnrelated1(Unrelated1 arg); + void passUnrelated2(Unrelated2 arg); + void passArrayBuffer(ArrayBuffer arg); + void passArrayBuffer(ArrayBufferView arg); + }; + + interface Kid : Parent {}; + interface Parent : Grandparent {}; + interface Grandparent {}; + interface Implemented : ImplementedParent {}; + Parent implements Implemented; + interface ImplementedParent {}; + 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 { + void method(long arg1, TestIface arg2); + void method(long arg1, long arg2); + void method(long arg1, Dummy arg2); + void 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 { + void method(long arg1, TestIface arg2); + void method(long arg1, long arg2); + void method(any arg1, Dummy arg2); + void method(DOMString arg1, DOMString arg2, DOMString arg3); + }; + """) + results = parser.finish() + except: + 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 { + void method(long arg1, TestIface arg2); + void method(long arg1, long arg2); + void method(any arg1, DOMString arg2); + void method(DOMString arg1, DOMString arg2, DOMString arg3); + }; + """) + results = parser.finish() + except: + 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?", "DOMString", "ByteString", "Enum", "Enum2", + "Interface", "Interface?", + "AncestorInterface", "UnrelatedInterface", + "ImplementedInterface", "CallbackInterface", + "CallbackInterface?", "CallbackInterface2", + "object", "Callback", "Callback2", "optional Dict", + "optional Dict2", "sequence<long>", "sequence<short>", + "MozMap<object>", "MozMap<Dict>", "MozMap<long>", + "Date", "Date?", "any", + "Promise<any>", "Promise<any>?", + "USVString", "ArrayBuffer", "ArrayBufferView", "SharedArrayBuffer", + "Uint8Array", "Uint16Array" ] + # When we can parse Date and RegExp, we need to add them here. + + # 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>?")] + numerics = [ "long", "short", "long?", "short?" ] + booleans = [ "boolean", "boolean?" ] + primitives = numerics + booleans + nonNumerics = allBut(argTypes, numerics) + nonBooleans = allBut(argTypes, booleans) + strings = [ "DOMString", "ByteString", "Enum", "Enum2", "USVString" ] + nonStrings = allBut(argTypes, strings) + nonObjects = primitives + strings + objects = allBut(argTypes, nonObjects ) + bufferSourceTypes = ["ArrayBuffer", "ArrayBufferView", "Uint8Array", "Uint16Array"] + sharedBufferSourceTypes = ["SharedArrayBuffer"] + interfaces = [ "Interface", "Interface?", "AncestorInterface", + "UnrelatedInterface", "ImplementedInterface" ] + bufferSourceTypes + sharedBufferSourceTypes + nullables = ["long?", "short?", "boolean?", "Interface?", + "CallbackInterface?", "optional Dict", "optional Dict2", + "Date?", "any", "Promise<any>?"] + dates = [ "Date", "Date?" ] + sequences = [ "sequence<long>", "sequence<short>" ] + nonUserObjects = nonObjects + interfaces + dates + sequences + otherObjects = allBut(argTypes, nonUserObjects + ["object"]) + notRelatedInterfaces = (nonObjects + ["UnrelatedInterface"] + + otherObjects + dates + sequences + bufferSourceTypes + sharedBufferSourceTypes) + mozMaps = [ "MozMap<object>", "MozMap<Dict>", "MozMap<long>" ] + + # 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("DOMString", nonStrings) + setDistinguishable("ByteString", nonStrings) + setDistinguishable("USVString", 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("ImplementedInterface", notRelatedInterfaces) + setDistinguishable("CallbackInterface", nonUserObjects) + setDistinguishable("CallbackInterface?", allBut(nonUserObjects, nullables)) + setDistinguishable("CallbackInterface2", nonUserObjects) + setDistinguishable("object", nonObjects) + setDistinguishable("Callback", nonUserObjects) + setDistinguishable("Callback2", nonUserObjects) + setDistinguishable("optional Dict", allBut(nonUserObjects, nullables)) + setDistinguishable("optional Dict2", allBut(nonUserObjects, nullables)) + setDistinguishable("sequence<long>", + allBut(argTypes, sequences + ["object"])) + setDistinguishable("sequence<short>", + allBut(argTypes, sequences + ["object"])) + setDistinguishable("MozMap<object>", nonUserObjects) + setDistinguishable("MozMap<Dict>", nonUserObjects) + setDistinguishable("MozMap<long>", nonUserObjects) + setDistinguishable("Date", allBut(argTypes, dates + ["object"])) + setDistinguishable("Date?", allBut(argTypes, dates + nullables + ["object"])) + 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("SharedArrayBuffer", allBut(argTypes, ["SharedArrayBuffer", "object"])) + + 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 {}; + interface ImplementedInterface {}; + Interface implements ImplementedInterface; + callback interface CallbackInterface {}; + callback interface CallbackInterface2 {}; + callback Callback = any(); + callback Callback2 = long(short arg); + dictionary Dict {}; + dictionary Dict2 {}; + interface _Promise {}; + interface TestInterface {%s + }; + """ + methodTemplate = """ + void myMethod(%s arg);""" + methods = (methodTemplate % type1) + (methodTemplate % type2) + idl = idlTemplate % methods + parser = parser.reset() + threw = False + try: + parser.parse(idl) + results = parser.finish() + except: + 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 000000000..700c7eade --- /dev/null +++ b/dom/bindings/parser/tests/test_double_null.py @@ -0,0 +1,14 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface DoubleNull { + attribute byte?? foo; + }; + """) + + results = parser.finish() + except: + 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 000000000..799f2e0e0 --- /dev/null +++ b/dom/bindings/parser/tests/test_duplicate_qualifiers.py @@ -0,0 +1,84 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface DuplicateQualifiers1 { + getter getter byte foo(unsigned long index); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface DuplicateQualifiers2 { + setter setter byte foo(unsigned long index, byte value); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface DuplicateQualifiers3 { + creator creator byte foo(unsigned long index, byte value); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface DuplicateQualifiers4 { + deleter deleter byte foo(unsigned long index); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface DuplicateQualifiers5 { + getter deleter getter byte foo(unsigned long index); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + results = parser.parse(""" + interface DuplicateQualifiers6 { + creator setter creator byte foo(unsigned long index, byte value); + }; + """) + + results = parser.finish() + except: + 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 000000000..ee0079f06 --- /dev/null +++ b/dom/bindings/parser/tests/test_empty_enum.py @@ -0,0 +1,14 @@ +import WebIDL + +def WebIDLTest(parser, harness): + try: + parser.parse(""" + enum TestEmptyEnum { + }; + """) + + harness.ok(False, "Should have thrown!") + except: + harness.ok(True, "Parsing TestEmptyEnum enum should fail") + + results = 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 000000000..350ae72f0 --- /dev/null +++ b/dom/bindings/parser/tests/test_empty_sequence_default_value.py @@ -0,0 +1,45 @@ +import WebIDL + +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface X { + const sequence<long> foo = []; + }; + """) + + results = parser.finish() + except Exception,x: + threw = True + + harness.ok(threw, "Constant cannot have [] as a default value") + + parser = parser.reset() + + parser.parse(""" + interface X { + void 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 000000000..862289391 --- /dev/null +++ b/dom/bindings/parser/tests/test_enum.py @@ -0,0 +1,93 @@ +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 { + void foo(optional Enum e = "d"); + }; + """) + results = parser.finish() + except: + 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 000000000..51205d209 --- /dev/null +++ b/dom/bindings/parser/tests/test_enum_duplicate_values.py @@ -0,0 +1,13 @@ +import WebIDL + +def WebIDLTest(parser, harness): + try: + parser.parse(""" + enum TestEnumDuplicateValue { + "", + "" + }; + """) + harness.ok(False, "Should have thrown!") + except: + 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 000000000..ca0674aec --- /dev/null +++ b/dom/bindings/parser/tests/test_error_colno.py @@ -0,0 +1,20 @@ +import WebIDL + +def WebIDLTest(parser, harness): + # Check that error messages put the '^' in the right place. + + threw = False + input = 'interface ?' + try: + parser.parse(input) + results = parser.finish() + except WebIDL.WebIDLError, 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 000000000..f11222e7a --- /dev/null +++ b/dom/bindings/parser/tests/test_error_lineno.py @@ -0,0 +1,28 @@ +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) + results = parser.finish() + except WebIDL.WebIDLError, 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 000000000..48957098b --- /dev/null +++ b/dom/bindings/parser/tests/test_exposed_extended_attribute.py @@ -0,0 +1,222 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + [PrimaryGlobal] interface Foo {}; + [Global=(Bar1,Bar2)] interface Bar {}; + [Global=Baz2] interface Baz {}; + + [Exposed=(Foo,Bar1)] + interface Iface { + void method1(); + + [Exposed=Bar1] + readonly attribute any attr; + }; + + [Exposed=Foo] + partial interface Iface { + void 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(""" + [PrimaryGlobal] interface Foo {}; + [Global=(Bar1,Bar2)] interface Bar {}; + [Global=Baz2] interface Baz {}; + + interface Iface2 { + void 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(""" + [PrimaryGlobal] interface Foo {}; + [Global=(Bar1,Bar2)] interface Bar {}; + [Global=Baz2] interface Baz {}; + + [Exposed=Foo] + interface Iface3 { + void method4(); + }; + + [Exposed=(Foo,Bar1)] + interface Mixin { + void method5(); + }; + + Iface3 implements 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,x: + 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,x: + 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] + void operation(); + }; + """) + + results = parser.finish() + except Exception,x: + 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,x: + threw = True + + harness.ok(threw, "Should have thrown on invalid Exposed value on constant.") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + [Global] interface Foo {}; + [Global] interface Bar {}; + + [Exposed=Foo] + interface Baz { + [Exposed=Bar] + void method(); + }; + """) + + results = parser.finish() + except Exception,x: + 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] interface Foo {}; + [Global] interface Bar {}; + + [Exposed=Foo] + interface Baz { + void method(); + }; + + [Exposed=Bar] + interface Mixin {}; + + Baz implements Mixin; + """) + + results = parser.finish() + except Exception,x: + threw = True + + harness.ok(threw, "Should have thrown on LHS of implements being exposed where RHS is not.") 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 000000000..85a70d98f --- /dev/null +++ b/dom/bindings/parser/tests/test_extended_attributes.py @@ -0,0 +1,107 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + [NoInterfaceObject] + interface TestExtendedAttr { + [Unforgeable] readonly attribute byte b; + }; + """) + + results = parser.finish() + + parser = parser.reset() + parser.parse(""" + [Pref="foo.bar",Pref=flop] + interface TestExtendedAttr { + [Pref="foo.bar"] attribute byte b; + }; + """) + + results = parser.finish() + + parser = parser.reset() + parser.parse(""" + interface TestLenientThis { + [LenientThis] attribute byte b; + }; + """) + + results = parser.finish() + harness.ok(results[0].members[0].hasLenientThis(), + "Should have a lenient this") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestLenientThis2 { + [LenientThis=something] attribute byte b; + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "[LenientThis] must take no arguments") + + parser = parser.reset() + parser.parse(""" + interface TestClamp { + void testClamp([Clamp] long foo); + void 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].clamp, + "Should be clamped") + harness.ok(not results[0].members[1].signatures()[0][1][0].clamp, + "Should not be clamped") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestClamp2 { + void testClamp([Clamp=something] long foo); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "[Clamp] must take no arguments") + + parser = parser.reset() + parser.parse(""" + interface TestEnforceRange { + void testEnforceRange([EnforceRange] long foo); + void 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].enforceRange, + "Should be enforceRange") + harness.ok(not results[0].members[1].signatures()[0][1][0].enforceRange, + "Should not be enforceRange") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestEnforceRange2 { + void testEnforceRange([EnforceRange=something] long foo); + }; + """) + results = parser.finish() + except: + 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 000000000..718f09c11 --- /dev/null +++ b/dom/bindings/parser/tests/test_float_types.py @@ -0,0 +1,125 @@ +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; + + void 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] + void 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] + void m3(float arg); + [LenientFloat] + void m4(double arg); + [LenientFloat] + void m5((float or FloatTypes) arg); + [LenientFloat] + void 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, x: + threw = True + harness.ok(threw, "[LenientFloat] only allowed on void methods") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface FloatTypes { + [LenientFloat] + void m(unrestricted float arg); + }; + """) + except Exception, x: + 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] + void m(sequence<unrestricted float> arg); + }; + """) + except Exception, x: + 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] + void m((unrestricted float or FloatTypes) arg); + }; + """) + except Exception, x: + 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, x: + 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 000000000..cac24c832 --- /dev/null +++ b/dom/bindings/parser/tests/test_forward_decl.py @@ -0,0 +1,15 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + interface ForwardDeclared; + interface ForwardDeclared; + + interface TestForwardDecl { + attribute ForwardDeclared foo; + }; + """) + + results = 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 000000000..c752cecd2 --- /dev/null +++ b/dom/bindings/parser/tests/test_global_extended_attr.py @@ -0,0 +1,122 @@ +def WebIDLTest(parser, harness): + parser.parse(""" + [Global] + interface Foo : Bar { + getter any(DOMString name); + }; + 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] + interface Foo { + getter any(DOMString name); + setter void(DOMString name, any arg); + }; + """) + results = parser.finish() + except: + 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] + interface Foo { + getter any(DOMString name); + creator void(DOMString name, any arg); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Should have thrown for [Global] used on an interface with a " + "named creator") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + [Global] + interface Foo { + getter any(DOMString name); + deleter void(DOMString name); + }; + """) + results = parser.finish() + except: + 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, OverrideBuiltins] + interface Foo { + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Should have thrown for [Global] used on an interface with a " + "[OverrideBuiltins]") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + [Global] + interface Foo : Bar { + }; + [OverrideBuiltins] + interface Bar { + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Should have thrown for [Global] used on an interface with an " + "[OverrideBuiltins] ancestor") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + [Global] + interface Foo { + }; + interface Bar : Foo { + }; + """) + results = parser.finish() + except: + 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 000000000..b510a30c0 --- /dev/null +++ b/dom/bindings/parser/tests/test_identifier_conflict.py @@ -0,0 +1,39 @@ +# Import the WebIDL module, so we can do isinstance checks and whatnot +import WebIDL + +def WebIDLTest(parser, harness): + try: + parser.parse(""" + enum Foo { "a" }; + interface Foo; + """) + results = parser.finish() + harness.ok(False, "Should fail to parse") + except Exception, e: + harness.ok("Name collision" in e.message, + "Should have name collision for interface") + + parser = parser.reset() + try: + parser.parse(""" + dictionary Foo { long x; }; + enum Foo { "a" }; + """) + results = parser.finish() + harness.ok(False, "Should fail to parse") + except Exception, e: + harness.ok("Name collision" in e.message, + "Should have name collision for dictionary") + + parser = parser.reset() + try: + parser.parse(""" + enum Foo { "a" }; + enum Foo { "b" }; + """) + results = parser.finish() + harness.ok(False, "Should fail to parse") + except Exception, e: + harness.ok("Multiple unresolvable definitions" in e.message, + "Should have name collision for dictionary") + diff --git a/dom/bindings/parser/tests/test_implements.py b/dom/bindings/parser/tests/test_implements.py new file mode 100644 index 000000000..04c47d92a --- /dev/null +++ b/dom/bindings/parser/tests/test_implements.py @@ -0,0 +1,216 @@ +# Import the WebIDL module, so we can do isinstance checks and whatnot +import WebIDL + +def WebIDLTest(parser, harness): + # Basic functionality + threw = False + try: + parser.parse(""" + A implements B; + interface B { + attribute long x; + }; + interface A { + attribute long y; + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(not threw, "Should not have thrown on implements statement " + "before interfaces") + harness.check(len(results), 3, "We have three statements") + harness.ok(isinstance(results[1], WebIDL.IDLInterface), "B is an interface") + harness.check(len(results[1].members), 1, "B has one member") + A = results[2] + harness.ok(isinstance(A, WebIDL.IDLInterface), "A is an interface") + harness.check(len(A.members), 2, "A has two members") + harness.check(A.members[0].identifier.name, "y", "First member is 'y'") + harness.check(A.members[1].identifier.name, "x", "Second member is 'x'") + + # Duplicated member names not allowed + threw = False + try: + parser.parse(""" + C implements D; + interface D { + attribute long x; + }; + interface C { + attribute long x; + }; + """) + parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown on implemented interface duplicating " + "a name on base interface") + + # Same, but duplicated across implemented interfaces + threw = False + try: + parser.parse(""" + E implements F; + E implements G; + interface F { + attribute long x; + }; + interface G { + attribute long x; + }; + interface E {}; + """) + parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown on implemented interfaces " + "duplicating each other's member names") + + # Same, but duplicated across indirectly implemented interfaces + threw = False + try: + parser.parse(""" + H implements I; + H implements J; + I implements K; + interface K { + attribute long x; + }; + interface L { + attribute long x; + }; + interface I {}; + interface J : L {}; + interface H {}; + """) + parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown on indirectly implemented interfaces " + "duplicating each other's member names") + + # Same, but duplicated across an implemented interface and its parent + threw = False + try: + parser.parse(""" + M implements N; + interface O { + attribute long x; + }; + interface N : O { + attribute long x; + }; + interface M {}; + """) + parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown on implemented interface and its " + "ancestor duplicating member names") + + # Reset the parser so we can actually find things where we expect + # them in the list + parser = parser.reset() + + # Diamonds should be allowed + threw = False + try: + parser.parse(""" + P implements Q; + P implements R; + Q implements S; + R implements S; + interface Q {}; + interface R {}; + interface S { + attribute long x; + }; + interface P {}; + """) + results = parser.finish() + except: + threw = True + + harness.ok(not threw, "Diamond inheritance is fine") + harness.check(results[6].identifier.name, "S", "We should be looking at 'S'") + harness.check(len(results[6].members), 1, "S should have one member") + harness.check(results[6].members[0].identifier.name, "x", + "S's member should be 'x'") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestInterface { + }; + callback interface TestCallbackInterface { + }; + TestInterface implements TestCallbackInterface; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Should not allow callback interfaces on the right-hand side " + "of 'implements'") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestInterface { + }; + callback interface TestCallbackInterface { + }; + TestCallbackInterface implements TestInterface; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Should not allow callback interfaces on the left-hand side of " + "'implements'") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestInterface { + }; + dictionary Dict { + }; + Dict implements TestInterface; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Should not allow non-interfaces on the left-hand side " + "of 'implements'") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestInterface { + }; + dictionary Dict { + }; + TestInterface implements Dict; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Should not allow non-interfaces on the right-hand side " + "of 'implements'") + 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 000000000..1f520a28e --- /dev/null +++ b/dom/bindings/parser/tests/test_incomplete_parent.py @@ -0,0 +1,18 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + interface TestIncompleteParent : NotYetDefined { + void 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 000000000..fdc396040 --- /dev/null +++ b/dom/bindings/parser/tests/test_incomplete_types.py @@ -0,0 +1,44 @@ +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 000000000..e8ed67b54 --- /dev/null +++ b/dom/bindings/parser/tests/test_interface.py @@ -0,0 +1,405 @@ +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: + 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: + 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 implements B; + B implements A; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow cycles via implements") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A {}; + interface C {}; + interface B {}; + A implements C; + C implements B; + B implements A; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow indirect cycles via implements") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A : B {}; + interface B {}; + B implements A; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow inheriting from an interface that implements us") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A : B {}; + interface B {}; + interface C {}; + B implements C; + C implements A; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow inheriting from an interface that indirectly implements us") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A : B {}; + interface B : C {}; + interface C {}; + C implements A; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow indirectly inheriting from an interface that implements us") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A : B {}; + interface B : C {}; + interface C {}; + interface D {}; + C implements D; + D implements A; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow indirectly inheriting from an interface that indirectly implements us") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A; + interface B : A {}; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow inheriting from an interface that is only forward declared") + + parser = parser.reset() + parser.parse(""" + [Constructor(long arg)] + interface A { + readonly attribute boolean x; + void foo(); + }; + [Constructor] + partial interface A { + readonly attribute boolean y; + void 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(""" + [Constructor] + partial interface A { + readonly attribute boolean y; + void foo(long arg); + }; + [Constructor(long arg)] + interface A { + readonly attribute boolean x; + void 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: + 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: + 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: + 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: + 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: + 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: + 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: + threw = True + harness.ok(threw, + "Should not allow unknown extended attributes on interfaces") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface B {}; + [ArrayClass] + interface A : B { + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, + "Should not allow [ArrayClass] on interfaces with parents") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + [ArrayClass] + interface A { + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(not threw, + "Should allow [ArrayClass] on interfaces without parents") 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 000000000..db944e7aa --- /dev/null +++ b/dom/bindings/parser/tests/test_interface_const_identifier_conflicts.py @@ -0,0 +1,15 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface IdentifierConflict { + const byte thing1 = 1; + const unsigned long thing1 = 1; + }; + """) + + results = parser.finish() + except: + 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 000000000..1a73fb917 --- /dev/null +++ b/dom/bindings/parser/tests/test_interface_identifier_conflicts_across_members.py @@ -0,0 +1,60 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface IdentifierConflictAcrossMembers1 { + const byte thing1 = 1; + readonly attribute long thing1; + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface IdentifierConflictAcrossMembers2 { + readonly attribute long thing1; + const byte thing1 = 1; + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface IdentifierConflictAcrossMembers3 { + getter boolean thing1(DOMString name); + readonly attribute long thing1; + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface IdentifierConflictAcrossMembers1 { + const byte thing1 = 1; + long thing1(); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") 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 000000000..ee5d870c2 --- /dev/null +++ b/dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py @@ -0,0 +1,691 @@ +import WebIDL +import traceback +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 (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, e: + harness.ok(True, + prefix + " - Interface failed as expected") + except Exception, 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) + setROChromeMembers = ([(x, WebIDL.IDLMethod) for x in ["__add", + "__clear", + "__delete"]] + + setROMembers) + setRWChromeMembers = ([(x, WebIDL.IDLMethod) for x in ["__add", + "__clear", + "__delete"]] + + setRWMembers) + 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) + mapRWChromeMembers = ([(x, WebIDL.IDLMethod) for x in ["__set", + "__clear", + "__delete"]] + + mapRWMembers) + + # 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)) + + valueIterableMembers = [("__iterable", WebIDL.IDLIterable)] + valueIterableMembers.append(("__indexedgetter", WebIDL.IDLMethod)) + valueIterableMembers.append(("length", WebIDL.IDLAttribute)) + + disallowedIterableNames = ["keys", "entries", "values"] + disallowedMemberNames = ["forEach", "has", "size"] + disallowedIterableNames + mapDisallowedMemberNames = ["get"] + disallowedMemberNames + disallowedNonMethodNames = ["clear", "delete"] + mapDisallowedNonMethodNames = ["set"] + disallowedNonMethodNames + setDisallowedNonMethodNames = ["add"] + 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("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) + + shouldPass("Implements with maplike/setlike", + """ + interface Foo1 { + maplike<long, long>; + }; + interface Foo2 { + }; + Foo2 implements Foo1; + """, mapRWMembers, numProductions=3) + + shouldPass("JS Implemented maplike interface", + """ + [JSImplementation="@mozilla.org/dom/test-interface-js-maplike;1", + Constructor()] + interface Foo1 { + setlike<long>; + }; + """, setRWChromeMembers) + + shouldPass("JS Implemented maplike interface", + """ + [JSImplementation="@mozilla.org/dom/test-interface-js-maplike;1", + Constructor()] + interface Foo1 { + maplike<long, long>; + }; + """, mapRWChromeMembers) + + # + # 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 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>; + }; + """) + + shouldFail("Consequential interface with conflicting maplike/setlike", + """ + interface Foo1 { + maplike<long, long>; + }; + interface Foo2 { + setlike<long>; + }; + Foo2 implements Foo1; + """) + + shouldFail("Consequential interfaces with conflicting maplike/setlike", + """ + interface Foo1 { + maplike<long, long>; + }; + interface Foo2 { + setlike<long>; + }; + interface Foo3 { + }; + Foo3 implements Foo1; + Foo3 implements Foo2; + """) + + # + # Member name collision tests + # + + def testConflictingMembers(likeMember, conflictName, expectedMembers, methodPasses): + """ + 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. + + """ + if methodPasses: + shouldPass("Conflicting method: %s and %s" % (likeMember, conflictName), + """ + interface Foo1 { + %s; + [Throws] + void %s(long test1, double test2, double test3); + }; + """ % (likeMember, conflictName), expectedMembers) + else: + shouldFail("Conflicting method: %s and %s" % (likeMember, conflictName), + """ + interface Foo1 { + %s; + [Throws] + void %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 { + void %s(long test1, double test2, double test3); + }; + interface Foo2 : Foo1 { + %s; + }; + """ % (conflictName, likeMember)) + shouldFail("Conflicting static method: %s and %s" % (likeMember, conflictName), + """ + interface Foo1 { + %s; + static void %s(long test1, double test2, double test3); + }; + """ % (likeMember, conflictName)) + 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) + 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 { + void 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 { + void entries(); + }; + """, mapRWMembers, numProductions=3) + + shouldFail("Interface with consequential maplike/setlike interface member collision", + """ + interface Foo1 { + void entries(); + }; + interface Foo2 { + maplike<long, long>; + }; + Foo1 implements Foo2; + """) + + shouldFail("Maplike interface with consequential interface member collision", + """ + interface Foo1 { + maplike<long, long>; + }; + interface Foo2 { + void entries(); + }; + Foo1 implements Foo2; + """) + + shouldPass("Consequential Maplike interface with inherited interface member collision", + """ + interface Foo1 { + maplike<long, long>; + }; + interface Foo2 { + void entries(); + }; + interface Foo3 : Foo2 { + }; + Foo3 implements Foo1; + """, mapRWMembers, numProductions=4) + + shouldPass("Inherited Maplike interface with consequential interface member collision", + """ + interface Foo1 { + maplike<long, long>; + }; + interface Foo2 { + void entries(); + }; + interface Foo3 : Foo1 { + }; + Foo3 implements Foo2; + """, mapRWMembers, numProductions=4) + + shouldFail("Inheritance of name collision with child maplike/setlike", + """ + interface Foo1 { + void entries(); + }; + interface Foo2 : Foo1 { + maplike<long, long>; + }; + """) + + shouldFail("Inheritance of multi-level name collision with child maplike/setlike", + """ + interface Foo1 { + void 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 { + void clear(); + }; + """, mapRWMembers, numProductions=2) + + shouldFail("Inheritance of unforgeable attribute collision with child maplike/setlike", + """ + interface Foo1 { + [Unforgeable] + attribute double size; + }; + interface Foo2 : Foo1 { + maplike<long, long>; + }; + """) + + shouldFail("Inheritance of multi-level unforgeable attribute collision with child maplike/setlike", + """ + interface Foo1 { + [Unforgeable] + attribute double size; + }; + interface Foo2 : Foo1 { + }; + interface Foo3 : Foo2 { + maplike<long, long>; + }; + """) + + shouldPass("Implemented interface with readonly allowable overrides", + """ + interface Foo1 { + readonly setlike<long>; + readonly attribute boolean clear; + }; + """, setROMembers + [("clear", WebIDL.IDLAttribute)]) + + shouldPass("JS Implemented read-only interface with readonly allowable overrides", + """ + [JSImplementation="@mozilla.org/dom/test-interface-js-maplike;1", + Constructor()] + interface Foo1 { + readonly setlike<long>; + readonly attribute boolean clear; + }; + """, setROChromeMembers + [("clear", WebIDL.IDLAttribute)]) + + shouldFail("JS Implemented read-write interface with non-readwrite allowable overrides", + """ + [JSImplementation="@mozilla.org/dom/test-interface-js-maplike;1", + Constructor()] + interface Foo1 { + setlike<long>; + readonly attribute boolean clear; + }; + """) + + 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_lenientSetter.py b/dom/bindings/parser/tests/test_lenientSetter.py new file mode 100644 index 000000000..78a9ffe9e --- /dev/null +++ b/dom/bindings/parser/tests/test_lenientSetter.py @@ -0,0 +1,58 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +def should_throw(parser, harness, message, code): + parser = parser.reset(); + threw = False + try: + parser.parse(code) + parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown: %s" % message) + + +def WebIDLTest(parser, harness): + # The [LenientSetter] extended attribute MUST take no arguments. + should_throw(parser, harness, "no arguments", """ + interface I { + [LenientSetter=X] readonly attribute long A; + }; + """) + + # An attribute with the [LenientSetter] extended attribute MUST NOT + # also be declared with the [PutForwards] extended attribute. + should_throw(parser, harness, "PutForwards", """ + interface I { + [PutForwards=B, LenientSetter] readonly attribute J A; + }; + interface J { + attribute long B; + }; + """) + + # An attribute with the [LenientSetter] extended attribute MUST NOT + # also be declared with the [Replaceable] extended attribute. + should_throw(parser, harness, "Replaceable", """ + interface I { + [Replaceable, LenientSetter] readonly attribute J A; + }; + """) + + # The [LenientSetter] extended attribute MUST NOT be used on an + # attribute that is not read only. + should_throw(parser, harness, "writable attribute", """ + interface I { + [LenientSetter] attribute long A; + }; + """) + + # The [LenientSetter] extended attribute MUST NOT be used on a + # static attribute. + should_throw(parser, harness, "static attribute", """ + interface I { + [LenientSetter] 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 000000000..cf7f1b40d --- /dev/null +++ b/dom/bindings/parser/tests/test_method.py @@ -0,0 +1,178 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + interface TestMethods { + void basic(); + static void basicStatic(); + void basicWithSimpleArgs(boolean arg1, byte arg2, unsigned long arg3); + boolean basicBoolean(); + static boolean basicStaticBoolean(); + boolean basicBooleanWithSimpleArgs(boolean arg1, byte arg2, unsigned long arg3); + void optionalArg(optional byte? arg1, optional sequence<byte> arg2); + void variadicArg(byte?... arg1); + object getObject(); + void setObject(object arg1); + void 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, creator=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.isCreator(), creator, "Method has the correct creator 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", [("Void", [])]) + checkMethod(methods[1], "::TestMethods::basicStatic", "basicStatic", + [("Void", [])], static=True) + checkMethod(methods[2], "::TestMethods::basicWithSimpleArgs", + "basicWithSimpleArgs", + [("Void", + [("::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", + [("Void", + [("::TestMethods::optionalArg::arg1", "arg1", "ByteOrNull", True, False), + ("::TestMethods::optionalArg::arg2", "arg2", "ByteSequence", True, False)])]) + checkMethod(methods[7], "::TestMethods::variadicArg", + "variadicArg", + [("Void", + [("::TestMethods::variadicArg::arg1", "arg1", "ByteOrNull", True, True)])]) + checkMethod(methods[8], "::TestMethods::getObject", + "getObject", [("Object", [])]) + checkMethod(methods[9], "::TestMethods::setObject", + "setObject", + [("Void", + [("::TestMethods::setObject::arg1", "arg1", "Object", False, False)])]) + checkMethod(methods[10], "::TestMethods::setAny", + "setAny", + [("Void", + [("::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 { + void foo(optional float bar = 1); + }; + """) + results = parser.finish() + except Exception, x: + threw = True + harness.ok(not threw, "Should allow integer to float type corecion") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A { + [GetterThrows] void foo(); + }; + """) + results = parser.finish() + except Exception, x: + threw = True + harness.ok(threw, "Should not allow [GetterThrows] on methods") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A { + [SetterThrows] void foo(); + }; + """) + results = parser.finish() + except Exception, x: + threw = True + harness.ok(threw, "Should not allow [SetterThrows] on methods") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A { + [Throw] void foo(); + }; + """) + results = parser.finish() + except Exception, x: + threw = True + harness.ok(threw, "Should spell [Throws] correctly on methods") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A { + void __noSuchMethod__(); + }; + """) + results = parser.finish() + except Exception, x: + threw = True + harness.ok(threw, "Should not allow __noSuchMethod__ methods") diff --git a/dom/bindings/parser/tests/test_mozmap.py b/dom/bindings/parser/tests/test_mozmap.py new file mode 100644 index 000000000..1a36fdd62 --- /dev/null +++ b/dom/bindings/parser/tests/test_mozmap.py @@ -0,0 +1,39 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + dictionary Dict {}; + interface MozMapArg { + void foo(MozMap<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.isMozMap(), "Should have a MozMap type here") + harness.ok(args[0].type.inner.isDictionary(), + "Should have a dictionary inner type") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface MozMapVoidArg { + void foo(MozMap<void> arg); + }; + """) + + results = parser.finish() + except Exception,x: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/dom/bindings/parser/tests/test_namespace.py b/dom/bindings/parser/tests/test_namespace.py new file mode 100644 index 000000000..74533a177 --- /dev/null +++ b/dom/bindings/parser/tests/test_namespace.py @@ -0,0 +1,223 @@ +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, x: + 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, x: + 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, x: + 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, x: + 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, x: + 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, x: + 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, x: + 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, x: + 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, x: + 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 000000000..26785c6a2 --- /dev/null +++ b/dom/bindings/parser/tests/test_newobject.py @@ -0,0 +1,70 @@ +# Import the WebIDL module, so we can do isinstance checks and whatnot +import WebIDL + +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; + }; + """) + results = parser.finish() + except: + 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(); + }; + """) + results = parser.finish() + except: + 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; + }; + """) + results = parser.finish() + except: + 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; + }; + """) + results = parser.finish() + except: + 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 000000000..2b48b615d --- /dev/null +++ b/dom/bindings/parser/tests/test_nullable_equivalency.py @@ -0,0 +1,115 @@ +import WebIDL + +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'] or \ + (hasattr(type(type1), attr) and not callable(getattr(type1, attr))): + continue + + a1 = getattr(type1, attr) + + if callable(a1): + try: + v1 = a1() + except: + # Can't call a1 with no args, so skip this attriute. + continue + + try: + a2 = getattr(type2, attr) + except: + 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: + 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 000000000..961ff825e --- /dev/null +++ b/dom/bindings/parser/tests/test_nullable_void.py @@ -0,0 +1,14 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface NullableVoid { + void? foo(); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") 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 000000000..6217465ce --- /dev/null +++ b/dom/bindings/parser/tests/test_optional_constraints.py @@ -0,0 +1,30 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface OptionalConstraints1 { + void foo(optional byte arg1, byte arg2); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(not threw, + "Should not have thrown on non-optional argument following " + "optional argument.") + + parser = parser.reset() + parser.parse(""" + interface OptionalConstraints2 { + void 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 000000000..3c680ad52 --- /dev/null +++ b/dom/bindings/parser/tests/test_overload.py @@ -0,0 +1,60 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + interface TestOverloads { + void basic(); + void basic(long arg1); + boolean abitharder(TestOverloads foo); + boolean abitharder(boolean foo); + void abitharder(ArrayBuffer? foo); + void withVariadics(long... numbers); + void withVariadics(TestOverloads iface); + void withVariadics(long num, TestOverloads iface); + void optionalTest(); + void 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), "Void", "Expect a void retval") + harness.check(len(argumentSet), 0, "Expect an empty argument set") + + (retval, argumentSet) = signatures[1] + harness.check(str(retval), "Void", "Expect a void 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 000000000..55bc07680 --- /dev/null +++ b/dom/bindings/parser/tests/test_promise.py @@ -0,0 +1,63 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface _Promise {}; + interface A { + legacycaller Promise<any> foo(); + }; + """) + results = parser.finish() + + except: + threw = True + harness.ok(threw, + "Should not allow Promise return values for legacycaller.") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface _Promise {}; + interface A { + Promise<any> foo(); + long foo(long arg); + }; + """) + results = parser.finish(); + except: + 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 _Promise {}; + interface A { + long foo(long arg); + Promise<any> foo(); + }; + """) + results = parser.finish(); + except: + threw = True + harness.ok(threw, + "Should not allow overloads which have both Promise and " + "non-Promise return types.") + + parser = parser.reset() + parser.parse(""" + interface _Promise {}; + interface A { + Promise<any> foo(); + Promise<any> foo(long arg); + }; + """) + results = parser.finish(); + + harness.ok(True, + "Should allow overloads which only have Promise and return " + "types.") 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 000000000..d3932b54f --- /dev/null +++ b/dom/bindings/parser/tests/test_prototype_ident.py @@ -0,0 +1,80 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface TestIface { + static attribute boolean prototype; + }; + """) + results = parser.finish() + except: + 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(); + }; + """) + results = parser.finish() + except: + 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; + }; + """) + results = parser.finish() + except: + 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 000000000..86a1bf115 --- /dev/null +++ b/dom/bindings/parser/tests/test_putForwards.py @@ -0,0 +1,107 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface I { + [PutForwards=B] readonly attribute long A; + }; + """) + + results = parser.finish() + except: + 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 { + }; + """) + + results = parser.finish() + except: + 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; + }; + """) + + results = parser.finish() + except: + 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; + }; + """) + + results = parser.finish() + except: + 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; + }; + """) + + results = parser.finish() + except: + 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; + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/dom/bindings/parser/tests/test_replaceable.py b/dom/bindings/parser/tests/test_replaceable.py new file mode 100644 index 000000000..93ee42ed9 --- /dev/null +++ b/dom/bindings/parser/tests/test_replaceable.py @@ -0,0 +1,58 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +def should_throw(parser, harness, message, code): + parser = parser.reset(); + threw = False + try: + parser.parse(code) + parser.finish() + except: + 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 000000000..d3184c007 --- /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 000000000..084f19fa7 --- /dev/null +++ b/dom/bindings/parser/tests/test_securecontext_extended_attribute.py @@ -0,0 +1,332 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + [SecureContext] + interface TestSecureContextOnInterface { + const octet TEST_CONSTANT = 0; + readonly attribute byte testAttribute; + void testMethod(byte foo); + }; + partial interface TestSecureContextOnInterface { + const octet TEST_CONSTANT_2 = 0; + readonly attribute byte testAttribute2; + void 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; + void testMethod2(byte foo); + }; + [SecureContext] + interface TestSecureContextOnInterfaceAfterPartialInterface { + const octet TEST_CONSTANT = 0; + readonly attribute byte testAttribute; + void 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; + void testMethod(byte foo); + }; + [SecureContext] + partial interface TestSecureContextOnPartialInterface { + const octet TEST_CONSTANT_2 = 0; + readonly attribute byte testAttribute2; + void 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; + void testNonSecureMethod1(byte foo); + [SecureContext] + void testSecureMethod(byte foo); + void 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; + void testNonSecureMethod1(byte foo); + [SecureContext] + void testSecureMethod(byte foo); + void 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: + threw = True + harness.ok(threw, "[SecureContext] must take no arguments (testing on interface)") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestSecureContextForOverloads1 { + [SecureContext] + void testSecureMethod(byte foo); + }; + partial interface TestSecureContextForOverloads1 { + void testSecureMethod(byte foo, byte bar); + }; + """) + results = parser.finish() + except: + 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] + void testSecureMethod(byte foo); + }; + partial interface TestSecureContextForOverloads2 { + [SecureContext] + void testSecureMethod(byte foo, byte bar); + }; + """) + results = parser.finish() + except: + 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] + void testSecureMethod(byte foo); + }; + """) + results = parser.finish() + except: + 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] + void testSecureMethod(byte foo); + }; + """) + results = parser.finish() + except: + 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] + void testSecureMethod(byte foo); + }; + """) + results = parser.finish() + except: + 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 { + void testSecureMethod(byte foo); + }; + """) + results = parser.finish() + except: + threw = True + harness.ok(threw, "[SecureContext] must appear on interfaces that inherit from another [SecureContext] interface") + + # Test 'implements'. The behavior tested here may have to change depending + # on the resolution of https://github.com/heycam/webidl/issues/118 + parser = parser.reset() + parser.parse(""" + [SecureContext] + interface TestSecureContextInterfaceThatImplementsNonSecureContextInterface { + const octet TEST_CONSTANT = 0; + }; + interface TestNonSecureContextInterface { + const octet TEST_CONSTANT_2 = 0; + readonly attribute byte testAttribute2; + void testMethod2(byte foo); + }; + TestSecureContextInterfaceThatImplementsNonSecureContextInterface implements TestNonSecureContextInterface; + """) + results = parser.finish() + harness.check(len(results[0].members), 4, "TestSecureContextInterfaceThatImplementsNonSecureContextInterface should have two 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] interface should not be [SecureContext]") + harness.ok(results[0].members[2].getExtendedAttribute("SecureContext") is None, + "Attributes copied from non-[SecureContext] interface should not be [SecureContext]") + harness.ok(results[0].members[3].getExtendedAttribute("SecureContext") is None, + "Methods copied from non-[SecureContext] interface should not be [SecureContext]") + + # Test SecureContext and NoInterfaceObject + parser = parser.reset() + parser.parse(""" + [NoInterfaceObject, SecureContext] + interface TestSecureContextNoInterfaceObject { + void testSecureMethod(byte foo); + }; + """) + results = parser.finish() + harness.check(len(results[0].members), 1, "TestSecureContextNoInterfaceObject 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 000000000..5ea1743d3 --- /dev/null +++ b/dom/bindings/parser/tests/test_special_method_signature_mismatch.py @@ -0,0 +1,294 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch1 { + getter long long foo(long index); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch2 { + getter void foo(unsigned long index); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch3 { + getter boolean foo(unsigned long index, boolean extraArg); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch4 { + getter boolean foo(unsigned long... index); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch5 { + getter boolean foo(optional unsigned long index); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch6 { + getter boolean foo(); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch7 { + deleter long long foo(long index); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch9 { + deleter boolean foo(unsigned long index, boolean extraArg); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch10 { + deleter boolean foo(unsigned long... index); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch11 { + deleter boolean foo(optional unsigned long index); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch12 { + deleter boolean foo(); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch13 { + setter long long foo(long index, long long value); + }; + """) + + results = parser.finish() + except: + 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); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch16 { + setter boolean foo(unsigned long index, boolean... value); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch17 { + setter boolean foo(unsigned long index, optional boolean value); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch18 { + setter boolean foo(); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch20 { + creator long long foo(long index, long long value); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch22 { + creator boolean foo(unsigned long index, boolean value, long long extraArg); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch23 { + creator boolean foo(unsigned long index, boolean... value); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch24 { + creator boolean foo(unsigned long index, optional boolean value); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch25 { + creator boolean foo(); + }; + """) + + results = parser.finish() + except: + 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 000000000..5b45c3399 --- /dev/null +++ b/dom/bindings/parser/tests/test_special_methods.py @@ -0,0 +1,85 @@ +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); + creator long long (unsigned long index, long long value); + getter boolean (DOMString name); + setter boolean (DOMString name, boolean value); + creator boolean (DOMString name, boolean value); + deleter boolean (DOMString name); + }; + + interface SpecialMethodsCombination { + setter creator long long (unsigned long index, long long value); + getter deleter boolean (DOMString name); + setter creator boolean (DOMString name, boolean value); + }; + """) + + results = parser.finish() + + def checkMethod(method, QName, name, + static=False, getter=False, setter=False, creator=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.isCreator(), creator, "Method has the correct creator 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), 7, "Expect 7 members") + + checkMethod(iface.members[0], "::SpecialMethods::__indexedgetter", "__indexedgetter", + getter=True) + checkMethod(iface.members[1], "::SpecialMethods::__indexedsetter", "__indexedsetter", + setter=True) + checkMethod(iface.members[2], "::SpecialMethods::__indexedcreator", "__indexedcreator", + creator=True) + checkMethod(iface.members[3], "::SpecialMethods::__namedgetter", "__namedgetter", + getter=True) + checkMethod(iface.members[4], "::SpecialMethods::__namedsetter", "__namedsetter", + setter=True) + checkMethod(iface.members[5], "::SpecialMethods::__namedcreator", "__namedcreator", + creator=True) + checkMethod(iface.members[6], "::SpecialMethods::__nameddeleter", "__nameddeleter", + deleter=True) + + iface = results[1] + harness.check(len(iface.members), 3, "Expect 3 members") + + checkMethod(iface.members[0], "::SpecialMethodsCombination::__indexedsettercreator", + "__indexedsettercreator", setter=True, creator=True) + checkMethod(iface.members[1], "::SpecialMethodsCombination::__namedgetterdeleter", + "__namedgetterdeleter", getter=True, deleter=True) + checkMethod(iface.members[2], "::SpecialMethodsCombination::__namedsettercreator", + "__namedsettercreator", setter=True, creator=True) + + parser = parser.reset(); + + threw = False + try: + parser.parse( + """ + interface IndexedDeleter { + deleter void(unsigned long index); + }; + """) + parser.finish() + except: + 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 000000000..42e2c5bb7 --- /dev/null +++ b/dom/bindings/parser/tests/test_special_methods_uniqueness.py @@ -0,0 +1,62 @@ +import WebIDL + +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface SpecialMethodUniqueness1 { + getter deleter boolean (DOMString name); + getter boolean (DOMString name); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodUniqueness1 { + deleter boolean (DOMString name); + getter deleter boolean (DOMString name); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodUniqueness1 { + setter creator boolean (DOMString name); + creator boolean (DOMString name); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodUniqueness1 { + setter boolean (DOMString name); + creator setter boolean (DOMString name); + }; + """) + + results = parser.finish() + except: + 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 000000000..14c2c5226 --- /dev/null +++ b/dom/bindings/parser/tests/test_stringifier.py @@ -0,0 +1,46 @@ +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: + 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: + threw = True + + harness.ok(threw, "Should not allow a 'stringifier;' and a 'stringifier()'") + diff --git a/dom/bindings/parser/tests/test_treatNonCallableAsNull.py b/dom/bindings/parser/tests/test_treatNonCallableAsNull.py new file mode 100644 index 000000000..7a0bde8a6 --- /dev/null +++ b/dom/bindings/parser/tests/test_treatNonCallableAsNull.py @@ -0,0 +1,71 @@ +import WebIDL + +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: + 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: + threw = True + + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + + threw = False + try: + parser.parse(""" + [TreatNonCallableAsNull, TreatNonObjectAsNull] + callback Function = any(any... arguments); + """) + + results = parser.finish() + except: + 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 000000000..8921985c5 --- /dev/null +++ b/dom/bindings/parser/tests/test_typedef.py @@ -0,0 +1,76 @@ +def WebIDLTest(parser, harness): + parser.parse(""" + typedef long mylong; + typedef long? mynullablelong; + interface Foo { + const mylong X = 5; + const mynullablelong Y = 7; + const mynullablelong Z = null; + void foo(mylong arg); + }; + """) + + results = parser.finish() + + harness.check(results[2].members[1].type.name, "LongOrNull", + "Should expand typedefs") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + typedef long? mynullablelong; + interface Foo { + void foo(mynullablelong? Y); + }; + """) + results = parser.finish() + except: + 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: + 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: + 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_unenumerable_own_properties.py b/dom/bindings/parser/tests/test_unenumerable_own_properties.py new file mode 100644 index 000000000..d017d5ce0 --- /dev/null +++ b/dom/bindings/parser/tests/test_unenumerable_own_properties.py @@ -0,0 +1,64 @@ +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, x: + 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, x: + 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, x: + 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 000000000..3787e8c6a --- /dev/null +++ b/dom/bindings/parser/tests/test_unforgeable.py @@ -0,0 +1,253 @@ +def WebIDLTest(parser, harness): + parser.parse(""" + interface Child : Parent { + }; + interface Parent { + [Unforgeable] readonly attribute long foo; + }; + """) + + results = parser.finish() + harness.check(len(results), 2, + "Should be able to inherit from an interface with " + "[Unforgeable] properties.") + + parser = parser.reset(); + parser.parse(""" + interface Child : Parent { + const short foo = 10; + }; + interface Parent { + [Unforgeable] readonly attribute long foo; + }; + """) + + results = parser.finish() + harness.check(len(results), 2, + "Should be able to inherit from an interface with " + "[Unforgeable] 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 { + [Unforgeable] readonly attribute long foo; + }; + """) + + results = parser.finish() + harness.check(len(results), 2, + "Should be able to inherit from an interface with " + "[Unforgeable] properties even if we have a static attribute " + "with the same name.") + + parser = parser.reset(); + parser.parse(""" + interface Child : Parent { + static void foo(); + }; + interface Parent { + [Unforgeable] readonly attribute long foo; + }; + """) + + results = parser.finish() + harness.check(len(results), 2, + "Should be able to inherit from an interface with " + "[Unforgeable] properties even if we have a static operation " + "with the same name.") + + parser = parser.reset(); + threw = False + try: + parser.parse(""" + interface Child : Parent { + void foo(); + }; + interface Parent { + [Unforgeable] readonly attribute long foo; + }; + """) + + results = parser.finish() + except: + 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 { + void foo(); + }; + interface Parent { + [Unforgeable] void foo(); + }; + """) + + results = parser.finish() + except: + 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 { + [Unforgeable] readonly attribute long foo; + }; + """) + + results = parser.finish() + except Exception,x: + 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 { + [Unforgeable] void foo(); + }; + """) + + results = parser.finish() + except Exception,x: + 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 Consequential { + [Unforgeable] readonly attribute long foo; + }; + Parent implements Consequential; + """) + + results = parser.finish() + harness.check(len(results), 4, + "Should be able to inherit from an interface with a " + "consequential interface with [Unforgeable] properties.") + + parser = parser.reset(); + threw = False + try: + parser.parse(""" + interface Child : Parent { + void foo(); + }; + interface Parent {}; + interface Consequential { + [Unforgeable] readonly attribute long foo; + }; + Parent implements Consequential; + """) + + results = parser.finish() + except: + 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 Consequential { + [Unforgeable] readonly attribute long foo; + }; + GrandParent implements Consequential; + interface ChildConsequential { + void foo(); + }; + Child implements ChildConsequential; + """) + + results = parser.finish() + except: + 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 Consequential { + [Unforgeable] void foo(); + }; + GrandParent implements Consequential; + interface ChildConsequential { + void foo(); + }; + Child implements ChildConsequential; + """) + + results = parser.finish() + except: + 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 { + [Unforgeable] attribute long foo; + }; + """) + + results = parser.finish() + harness.check(len(results), 1, + "Should allow writable [Unforgeable] attribute.") + + parser = parser.reset(); + threw = False + try: + parser.parse(""" + interface iface { + [Unforgeable] static readonly attribute long foo; + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown for static [Unforgeable] attribute.") diff --git a/dom/bindings/parser/tests/test_union.py b/dom/bindings/parser/tests/test_union.py new file mode 100644 index 000000000..9c4f2a56a --- /dev/null +++ b/dom/bindings/parser/tests/test_union.py @@ -0,0 +1,168 @@ +import WebIDL +import itertools +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 = 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(""" + void method${i}(${type} arg); + ${type} returnMethod${i}(); + attribute ${type} attr${i}; + void 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 { + void method(${type} arg); + }; + """).substitute(type=invalid) + + threw = False + try: + parser.parse(interface) + results = parser.finish() + except: + 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 000000000..e34cadab4 --- /dev/null +++ b/dom/bindings/parser/tests/test_union_any.py @@ -0,0 +1,14 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface AnyNotInUnion { + void foo((any or DOMString) arg); + }; + """) + + results = parser.finish() + except: + 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 000000000..08430a94a --- /dev/null +++ b/dom/bindings/parser/tests/test_union_nullable.py @@ -0,0 +1,53 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface OneNullableInUnion { + void foo((object? or DOMString?) arg); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Two nullable member types of a union should have thrown.") + + parser.reset() + threw = False + + try: + parser.parse(""" + interface NullableInNullableUnion { + void foo((object? or DOMString)? arg); + }; + """) + + results = parser.finish() + except: + 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 { + void foo(((object? or DOMString) or NullableInUnionNullableUnionHelper)? arg); + }; + """) + + results = parser.finish() + except: + 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 000000000..3a1369abd --- /dev/null +++ b/dom/bindings/parser/tests/test_usvstring.py @@ -0,0 +1,36 @@ +# -*- 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 000000000..d9a78db20 --- /dev/null +++ b/dom/bindings/parser/tests/test_variadic_callback.py @@ -0,0 +1,10 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + callback TestVariadicCallback = any(any... arguments); + """) + + results = 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 000000000..7448e40d5 --- /dev/null +++ b/dom/bindings/parser/tests/test_variadic_constraints.py @@ -0,0 +1,63 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface VariadicConstraints1 { + void foo(byte... arg1, byte arg2); + }; + """) + results = parser.finish() + + except: + 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 { + void foo(byte... arg1, optional byte arg2); + }; + """) + results = parser.finish(); + except: + 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 { + void foo(optional byte... arg1); + }; + """) + results = parser.finish() + + except: + 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 { + void foo(byte... arg1 = 0); + }; + """) + results = parser.finish() + except: + 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 000000000..844a51c27 --- /dev/null +++ b/dom/bindings/test/Makefile.in @@ -0,0 +1,21 @@ +# 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 + +check:: + PYTHONDONTWRITEBYTECODE=1 $(PYTHON) $(topsrcdir)/config/pythonpath.py \ + $(PLY_INCLUDE) $(srcdir)/../parser/runtests.py diff --git a/dom/bindings/test/TestBindingHeader.h b/dom/bindings/test/TestBindingHeader.h new file mode 100644 index 000000000..ca5aafdc5 --- /dev/null +++ b/dom/bindings/test/TestBindingHeader.h @@ -0,0 +1,1431 @@ +/* -*- 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/Date.h" +#include "mozilla/dom/MozMap.h" +#include "mozilla/dom/TypedArray.h" +#include "mozilla/ErrorResult.h" +#include "nsCOMPtr.h" +#include "nsGenericHTMLElement.h" +#include "nsWrapperCache.h" + +// 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 TestExternalInterface; +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 IndirectlyImplementedInterface +#define NS_INDIRECTLY_IMPLEMENTED_INTERFACE_IID \ +{ 0xfed55b69, 0x7012, 0x4849, \ + { 0xaf, 0x56, 0x4b, 0xa9, 0xee, 0x41, 0x30, 0x89 } } + +class IndirectlyImplementedInterface : public nsISupports, + public nsWrapperCache +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_INDIRECTLY_IMPLEMENTED_INTERFACE_IID) + NS_DECL_ISUPPORTS + + // We need a GetParentObject to make binding codegen happy + virtual nsISupports* GetParentObject(); + + bool IndirectlyImplementedProperty(); + void IndirectlyImplementedProperty(bool); + void IndirectlyImplementedMethod(); +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IndirectlyImplementedInterface, NS_INDIRECTLY_IMPLEMENTED_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 to make binding codegen happy + virtual nsISupports* GetParentObject(); + + // And now our actual WebIDL API + // Constructors + static + already_AddRefed<TestInterface> + Constructor(const GlobalObject&, ErrorResult&); + static + already_AddRefed<TestInterface> + Constructor(const GlobalObject&, const nsAString&, ErrorResult&); + static + already_AddRefed<TestInterface> + Constructor(const GlobalObject&, uint32_t, const Nullable<bool>&, + ErrorResult&); + static + already_AddRefed<TestInterface> + Constructor(const GlobalObject&, TestInterface*, ErrorResult&); + static + already_AddRefed<TestInterface> + Constructor(const GlobalObject&, uint32_t, IndirectlyImplementedInterface&, ErrorResult&); + + static + already_AddRefed<TestInterface> + Constructor(const GlobalObject&, Date&, ErrorResult&); + static + already_AddRefed<TestInterface> + Constructor(const GlobalObject&, const ArrayBuffer&, ErrorResult&); + static + already_AddRefed<TestInterface> + Constructor(const GlobalObject&, const Uint8Array&, ErrorResult&); + /* static + already_AddRefed<TestInterface> + Constructor(const GlobalObject&, uint32_t, uint32_t, + const TestInterfaceOrOnlyForUseInConstructor&, ErrorResult&); + */ + + 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 LongOrAnyMozMap&, + 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(); + int8_t SetSideEffectFreeByte(int8_t); + int8_t DomDependentByte(); + int8_t 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(Nullable<float>); + Nullable<float> GetWritableNullableUnrestrictedFloat() const; + void SetWritableNullableUnrestrictedFloat(Nullable<float>); + double WritableDouble() const; + void SetWritableDouble(double); + double WritableUnrestrictedDouble() const; + void SetWritableUnrestrictedDouble(double); + Nullable<double> GetWritableNullableDouble() const; + void SetWritableNullableDouble(Nullable<double>); + Nullable<double> GetWritableNullableUnrestrictedDouble() const; + void SetWritableNullableUnrestrictedDouble(Nullable<double>); + void PassFloat(float, float, Nullable<float>, Nullable<float>, + double, double, Nullable<double>, 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, Nullable<float>, Nullable<float>, + double, double, Nullable<double>, 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<IndirectlyImplementedInterface> ReceiveOther(); + already_AddRefed<IndirectlyImplementedInterface> ReceiveNullableOther(); + IndirectlyImplementedInterface* ReceiveWeakOther(); + IndirectlyImplementedInterface* ReceiveWeakNullableOther(); + void PassOther(IndirectlyImplementedInterface&); + void PassNullableOther(IndirectlyImplementedInterface*); + already_AddRefed<IndirectlyImplementedInterface> NonNullOther(); + void SetNonNullOther(IndirectlyImplementedInterface&); + already_AddRefed<IndirectlyImplementedInterface> GetNullableOther(); + void SetNullableOther(IndirectlyImplementedInterface*); + void PassOptionalOther(const Optional<IndirectlyImplementedInterface*>&); + void PassOptionalNonNullOther(const Optional<NonNull<IndirectlyImplementedInterface> >&); + void PassOptionalOtherWithDefault(IndirectlyImplementedInterface*); + + 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*); + + already_AddRefed<IndirectlyImplementedInterface> ReceiveConsequentialInterface(); + void PassConsequentialInterface(IndirectlyImplementedInterface&); + + // 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 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>>>&); + + // MozMap types + void PassMozMap(const MozMap<int32_t> &); + void PassNullableMozMap(const Nullable< MozMap<int32_t> >&); + void PassMozMapOfNullableInts(const MozMap<Nullable<int32_t> >&); + void PassOptionalMozMapOfNullableInts(const Optional<MozMap<Nullable<int32_t> > > &); + void PassOptionalNullableMozMapOfNullableInts(const Optional<Nullable<MozMap<Nullable<int32_t> > > > &); + void PassCastableObjectMozMap(const MozMap< OwningNonNull<TestInterface> >&); + void PassNullableCastableObjectMozMap(const MozMap< RefPtr<TestInterface> > &); + void PassCastableObjectNullableMozMap(const Nullable< MozMap< OwningNonNull<TestInterface> > >&); + void PassNullableCastableObjectNullableMozMap(const Nullable< MozMap< RefPtr<TestInterface> > >&); + void PassOptionalMozMap(const Optional<MozMap<int32_t> >&); + void PassOptionalNullableMozMap(const Optional<Nullable<MozMap<int32_t> > >&); + void PassOptionalNullableMozMapWithDefaultValue(const Nullable< MozMap<int32_t> >&); + void PassOptionalObjectMozMap(const Optional<MozMap<OwningNonNull<TestInterface> > >&); + void PassExternalInterfaceMozMap(const MozMap<RefPtr<TestExternalInterface> >&); + void PassNullableExternalInterfaceMozMap(const MozMap<RefPtr<TestExternalInterface> >&); + void PassStringMozMap(const MozMap<nsString>&); + void PassByteStringMozMap(const MozMap<nsCString>&); + void PassMozMapOfMozMaps(const MozMap< MozMap<int32_t> >&); + void ReceiveMozMap(MozMap<int32_t>&); + void ReceiveNullableMozMap(Nullable<MozMap<int32_t>>&); + void ReceiveMozMapOfNullableInts(MozMap<Nullable<int32_t>>&); + void ReceiveNullableMozMapOfNullableInts(Nullable<MozMap<Nullable<int32_t>>>&); + void ReceiveMozMapOfMozMaps(MozMap<MozMap<int32_t>>&); + void ReceiveAnyMozMap(JSContext*, MozMap<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 PassMozMapOfArrayBuffers(const MozMap<ArrayBuffer>&); + void PassMozMapOfNullableArrayBuffers(const MozMap<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&); + + // 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&); + + // 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&); + + // 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 PassMozMapOfAny(JSContext*, const MozMap<JS::Value>&); + void PassNullableMozMapOfAny(JSContext*, const Nullable<MozMap<JS::Value> >&); + void PassOptionalMozMapOfAny(JSContext*, const Optional<MozMap<JS::Value> >&); + void PassOptionalNullableMozMapOfAny(JSContext*, const Optional<Nullable<MozMap<JS::Value> > >&); + void PassOptionalMozMapOfAnyWithDefaultValue(JSContext*, const Nullable<MozMap<JS::Value> >&); + void PassMozMapOfMozMapOfAny(JSContext*, const MozMap<MozMap<JS::Value> >&); + void PassMozMapOfNullableMozMapOfAny(JSContext*, const MozMap<Nullable<MozMap<JS::Value> > >&); + void PassNullableMozMapOfNullableMozMapOfAny(JSContext*, const Nullable<MozMap<Nullable<MozMap<JS::Value> > > >&); + void PassOptionalNullableMozMapOfNullableMozMapOfAny(JSContext*, const Optional<Nullable<MozMap<Nullable<MozMap<JS::Value>>>>>&); + void PassOptionalNullableMozMapOfNullableSequenceOfAny(JSContext*, const Optional<Nullable<MozMap<Nullable<Sequence<JS::Value>>>>>&); + void PassOptionalNullableSequenceOfNullableMozMapOfAny(JSContext*, const Optional<Nullable<Sequence<Nullable<MozMap<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 PassMozMapOfObject(JSContext*, const MozMap<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 LongMozMapOrLong&); + void PassUnion22(JSContext*, const ObjectMozMapOrLong&); + 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 PassUnionWithMozMap(const StringMozMapOrString&); + void PassUnionWithMozMapAndSequence(const StringMozMapOrStringSequence&); + void PassUnionWithSequenceAndMozMap(const StringSequenceOrStringMozMap&); + 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 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 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 PassMozMapOfUnions(const MozMap<OwningCanvasPatternOrCanvasGradient>&); + void PassMozMapOfUnions2(JSContext*, const MozMap<OwningObjectOrLong>&); + + void ReceiveUnion(OwningCanvasPatternOrCanvasGradient&); + void ReceiveUnion2(JSContext*, OwningObjectOrLong&); + void ReceiveUnionContainingNull(OwningCanvasPatternOrNullOrCanvasGradient&); + void ReceiveNullableUnion(Nullable<OwningCanvasPatternOrCanvasGradient>&); + void ReceiveNullableUnion2(JSContext*, Nullable<OwningObjectOrLong>&); + void GetWritableUnion(OwningCanvasPatternOrCanvasGradient&); + void SetWritableUnion(const CanvasPatternOrCanvasGradient&); + void GetWritableUnionContainingNull(OwningCanvasPatternOrNullOrCanvasGradient&); + void SetWritableUnionContainingNull(const CanvasPatternOrNullOrCanvasGradient&); + void GetWritableNullableUnion(Nullable<OwningCanvasPatternOrCanvasGradient>&); + void SetWritableNullableUnion(const Nullable<CanvasPatternOrCanvasGradient>&); + + // Date types + void PassDate(Date); + void PassNullableDate(const Nullable<Date>&); + void PassOptionalDate(const Optional<Date>&); + void PassOptionalNullableDate(const Optional<Nullable<Date> >&); + void PassOptionalNullableDateWithDefaultValue(const Nullable<Date>&); + void PassDateSequence(const Sequence<Date>&); + void PassDateMozMap(const MozMap<Date>&); + void PassNullableDateSequence(const Sequence<Nullable<Date> >&); + Date ReceiveDate(); + Nullable<Date> ReceiveNullableDate(); + + // Promise types + void PassPromise(Promise&); + void PassNullablePromise(Promise*); + void PassOptionalPromise(const Optional<OwningNonNull<Promise>>&); + void PassOptionalNullablePromise(const Optional<RefPtr<Promise>>&); + void PassOptionalNullablePromiseWithDefaultValue(Promise*); + void PassPromiseSequence(const Sequence<OwningNonNull<Promise>>&); + void PassPromiseMozMap(const MozMap<RefPtr<Promise>>&); + void PassNullablePromiseSequence(const Sequence<RefPtr<Promise>> &); + Promise* ReceivePromise(); + already_AddRefed<Promise> ReceiveAddrefedPromise(); + + // binaryNames tests + void MethodRenamedTo(); + void OtherMethodRenamedTo(); + void MethodRenamedTo(int8_t); + int8_t AttributeGetterRenamedTo(); + int8_t AttributeRenamedTo(); + void SetAttributeRenamedTo(int8_t); + int8_t OtherAttributeRenamedTo(); + void SetOtherAttributeRenamedTo(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 PassMozMapOfDictionaries(const MozMap<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(); + int8_t 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 int8_t SetStaticDeprecatedAttribute(const GlobalObject&, int8_t); + static int8_t StaticDeprecatedMethod(const GlobalObject&); + static int8_t 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 Overload2(Date); + 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 MozMap<int32_t>&); + void Overload18(const MozMap<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(); + + // 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 GetJsonifierShouldSkipThis(JSContext*, JS::MutableHandle<JS::Value>); + void SetJsonifierShouldSkipThis(JSContext*, JS::Rooted<JS::Value>&); + TestParentInterface* JsonifierShouldSkipThis2(); + void SetJsonifierShouldSkipThis2(TestParentInterface&); + TestCallbackInterface* JsonifierShouldSkipThis3(); + void SetJsonifierShouldSkipThis3(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 NeedsSubjectPrincipalMethod(nsIPrincipal&); + bool NeedsSubjectPrincipalAttr(nsIPrincipal&); + void SetNeedsSubjectPrincipalAttr(bool, nsIPrincipal&); + void NeedsCallerTypeMethod(CallerType); + bool NeedsCallerTypeAttr(CallerType); + void SetNeedsCallerTypeAttr(bool, CallerType); + 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(); + + // Methods and properties imported via "implements" + bool ImplementedProperty(); + void SetImplementedProperty(bool); + void ImplementedMethod(); + bool ImplementedParentProperty(); + void SetImplementedParentProperty(bool); + void ImplementedParentMethod(); + bool IndirectlyImplementedProperty(); + void SetIndirectlyImplementedProperty(bool); + void IndirectlyImplementedMethod(); + uint32_t DiamondImplementedProperty(); + + // Test EnforceRange/Clamp + void DontEnforceRangeOrClamp(int8_t); + void DoEnforceRange(int8_t); + void DoClamp(int8_t); + void SetEnforcedByte(int8_t); + int8_t EnforcedByte(); + void SetClampedByte(int8_t); + int8_t ClampedByte(); + +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 PassOptionalOther(Optional<IndirectlyImplementedInterface*>&); + void PassOptionalNonNullOther(Optional<NonNull<IndirectlyImplementedInterface> >&); + 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; + + // 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 various date stuff is const as needed + void PassNullableDate(Nullable<Date>&) = delete; + void PassOptionalDate(Optional<Date>&) = delete; + void PassOptionalNullableDate(Optional<Nullable<Date> >&) = delete; + void PassOptionalNullableDateWithDefaultValue(Nullable<Date>&) = delete; + void PassDateSequence(Sequence<Date>&) = delete; + void PassNullableDateSequence(Sequence<Nullable<Date> >&) = 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 PassOther(NonNull<IndirectlyImplementedInterface>&) = delete; + void PassOther(const NonNull<IndirectlyImplementedInterface>&) = delete; + void PassOther(OwningNonNull<IndirectlyImplementedInterface>&) = delete; + void PassOther(const OwningNonNull<IndirectlyImplementedInterface>&) = 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>); +}; + +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&, ErrorResult&); + + 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&, ErrorResult&); + + 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); +}; + +} // 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 000000000..64b5c9954 --- /dev/null +++ b/dom/bindings/test/TestCImplementedInterface.h @@ -0,0 +1,43 @@ +/* -*- 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, + nsIGlobalObject* aParent) + : TestJSImplInterface(aJSImpl, 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/TestCodeGen.webidl b/dom/bindings/test/TestCodeGen.webidl new file mode 100644 index 000000000..4fb9be270 --- /dev/null +++ b/dom/bindings/test/TestCodeGen.webidl @@ -0,0 +1,1264 @@ +/* -*- 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; + +interface TestExternalInterface; + +[Pref="xyz"] +interface TestRenamedInterface { +}; + +callback interface TestCallbackInterface { + readonly attribute long foo; + attribute DOMString bar; + void doSomething(); + long doSomethingElse(DOMString arg, TestInterface otherArg); + void doSequenceLongArg(sequence<long> arg); + void doSequenceStringArg(sequence<DOMString> arg); + void doMozMapLongArg(MozMap<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(); + MozMap<long> getMozMapOfLong(); + Dict? getDictionary(); + void passArrayBuffer(ArrayBuffer arg); + void passNullableArrayBuffer(ArrayBuffer? arg); + void passOptionalArrayBuffer(optional ArrayBuffer arg); + void passOptionalNullableArrayBuffer(optional ArrayBuffer? arg); + void passOptionalNullableArrayBufferWithDefaultValue(optional ArrayBuffer? arg= null); + void passArrayBufferView(ArrayBufferView arg); + void passInt8Array(Int8Array arg); + void passInt16Array(Int16Array arg); + void passInt32Array(Int32Array arg); + void passUint8Array(Uint8Array arg); + void passUint16Array(Uint16Array arg); + void passUint32Array(Uint32Array arg); + void passUint8ClampedArray(Uint8ClampedArray arg); + void passFloat32Array(Float32Array arg); + void passFloat64Array(Float64Array arg); + void passSequenceOfArrayBuffers(sequence<ArrayBuffer> arg); + void passSequenceOfNullableArrayBuffers(sequence<ArrayBuffer?> arg); + void passVariadicTypedArray(Float32Array... arg); + void passVariadicNullableTypedArray(Float32Array?... arg); + Uint8Array receiveUint8Array(); + attribute Uint8Array uint8ArrayAttr; + Promise<void> receivePromise(); +}; + +callback interface TestSingleOperationCallbackInterface { + TestInterface doSomething(short arg, sequence<double> anotherArg); +}; + +enum TestEnum { + "1", + "a", + "b" +}; + +callback TestCallback = void(); +[TreatNonCallableAsNull] callback TestTreatAsNullCallback = void(); + +// 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 = void(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 = void(DOMString myString, DOMString? nullString, + TestEnum myEnum); +callback TestObjectArguments = void(object anObj, object? anotherObj, + ArrayBuffer buf, ArrayBuffer? buf2); +callback TestOptionalArguments = void(optional DOMString aString, + optional object something, + optional sequence<TestInterface> aSeq, + optional TestInterface? anInterface, + optional TestInterface anotherInterface, + optional long aLong); +// If you add a new test callback, add it to the forceCallbackGeneration +// method on TestInterface so it actually gets tested. + +TestInterface implements ImplementedInterface; + +// This interface is only for use in the constructor below +interface OnlyForUseInConstructor { +}; + +[Constructor, + Constructor(DOMString str), + Constructor(unsigned long num, boolean? boolArg), + Constructor(TestInterface? iface), + Constructor(long arg1, IndirectlyImplementedInterface iface), + Constructor(Date arg1), + Constructor(ArrayBuffer arrayBuf), + Constructor(Uint8Array typedArr), + // Constructor(long arg1, long arg2, (TestInterface or OnlyForUseInConstructor) arg3), + NamedConstructor=Test, + NamedConstructor=Test(DOMString str), + NamedConstructor=Test2(DictForConstructor dict, any any1, object obj1, + object? obj2, sequence<Dict> seq, optional any any2, + optional object obj3, optional object? obj4), + NamedConstructor=Test3((long or MozMap<any>) arg1) + ] +interface TestInterface { + // Integer types + // XXXbz add tests for throwing versions of all the integer stuff + readonly attribute byte readonlyByte; + attribute byte writableByte; + void passByte(byte arg); + byte receiveByte(); + void passOptionalByte(optional byte arg); + void passOptionalByteBeforeRequired(optional byte arg1, byte arg2); + void passOptionalByteWithDefault(optional byte arg = 0); + void passOptionalByteWithDefaultBeforeRequired(optional byte arg1 = 0, byte arg2); + void passNullableByte(byte? arg); + void passOptionalNullableByte(optional byte? arg); + void 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(); + + [UnsafeInPrerendering] + void unsafePrerenderMethod(); + [UnsafeInPrerendering] + attribute long unsafePrerenderWritable; + [UnsafeInPrerendering] + readonly attribute long unsafePrerenderReadonly; + readonly attribute short readonlyShort; + attribute short writableShort; + void passShort(short arg); + short receiveShort(); + void passOptionalShort(optional short arg); + void passOptionalShortWithDefault(optional short arg = 5); + + readonly attribute long readonlyLong; + attribute long writableLong; + void passLong(long arg); + long receiveLong(); + void passOptionalLong(optional long arg); + void passOptionalLongWithDefault(optional long arg = 7); + + readonly attribute long long readonlyLongLong; + attribute long long writableLongLong; + void passLongLong(long long arg); + long long receiveLongLong(); + void passOptionalLongLong(optional long long arg); + void passOptionalLongLongWithDefault(optional long long arg = -12); + + readonly attribute octet readonlyOctet; + attribute octet writableOctet; + void passOctet(octet arg); + octet receiveOctet(); + void passOptionalOctet(optional octet arg); + void passOptionalOctetWithDefault(optional octet arg = 19); + + readonly attribute unsigned short readonlyUnsignedShort; + attribute unsigned short writableUnsignedShort; + void passUnsignedShort(unsigned short arg); + unsigned short receiveUnsignedShort(); + void passOptionalUnsignedShort(optional unsigned short arg); + void passOptionalUnsignedShortWithDefault(optional unsigned short arg = 2); + + readonly attribute unsigned long readonlyUnsignedLong; + attribute unsigned long writableUnsignedLong; + void passUnsignedLong(unsigned long arg); + unsigned long receiveUnsignedLong(); + void passOptionalUnsignedLong(optional unsigned long arg); + void passOptionalUnsignedLongWithDefault(optional unsigned long arg = 6); + + readonly attribute unsigned long long readonlyUnsignedLongLong; + attribute unsigned long long writableUnsignedLongLong; + void passUnsignedLongLong(unsigned long long arg); + unsigned long long receiveUnsignedLongLong(); + void passOptionalUnsignedLongLong(optional unsigned long long arg); + void 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; + void 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] + void 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; + + void 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(); + void passSelf(TestInterface arg); + void passNullableSelf(TestInterface? arg); + attribute TestInterface nonNullSelf; + attribute TestInterface? nullableSelf; + [Cached, Pure] + readonly attribute TestInterface cachedSelf; + // Optional arguments + void passOptionalSelf(optional TestInterface? arg); + void passOptionalNonNullSelf(optional TestInterface arg); + void 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(); + + // Non-castable interface types + IndirectlyImplementedInterface receiveOther(); + IndirectlyImplementedInterface? receiveNullableOther(); + IndirectlyImplementedInterface receiveWeakOther(); + IndirectlyImplementedInterface? receiveWeakNullableOther(); + void passOther(IndirectlyImplementedInterface arg); + void passNullableOther(IndirectlyImplementedInterface? arg); + attribute IndirectlyImplementedInterface nonNullOther; + attribute IndirectlyImplementedInterface? nullableOther; + // Optional arguments + void passOptionalOther(optional IndirectlyImplementedInterface? arg); + void passOptionalNonNullOther(optional IndirectlyImplementedInterface arg); + void passOptionalOtherWithDefault(optional IndirectlyImplementedInterface? arg = null); + + // External interface types + TestExternalInterface receiveExternal(); + TestExternalInterface? receiveNullableExternal(); + TestExternalInterface receiveWeakExternal(); + TestExternalInterface? receiveWeakNullableExternal(); + void passExternal(TestExternalInterface arg); + void passNullableExternal(TestExternalInterface? arg); + attribute TestExternalInterface nonNullExternal; + attribute TestExternalInterface? nullableExternal; + // Optional arguments + void passOptionalExternal(optional TestExternalInterface? arg); + void passOptionalNonNullExternal(optional TestExternalInterface arg); + void passOptionalExternalWithDefault(optional TestExternalInterface? arg = null); + + // Callback interface types + TestCallbackInterface receiveCallbackInterface(); + TestCallbackInterface? receiveNullableCallbackInterface(); + TestCallbackInterface receiveWeakCallbackInterface(); + TestCallbackInterface? receiveWeakNullableCallbackInterface(); + void passCallbackInterface(TestCallbackInterface arg); + void passNullableCallbackInterface(TestCallbackInterface? arg); + attribute TestCallbackInterface nonNullCallbackInterface; + attribute TestCallbackInterface? nullableCallbackInterface; + // Optional arguments + void passOptionalCallbackInterface(optional TestCallbackInterface? arg); + void passOptionalNonNullCallbackInterface(optional TestCallbackInterface arg); + void passOptionalCallbackInterfaceWithDefault(optional TestCallbackInterface? arg = null); + + // Miscellaneous interface tests + IndirectlyImplementedInterface receiveConsequentialInterface(); + void passConsequentialInterface(IndirectlyImplementedInterface arg); + + // 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(); + void passSequence(sequence<long> arg); + void passNullableSequence(sequence<long>? arg); + void passSequenceOfNullableInts(sequence<long?> arg); + void passOptionalSequenceOfNullableInts(optional sequence<long?> arg); + void 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(); + void passCastableObjectSequence(sequence<TestInterface> arg); + void passNullableCastableObjectSequence(sequence<TestInterface?> arg); + void passCastableObjectNullableSequence(sequence<TestInterface>? arg); + void passNullableCastableObjectNullableSequence(sequence<TestInterface?>? arg); + void passOptionalSequence(optional sequence<long> arg); + void passOptionalSequenceWithDefaultValue(optional sequence<long> arg = []); + void passOptionalNullableSequence(optional sequence<long>? arg); + void passOptionalNullableSequenceWithDefaultValue(optional sequence<long>? arg = null); + void passOptionalNullableSequenceWithDefaultValue2(optional sequence<long>? arg = []); + void passOptionalObjectSequence(optional sequence<TestInterface> arg); + void passExternalInterfaceSequence(sequence<TestExternalInterface> arg); + void passNullableExternalInterfaceSequence(sequence<TestExternalInterface?> arg); + + sequence<DOMString> receiveStringSequence(); + void passStringSequence(sequence<DOMString> arg); + + sequence<ByteString> receiveByteStringSequence(); + void passByteStringSequence(sequence<ByteString> arg); + + sequence<any> receiveAnySequence(); + sequence<any>? receiveNullableAnySequence(); + sequence<sequence<any>> receiveAnySequenceSequence(); + + sequence<object> receiveObjectSequence(); + sequence<object?> receiveNullableObjectSequence(); + + void passSequenceOfSequences(sequence<sequence<long>> arg); + void passSequenceOfSequencesOfSequences(sequence<sequence<sequence<long>>> arg); + sequence<sequence<long>> receiveSequenceOfSequences(); + sequence<sequence<sequence<long>>> receiveSequenceOfSequencesOfSequences(); + + // MozMap types + void passMozMap(MozMap<long> arg); + void passNullableMozMap(MozMap<long>? arg); + void passMozMapOfNullableInts(MozMap<long?> arg); + void passOptionalMozMapOfNullableInts(optional MozMap<long?> arg); + void passOptionalNullableMozMapOfNullableInts(optional MozMap<long?>? arg); + void passCastableObjectMozMap(MozMap<TestInterface> arg); + void passNullableCastableObjectMozMap(MozMap<TestInterface?> arg); + void passCastableObjectNullableMozMap(MozMap<TestInterface>? arg); + void passNullableCastableObjectNullableMozMap(MozMap<TestInterface?>? arg); + void passOptionalMozMap(optional MozMap<long> arg); + void passOptionalNullableMozMap(optional MozMap<long>? arg); + void passOptionalNullableMozMapWithDefaultValue(optional MozMap<long>? arg = null); + void passOptionalObjectMozMap(optional MozMap<TestInterface> arg); + void passExternalInterfaceMozMap(MozMap<TestExternalInterface> arg); + void passNullableExternalInterfaceMozMap(MozMap<TestExternalInterface?> arg); + void passStringMozMap(MozMap<DOMString> arg); + void passByteStringMozMap(MozMap<ByteString> arg); + void passMozMapOfMozMaps(MozMap<MozMap<long>> arg); + MozMap<long> receiveMozMap(); + MozMap<long>? receiveNullableMozMap(); + MozMap<long?> receiveMozMapOfNullableInts(); + MozMap<long?>? receiveNullableMozMapOfNullableInts(); + MozMap<MozMap<long>> receiveMozMapOfMozMaps(); + MozMap<any> receiveAnyMozMap(); + + // Typed array types + void passArrayBuffer(ArrayBuffer arg); + void passNullableArrayBuffer(ArrayBuffer? arg); + void passOptionalArrayBuffer(optional ArrayBuffer arg); + void passOptionalNullableArrayBuffer(optional ArrayBuffer? arg); + void passOptionalNullableArrayBufferWithDefaultValue(optional ArrayBuffer? arg= null); + void passArrayBufferView(ArrayBufferView arg); + void passInt8Array(Int8Array arg); + void passInt16Array(Int16Array arg); + void passInt32Array(Int32Array arg); + void passUint8Array(Uint8Array arg); + void passUint16Array(Uint16Array arg); + void passUint32Array(Uint32Array arg); + void passUint8ClampedArray(Uint8ClampedArray arg); + void passFloat32Array(Float32Array arg); + void passFloat64Array(Float64Array arg); + void passSequenceOfArrayBuffers(sequence<ArrayBuffer> arg); + void passSequenceOfNullableArrayBuffers(sequence<ArrayBuffer?> arg); + void passMozMapOfArrayBuffers(MozMap<ArrayBuffer> arg); + void passMozMapOfNullableArrayBuffers(MozMap<ArrayBuffer?> arg); + void passVariadicTypedArray(Float32Array... arg); + void passVariadicNullableTypedArray(Float32Array?... arg); + Uint8Array receiveUint8Array(); + attribute Uint8Array uint8ArrayAttr; + + // DOMString types + void passString(DOMString arg); + void passNullableString(DOMString? arg); + void passOptionalString(optional DOMString arg); + void passOptionalStringWithDefaultValue(optional DOMString arg = "abc"); + void passOptionalNullableString(optional DOMString? arg); + void passOptionalNullableStringWithDefaultValue(optional DOMString? arg = null); + void passVariadicString(DOMString... arg); + DOMString receiveString(); + + // ByteString types + void passByteString(ByteString arg); + void passNullableByteString(ByteString? arg); + void passOptionalByteString(optional ByteString arg); + void passOptionalByteStringWithDefaultValue(optional ByteString arg = "abc"); + void passOptionalNullableByteString(optional ByteString? arg); + void passOptionalNullableByteStringWithDefaultValue(optional ByteString? arg = null); + void passVariadicByteString(ByteString... arg); + void passOptionalUnionByteString(optional (ByteString or long) arg); + void passOptionalUnionByteStringWithDefaultValue(optional (ByteString or long) arg = "abc"); + + // USVString types + void passUSVS(USVString arg); + void passNullableUSVS(USVString? arg); + void passOptionalUSVS(optional USVString arg); + void passOptionalUSVSWithDefaultValue(optional USVString arg = "abc"); + void passOptionalNullableUSVS(optional USVString? arg); + void passOptionalNullableUSVSWithDefaultValue(optional USVString? arg = null); + void passVariadicUSVS(USVString... arg); + USVString receiveUSVS(); + + // Enumerated types + void passEnum(TestEnum arg); + void passNullableEnum(TestEnum? arg); + void passOptionalEnum(optional TestEnum arg); + void passEnumWithDefault(optional TestEnum arg = "a"); + void passOptionalNullableEnum(optional TestEnum? arg); + void passOptionalNullableEnumWithDefaultValue(optional TestEnum? arg = null); + void passOptionalNullableEnumWithDefaultValue2(optional TestEnum? arg = "a"); + TestEnum receiveEnum(); + TestEnum? receiveNullableEnum(); + attribute TestEnum enumAttribute; + readonly attribute TestEnum readonlyEnumAttribute; + + // Callback types + void passCallback(TestCallback arg); + void passNullableCallback(TestCallback? arg); + void passOptionalCallback(optional TestCallback arg); + void passOptionalNullableCallback(optional TestCallback? arg); + void passOptionalNullableCallbackWithDefaultValue(optional TestCallback? arg = null); + TestCallback receiveCallback(); + TestCallback? receiveNullableCallback(); + void passNullableTreatAsNullCallback(TestTreatAsNullCallback? arg); + void passOptionalNullableTreatAsNullCallback(optional TestTreatAsNullCallback? arg); + void passOptionalNullableTreatAsNullCallbackWithDefaultValue(optional TestTreatAsNullCallback? arg = null); + attribute TestTreatAsNullCallback treatAsNullCallback; + attribute TestTreatAsNullCallback? nullableTreatAsNullCallback; + + // Force code generation of the various test callbacks we have. + void 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); + + // Any types + void passAny(any arg); + void passVariadicAny(any... arg); + void passOptionalAny(optional any arg); + void passAnyDefaultNull(optional any arg = null); + void passSequenceOfAny(sequence<any> arg); + void passNullableSequenceOfAny(sequence<any>? arg); + void passOptionalSequenceOfAny(optional sequence<any> arg); + void passOptionalNullableSequenceOfAny(optional sequence<any>? arg); + void passOptionalSequenceOfAnyWithDefaultValue(optional sequence<any>? arg = null); + void passSequenceOfSequenceOfAny(sequence<sequence<any>> arg); + void passSequenceOfNullableSequenceOfAny(sequence<sequence<any>?> arg); + void passNullableSequenceOfNullableSequenceOfAny(sequence<sequence<any>?>? arg); + void passOptionalNullableSequenceOfNullableSequenceOfAny(optional sequence<sequence<any>?>? arg); + void passMozMapOfAny(MozMap<any> arg); + void passNullableMozMapOfAny(MozMap<any>? arg); + void passOptionalMozMapOfAny(optional MozMap<any> arg); + void passOptionalNullableMozMapOfAny(optional MozMap<any>? arg); + void passOptionalMozMapOfAnyWithDefaultValue(optional MozMap<any>? arg = null); + void passMozMapOfMozMapOfAny(MozMap<MozMap<any>> arg); + void passMozMapOfNullableMozMapOfAny(MozMap<MozMap<any>?> arg); + void passNullableMozMapOfNullableMozMapOfAny(MozMap<MozMap<any>?>? arg); + void passOptionalNullableMozMapOfNullableMozMapOfAny(optional MozMap<MozMap<any>?>? arg); + void passOptionalNullableMozMapOfNullableSequenceOfAny(optional MozMap<sequence<any>?>? arg); + void passOptionalNullableSequenceOfNullableMozMapOfAny(optional sequence<MozMap<any>?>? arg); + any receiveAny(); + + // object types + void passObject(object arg); + void passVariadicObject(object... arg); + void passNullableObject(object? arg); + void passVariadicNullableObject(object... arg); + void passOptionalObject(optional object arg); + void passOptionalNullableObject(optional object? arg); + void passOptionalNullableObjectWithDefaultValue(optional object? arg = null); + void passSequenceOfObject(sequence<object> arg); + void passSequenceOfNullableObject(sequence<object?> arg); + void passNullableSequenceOfObject(sequence<object>? arg); + void passOptionalNullableSequenceOfNullableSequenceOfObject(optional sequence<sequence<object>?>? arg); + void passOptionalNullableSequenceOfNullableSequenceOfNullableObject(optional sequence<sequence<object?>?>? arg); + void passMozMapOfObject(MozMap<object> arg); + object receiveObject(); + object? receiveNullableObject(); + + // Union types + void passUnion((object or long) arg); + // Some union tests are debug-only to avoid creating all those + // unused union types in opt builds. +#ifdef DEBUG + void passUnion2((long or boolean) arg); + void passUnion3((object or long or boolean) arg); + void passUnion4((Node or long or boolean) arg); + void passUnion5((object or boolean) arg); + void passUnion6((object or DOMString) arg); + void passUnion7((object or DOMString or long) arg); + void passUnion8((object or DOMString or boolean) arg); + void passUnion9((object or DOMString or long or boolean) arg); + void passUnion10(optional (EventInit or long) arg); + void passUnion11(optional (CustomEventInit or long) arg); + void passUnion12(optional (EventInit or long) arg = 5); + void passUnion13(optional (object or long?) arg = null); + void passUnion14(optional (object or long?) arg = 5); + void passUnion15((sequence<long> or long) arg); + void passUnion16(optional (sequence<long> or long) arg); + void passUnion17(optional (sequence<long>? or long) arg = 5); + void passUnion18((sequence<object> or long) arg); + void passUnion19(optional (sequence<object> or long) arg); + void passUnion20(optional (sequence<object> or long) arg = []); + void passUnion21((MozMap<long> or long) arg); + void passUnion22((MozMap<object> or long) arg); + void passUnion23((sequence<ImageData> or long) arg); + void passUnion24((sequence<ImageData?> or long) arg); + void passUnion25((sequence<sequence<ImageData>> or long) arg); + void passUnion26((sequence<sequence<ImageData?>> or long) arg); + void passUnion27(optional (sequence<DOMString> or EventInit) arg); + void passUnion28(optional (EventInit or sequence<DOMString>) arg); + void passUnionWithCallback((EventHandler or long) arg); + void passUnionWithByteString((ByteString or long) arg); + void passUnionWithMozMap((MozMap<DOMString> or DOMString) arg); + void passUnionWithMozMapAndSequence((MozMap<DOMString> or sequence<DOMString>) arg); + void passUnionWithSequenceAndMozMap((sequence<DOMString> or MozMap<DOMString>) arg); + void passUnionWithUSVS((USVString or long) arg); +#endif + void passUnionWithNullable((object? or long) arg); + void passNullableUnion((object or long)? arg); + void passOptionalUnion(optional (object or long) arg); + void passOptionalNullableUnion(optional (object or long)? arg); + void passOptionalNullableUnionWithDefaultValue(optional (object or long)? arg = null); + //void passUnionWithInterfaces((TestInterface or TestExternalInterface) arg); + //void passUnionWithInterfacesAndNullable((TestInterface? or TestExternalInterface) arg); + //void passUnionWithSequence((sequence<object> or long) arg); + void passUnionWithArrayBuffer((ArrayBuffer or long) arg); + void 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. + void passUnionWithEnum((SupportedType or object) arg); + + // Trying to use a callback in a union won't include the test + // headers, unfortunately, so won't compile. + //void passUnionWithCallback((TestCallback or long) arg); + void passUnionWithObject((object or long) arg); + //void passUnionWithDict((Dict or long) arg); + + void passUnionWithDefaultValue1(optional (double or DOMString) arg = ""); + void passUnionWithDefaultValue2(optional (double or DOMString) arg = 1); + void passUnionWithDefaultValue3(optional (double or DOMString) arg = 1.5); + void passUnionWithDefaultValue4(optional (float or DOMString) arg = ""); + void passUnionWithDefaultValue5(optional (float or DOMString) arg = 1); + void passUnionWithDefaultValue6(optional (float or DOMString) arg = 1.5); + void passUnionWithDefaultValue7(optional (unrestricted double or DOMString) arg = ""); + void passUnionWithDefaultValue8(optional (unrestricted double or DOMString) arg = 1); + void passUnionWithDefaultValue9(optional (unrestricted double or DOMString) arg = 1.5); + void passUnionWithDefaultValue10(optional (unrestricted double or DOMString) arg = Infinity); + void passUnionWithDefaultValue11(optional (unrestricted float or DOMString) arg = ""); + void passUnionWithDefaultValue12(optional (unrestricted float or DOMString) arg = 1); + void passUnionWithDefaultValue13(optional (unrestricted float or DOMString) arg = Infinity); + void passUnionWithDefaultValue14(optional (double or ByteString) arg = ""); + void passUnionWithDefaultValue15(optional (double or ByteString) arg = 1); + void passUnionWithDefaultValue16(optional (double or ByteString) arg = 1.5); + void passUnionWithDefaultValue17(optional (double or SupportedType) arg = "text/html"); + void passUnionWithDefaultValue18(optional (double or SupportedType) arg = 1); + void passUnionWithDefaultValue19(optional (double or SupportedType) arg = 1.5); + + void passNullableUnionWithDefaultValue1(optional (double or DOMString)? arg = ""); + void passNullableUnionWithDefaultValue2(optional (double or DOMString)? arg = 1); + void passNullableUnionWithDefaultValue3(optional (double or DOMString)? arg = null); + void passNullableUnionWithDefaultValue4(optional (float or DOMString)? arg = ""); + void passNullableUnionWithDefaultValue5(optional (float or DOMString)? arg = 1); + void passNullableUnionWithDefaultValue6(optional (float or DOMString)? arg = null); + void passNullableUnionWithDefaultValue7(optional (unrestricted double or DOMString)? arg = ""); + void passNullableUnionWithDefaultValue8(optional (unrestricted double or DOMString)? arg = 1); + void passNullableUnionWithDefaultValue9(optional (unrestricted double or DOMString)? arg = null); + void passNullableUnionWithDefaultValue10(optional (unrestricted float or DOMString)? arg = ""); + void passNullableUnionWithDefaultValue11(optional (unrestricted float or DOMString)? arg = 1); + void passNullableUnionWithDefaultValue12(optional (unrestricted float or DOMString)? arg = null); + void passNullableUnionWithDefaultValue13(optional (double or ByteString)? arg = ""); + void passNullableUnionWithDefaultValue14(optional (double or ByteString)? arg = 1); + void passNullableUnionWithDefaultValue15(optional (double or ByteString)? arg = 1.5); + void passNullableUnionWithDefaultValue16(optional (double or ByteString)? arg = null); + void passNullableUnionWithDefaultValue17(optional (double or SupportedType)? arg = "text/html"); + void passNullableUnionWithDefaultValue18(optional (double or SupportedType)? arg = 1); + void passNullableUnionWithDefaultValue19(optional (double or SupportedType)? arg = 1.5); + void passNullableUnionWithDefaultValue20(optional (double or SupportedType)? arg = null); + + void passSequenceOfUnions(sequence<(CanvasPattern or CanvasGradient)> arg); + void passSequenceOfUnions2(sequence<(object or long)> arg); + void passVariadicUnion((CanvasPattern or CanvasGradient)... arg); + + void passSequenceOfNullableUnions(sequence<(CanvasPattern or CanvasGradient)?> arg); + void passVariadicNullableUnion((CanvasPattern or CanvasGradient)?... arg); + void passMozMapOfUnions(MozMap<(CanvasPattern or CanvasGradient)> arg); + // XXXbz no move constructor on some unions + // void passMozMapOfUnions2(MozMap<(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; + + // Date types + void passDate(Date arg); + void passNullableDate(Date? arg); + void passOptionalDate(optional Date arg); + void passOptionalNullableDate(optional Date? arg); + void passOptionalNullableDateWithDefaultValue(optional Date? arg = null); + void passDateSequence(sequence<Date> arg); + void passNullableDateSequence(sequence<Date?> arg); + void passDateMozMap(MozMap<Date> arg); + Date receiveDate(); + Date? receiveNullableDate(); + + // Promise types + void passPromise(Promise<any> arg); + void passNullablePromise(Promise<any>? arg); + void passOptionalPromise(optional Promise<any> arg); + void passOptionalNullablePromise(optional Promise<any>? arg); + void passOptionalNullablePromiseWithDefaultValue(optional Promise<any>? arg = null); + void passPromiseSequence(sequence<Promise<any>> arg); + void passNullablePromiseSequence(sequence<Promise<any>?> arg); + Promise<any> receivePromise(); + Promise<any> receiveAddrefedPromise(); + + // binaryNames tests + void methodRenamedFrom(); + [BinaryName="otherMethodRenamedTo"] + void otherMethodRenamedFrom(); + void methodRenamedFrom(byte argument); + readonly attribute byte attributeGetterRenamedFrom; + attribute byte attributeRenamedFrom; + [BinaryName="otherAttributeRenamedTo"] + attribute byte otherAttributeRenamedFrom; + + void passDictionary(optional Dict x); + void 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(); + void passOtherDictionary(optional GrandparentDict x); + void passSequenceOfDictionaries(sequence<Dict> x); + void passMozMapOfDictionaries(MozMap<GrandparentDict> x); + // No support for nullable dictionaries inside a sequence (nor should there be) + // void passSequenceOfNullableDictionaries(sequence<Dict?> x); + void passDictionaryOrLong(optional Dict x); + void passDictionaryOrLong(long x); + + void passDictContainingDict(optional DictContainingDict arg); + void passDictContainingSequence(optional DictContainingSequence arg); + DictContainingSequence receiveDictContainingSequence(); + void passVariadicDictionary(Dict... arg); + + // EnforceRange/Clamp tests + void dontEnforceRangeOrClamp(byte arg); + void doEnforceRange([EnforceRange] byte arg); + void doClamp([Clamp] byte arg); + [EnforceRange] attribute byte enforcedByte; + [Clamp] attribute byte clampedByte; + + // Typedefs + const myLong myLongConstant = 5; + void exerciseTypedefInterfaces1(AnotherNameForTestInterface arg); + AnotherNameForTestInterface exerciseTypedefInterfaces2(NullableTestInterface arg); + void exerciseTypedefInterfaces3(YetAnotherNameForTestInterface arg); + + // Deprecated methods and attributes + [Deprecated="GetAttributeNode"] + attribute byte deprecatedAttribute; + [Deprecated="GetAttributeNode"] + byte deprecatedMethod(); + [Deprecated="GetAttributeNode"] + byte deprecatedMethodWithContext(any arg); + + // Static methods and attributes + static attribute boolean staticAttribute; + static void staticMethod(boolean arg); + static void staticMethodWithContext(any arg); + + // Testing static method with a reserved C++ keyword as the name + static void assert(boolean arg); + + // Deprecated static methods and attributes + [Deprecated="GetAttributeNode"] + static attribute byte staticDeprecatedAttribute; + [Deprecated="GetAttributeNode"] + static void staticDeprecatedMethod(); + [Deprecated="GetAttributeNode"] + static void staticDeprecatedMethodWithContext(any arg); + + // Overload resolution tests + //void overload1(DOMString... strs); + boolean overload1(TestInterface arg); + TestInterface overload1(DOMString strs, TestInterface arg); + void overload2(TestInterface arg); + void overload2(optional Dict arg); + void overload2(boolean arg); + void overload2(DOMString arg); + void overload2(Date arg); + void overload3(TestInterface arg); + void overload3(TestCallback arg); + void overload3(boolean arg); + void overload4(TestInterface arg); + void overload4(TestCallbackInterface arg); + void overload4(DOMString arg); + void overload5(long arg); + void overload5(TestEnum arg); + void overload6(long arg); + void overload6(boolean arg); + void overload7(long arg); + void overload7(boolean arg); + void overload7(ByteString arg); + void overload8(long arg); + void overload8(TestInterface arg); + void overload9(long? arg); + void overload9(DOMString arg); + void overload10(long? arg); + void overload10(object arg); + void overload11(long arg); + void overload11(DOMString? arg); + void overload12(long arg); + void overload12(boolean? arg); + void overload13(long? arg); + void overload13(boolean arg); + void overload14(optional long arg); + void overload14(TestInterface arg); + void overload15(long arg); + void overload15(optional TestInterface arg); + void overload16(long arg); + void overload16(optional TestInterface? arg); + void overload17(sequence<long> arg); + void overload17(MozMap<long> arg); + void overload18(MozMap<DOMString> arg); + void overload18(sequence<DOMString> arg); + void overload19(sequence<long> arg); + void overload19(optional Dict arg); + void overload20(optional Dict arg); + void overload20(sequence<long> arg); + + // Variadic handling + void passVariadicThirdArg(DOMString arg1, long arg2, TestInterface... arg3); + + // Conditionally exposed methods/attributes + [Pref="abc.def"] + readonly attribute boolean prefable1; + [Pref="abc.def"] + readonly attribute boolean prefable2; + [Pref="ghi.jkl"] + readonly attribute boolean prefable3; + [Pref="ghi.jkl"] + readonly attribute boolean prefable4; + [Pref="abc.def"] + readonly attribute boolean prefable5; + [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + readonly attribute boolean prefable6; + [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + readonly attribute boolean prefable7; + [Pref="ghi.jkl", Func="nsGenericHTMLElement::TouchEventsEnabled"] + readonly attribute boolean prefable8; + [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + readonly attribute boolean prefable9; + [Pref="abc.def"] + void prefable10(); + [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + void prefable11(); + [Pref="abc.def", Func="TestFuncControlledMember"] + readonly attribute boolean prefable12; + [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + void prefable13(); + [Pref="abc.def", Func="TestFuncControlledMember"] + readonly attribute boolean prefable14; + [Func="TestFuncControlledMember"] + readonly attribute boolean prefable15; + [Func="TestFuncControlledMember"] + readonly attribute boolean prefable16; + [Pref="abc.def", Func="TestFuncControlledMember"] + void prefable17(); + [Func="TestFuncControlledMember"] + void prefable18(); + [Func="TestFuncControlledMember"] + void prefable19(); + [Pref="abc.def", Func="TestFuncControlledMember", ChromeOnly] + void prefable20(); + + // Conditionally exposed methods/attributes involving [SecureContext] + [SecureContext] + readonly attribute boolean conditionalOnSecureContext1; + [SecureContext, Pref="abc.def"] + readonly attribute boolean conditionalOnSecureContext2; + [SecureContext, Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + readonly attribute boolean conditionalOnSecureContext3; + [SecureContext, Pref="abc.def", Func="TestFuncControlledMember"] + readonly attribute boolean conditionalOnSecureContext4; + [SecureContext] + void conditionalOnSecureContext5(); + [SecureContext, Pref="abc.def"] + void conditionalOnSecureContext6(); + [SecureContext, Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + void conditionalOnSecureContext7(); + [SecureContext, Pref="abc.def", Func="TestFuncControlledMember"] + void conditionalOnSecureContext8(); + + // Miscellania + [LenientThis] attribute long attrWithLenientThis; + [Unforgeable] readonly attribute long unforgeableAttr; + [Unforgeable, ChromeOnly] readonly attribute long unforgeableAttr2; + [Unforgeable] long unforgeableMethod(); + [Unforgeable, ChromeOnly] long unforgeableMethod2(); + stringifier; + void passRenamedInterface(TestRenamedInterface arg); + [PutForwards=writableByte] readonly attribute TestInterface putForwardsAttr; + [PutForwards=writableByte, LenientThis] readonly attribute TestInterface putForwardsAttr2; + [PutForwards=writableByte, ChromeOnly] readonly attribute TestInterface putForwardsAttr3; + [Throws] void throwingMethod(); + [Throws] attribute boolean throwingAttr; + [GetterThrows] attribute boolean throwingGetterAttr; + [SetterThrows] attribute boolean throwingSetterAttr; + [NeedsSubjectPrincipal] void needsSubjectPrincipalMethod(); + [NeedsSubjectPrincipal] attribute boolean needsSubjectPrincipalAttr; + [NeedsCallerType] void needsCallerTypeMethod(); + [NeedsCallerType] attribute boolean needsCallerTypeAttr; + legacycaller short(unsigned long arg1, TestInterface arg2); + void passArgsWithDefaults(optional long arg1, + optional TestInterface? arg2 = null, + optional Dict arg3, optional double arg4 = 5.0, + optional float arg5); + + attribute any jsonifierShouldSkipThis; + attribute TestParentInterface jsonifierShouldSkipThis2; + attribute TestCallbackInterface jsonifierShouldSkipThis3; + jsonifier; + + attribute byte dashed-attribute; + void dashed-method(); + + // If you add things here, add them to TestExampleGen and TestJSImplGen as well +}; + +interface TestParentInterface { +}; + +interface TestChildInterface : TestParentInterface { +}; + +interface TestNonWrapperCacheInterface { +}; + +[NoInterfaceObject] +interface ImplementedInterfaceParent { + void implementedParentMethod(); + attribute boolean implementedParentProperty; + + const long implementedParentConstant = 8; +}; + +ImplementedInterfaceParent implements IndirectlyImplementedInterface; + +[NoInterfaceObject] +interface IndirectlyImplementedInterface { + void indirectlyImplementedMethod(); + attribute boolean indirectlyImplementedProperty; + + const long indirectlyImplementedConstant = 9; +}; + +[NoInterfaceObject] +interface ImplementedInterface : ImplementedInterfaceParent { + void implementedMethod(); + attribute boolean implementedProperty; + + const long implementedConstant = 5; +}; + +[NoInterfaceObject] +interface DiamondImplements { + readonly attribute long diamondImplementedProperty; +}; +[NoInterfaceObject] +interface DiamondBranch1A { +}; +[NoInterfaceObject] +interface DiamondBranch1B { +}; +[NoInterfaceObject] +interface DiamondBranch2A : DiamondImplements { +}; +[NoInterfaceObject] +interface DiamondBranch2B : DiamondImplements { +}; +TestInterface implements DiamondBranch1A; +TestInterface implements DiamondBranch1B; +TestInterface implements DiamondBranch2A; +TestInterface implements DiamondBranch2B; +DiamondBranch1A implements DiamondImplements; +DiamondBranch1B implements DiamondImplements; + +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"; + 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 = null; + (CustomEventInit or long) eventInitOrLongWithDefaultValue2 = null; + (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<void> promise; + sequence<Promise<void>> promiseSequence; +}; + +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::TouchEventsEnabled"] + long chromeOnlyFuncControlledMember; +}; + +interface TestIndexedGetterInterface { + getter long item(unsigned long idx); + readonly attribute unsigned long length; + legacycaller void(); +}; + +interface TestNamedGetterInterface { + getter DOMString (DOMString name); +}; + +interface TestIndexedGetterAndSetterAndNamedGetterInterface { + getter DOMString (DOMString myName); + getter long (unsigned long index); + setter creator void (unsigned long index, long arg); +}; + +interface TestIndexedAndNamedGetterInterface { + getter long (unsigned long index); + getter DOMString namedItem(DOMString name); + readonly attribute unsigned long length; +}; + +interface TestIndexedSetterInterface { + setter creator void setItem(unsigned long idx, DOMString item); + getter DOMString (unsigned long idx); +}; + +interface TestNamedSetterInterface { + setter creator void (DOMString myName, TestIndexedSetterInterface item); + getter TestIndexedSetterInterface (DOMString name); +}; + +interface TestIndexedAndNamedSetterInterface { + setter creator void (unsigned long index, TestIndexedSetterInterface item); + getter TestIndexedSetterInterface (unsigned long index); + setter creator void setNamedItem(DOMString name, TestIndexedSetterInterface item); + getter TestIndexedSetterInterface (DOMString name); +}; + +interface TestIndexedAndNamedGetterAndSetterInterface : TestIndexedSetterInterface { + getter long item(unsigned long index); + getter DOMString namedItem(DOMString name); + setter creator void (unsigned long index, long item); + setter creator void (DOMString name, DOMString item); + stringifier DOMString (); + readonly attribute unsigned long length; +}; + +interface TestNamedDeleterInterface { + deleter void (DOMString name); + getter long (DOMString name); +}; + +interface TestNamedDeleterWithRetvalInterface { + deleter boolean delNamedItem(DOMString name); + getter long (DOMString name); +}; + +interface TestCppKeywordNamedMethodsInterface { + boolean continue(); + boolean delete(); + long volatile(); +}; + +[Deprecated="GetAttributeNode", Constructor()] +interface TestDeprecatedInterface { + static void alsoDeprecated(); +}; + + +[Constructor(Promise<void> promise)] +interface TestInterfaceWithPromiseConstructorArg { +}; + +namespace TestNamespace { + readonly attribute boolean foo; + long bar(); +}; + +partial namespace TestNamespace { + void baz(); +}; + +[ClassString="RenamedNamespaceClassName"] +namespace TestRenamedNamespace { +}; + +[ProtoObjectHack] +namespace TestProtoObjectHackedNamespace { +}; + +[SecureContext] +interface TestSecureContextInterface { + static void alsoSecureContext(); +}; + +[Exposed=(Window,Worker)] +interface TestWorkerExposedInterface { + [NeedsSubjectPrincipal] void needsSubjectPrincipalMethod(); + [NeedsSubjectPrincipal] attribute boolean needsSubjectPrincipalAttr; + [NeedsCallerType] void needsCallerTypeMethod(); + [NeedsCallerType] attribute boolean needsCallerTypeAttr; +}; diff --git a/dom/bindings/test/TestDictionary.webidl b/dom/bindings/test/TestDictionary.webidl new file mode 100644 index 000000000..3dd91bd65 --- /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; +};
\ No newline at end of file diff --git a/dom/bindings/test/TestExampleGen.webidl b/dom/bindings/test/TestExampleGen.webidl new file mode 100644 index 000000000..ea6387a84 --- /dev/null +++ b/dom/bindings/test/TestExampleGen.webidl @@ -0,0 +1,811 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +[Constructor, + Constructor(DOMString str), + Constructor(unsigned long num, boolean? boolArg), + Constructor(TestInterface? iface), + Constructor(long arg1, IndirectlyImplementedInterface iface), + Constructor(Date arg1), + Constructor(ArrayBuffer arrayBuf), + Constructor(Uint8Array typedArr), + // Constructor(long arg1, long arg2, (TestInterface or OnlyForUseInConstructor) arg3), + NamedConstructor=Example, + NamedConstructor=Example(DOMString str), + NamedConstructor=Example2(DictForConstructor dict, any any1, object obj1, + object? obj2, sequence<Dict> seq, optional any any2, + optional object obj3, optional object? obj4), + NamedConstructor=Example2((long or MozMap<any>) arg1) + ] +interface TestExampleInterface { + // Integer types + // XXXbz add tests for throwing versions of all the integer stuff + readonly attribute byte readonlyByte; + attribute byte writableByte; + void passByte(byte arg); + byte receiveByte(); + void passOptionalByte(optional byte arg); + void passOptionalByteBeforeRequired(optional byte arg1, byte arg2); + void passOptionalByteWithDefault(optional byte arg = 0); + void passOptionalByteWithDefaultBeforeRequired(optional byte arg1 = 0, byte arg2); + void passNullableByte(byte? arg); + void passOptionalNullableByte(optional byte? arg); + void 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; + void passShort(short arg); + short receiveShort(); + void passOptionalShort(optional short arg); + void passOptionalShortWithDefault(optional short arg = 5); + + readonly attribute long readonlyLong; + attribute long writableLong; + void passLong(long arg); + long receiveLong(); + void passOptionalLong(optional long arg); + void passOptionalLongWithDefault(optional long arg = 7); + + readonly attribute long long readonlyLongLong; + attribute long long writableLongLong; + void passLongLong(long long arg); + long long receiveLongLong(); + void passOptionalLongLong(optional long long arg); + void passOptionalLongLongWithDefault(optional long long arg = -12); + + readonly attribute octet readonlyOctet; + attribute octet writableOctet; + void passOctet(octet arg); + octet receiveOctet(); + void passOptionalOctet(optional octet arg); + void passOptionalOctetWithDefault(optional octet arg = 19); + + readonly attribute unsigned short readonlyUnsignedShort; + attribute unsigned short writableUnsignedShort; + void passUnsignedShort(unsigned short arg); + unsigned short receiveUnsignedShort(); + void passOptionalUnsignedShort(optional unsigned short arg); + void passOptionalUnsignedShortWithDefault(optional unsigned short arg = 2); + + readonly attribute unsigned long readonlyUnsignedLong; + attribute unsigned long writableUnsignedLong; + void passUnsignedLong(unsigned long arg); + unsigned long receiveUnsignedLong(); + void passOptionalUnsignedLong(optional unsigned long arg); + void passOptionalUnsignedLongWithDefault(optional unsigned long arg = 6); + + readonly attribute unsigned long long readonlyUnsignedLongLong; + attribute unsigned long long writableUnsignedLongLong; + void passUnsignedLongLong(unsigned long long arg); + unsigned long long receiveUnsignedLongLong(); + void passOptionalUnsignedLongLong(optional unsigned long long arg); + void 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; + void 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] + void 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(); + void passSelf(TestInterface arg); + void passNullableSelf(TestInterface? arg); + attribute TestInterface nonNullSelf; + attribute TestInterface? nullableSelf; + [Cached, Pure] + readonly attribute TestInterface cachedSelf; + // Optional arguments + void passOptionalSelf(optional TestInterface? arg); + void passOptionalNonNullSelf(optional TestInterface arg); + void 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(); + + // Non-castable interface types + IndirectlyImplementedInterface receiveOther(); + IndirectlyImplementedInterface? receiveNullableOther(); + IndirectlyImplementedInterface receiveWeakOther(); + IndirectlyImplementedInterface? receiveWeakNullableOther(); + void passOther(IndirectlyImplementedInterface arg); + void passNullableOther(IndirectlyImplementedInterface? arg); + attribute IndirectlyImplementedInterface nonNullOther; + attribute IndirectlyImplementedInterface? nullableOther; + // Optional arguments + void passOptionalOther(optional IndirectlyImplementedInterface? arg); + void passOptionalNonNullOther(optional IndirectlyImplementedInterface arg); + void passOptionalOtherWithDefault(optional IndirectlyImplementedInterface? arg = null); + + // External interface types + TestExternalInterface receiveExternal(); + TestExternalInterface? receiveNullableExternal(); + TestExternalInterface receiveWeakExternal(); + TestExternalInterface? receiveWeakNullableExternal(); + void passExternal(TestExternalInterface arg); + void passNullableExternal(TestExternalInterface? arg); + attribute TestExternalInterface nonNullExternal; + attribute TestExternalInterface? nullableExternal; + // Optional arguments + void passOptionalExternal(optional TestExternalInterface? arg); + void passOptionalNonNullExternal(optional TestExternalInterface arg); + void passOptionalExternalWithDefault(optional TestExternalInterface? arg = null); + + // Callback interface types + TestCallbackInterface receiveCallbackInterface(); + TestCallbackInterface? receiveNullableCallbackInterface(); + TestCallbackInterface receiveWeakCallbackInterface(); + TestCallbackInterface? receiveWeakNullableCallbackInterface(); + void passCallbackInterface(TestCallbackInterface arg); + void passNullableCallbackInterface(TestCallbackInterface? arg); + attribute TestCallbackInterface nonNullCallbackInterface; + attribute TestCallbackInterface? nullableCallbackInterface; + // Optional arguments + void passOptionalCallbackInterface(optional TestCallbackInterface? arg); + void passOptionalNonNullCallbackInterface(optional TestCallbackInterface arg); + void passOptionalCallbackInterfaceWithDefault(optional TestCallbackInterface? arg = null); + + // Miscellaneous interface tests + IndirectlyImplementedInterface receiveConsequentialInterface(); + void passConsequentialInterface(IndirectlyImplementedInterface arg); + + // 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(); + void passSequence(sequence<long> arg); + void passNullableSequence(sequence<long>? arg); + void passSequenceOfNullableInts(sequence<long?> arg); + void passOptionalSequenceOfNullableInts(optional sequence<long?> arg); + void 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(); + void passCastableObjectSequence(sequence<TestInterface> arg); + void passNullableCastableObjectSequence(sequence<TestInterface?> arg); + void passCastableObjectNullableSequence(sequence<TestInterface>? arg); + void passNullableCastableObjectNullableSequence(sequence<TestInterface?>? arg); + void passOptionalSequence(optional sequence<long> arg); + void passOptionalSequenceWithDefaultValue(optional sequence<long> arg = []); + void passOptionalNullableSequence(optional sequence<long>? arg); + void passOptionalNullableSequenceWithDefaultValue(optional sequence<long>? arg = null); + void passOptionalNullableSequenceWithDefaultValue2(optional sequence<long>? arg = []); + void passOptionalObjectSequence(optional sequence<TestInterface> arg); + void passExternalInterfaceSequence(sequence<TestExternalInterface> arg); + void passNullableExternalInterfaceSequence(sequence<TestExternalInterface?> arg); + + sequence<DOMString> receiveStringSequence(); + void passStringSequence(sequence<DOMString> arg); + + sequence<ByteString> receiveByteStringSequence(); + void passByteStringSequence(sequence<ByteString> 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(); + + void passSequenceOfSequences(sequence<sequence<long>> arg); + void passSequenceOfSequencesOfSequences(sequence<sequence<sequence<long>>> arg); + //XXXbz No support for sequence of sequence return values yet. + //sequence<sequence<long>> receiveSequenceOfSequences(); + + // MozMap types + void passMozMap(MozMap<long> arg); + void passNullableMozMap(MozMap<long>? arg); + void passMozMapOfNullableInts(MozMap<long?> arg); + void passOptionalMozMapOfNullableInts(optional MozMap<long?> arg); + void passOptionalNullableMozMapOfNullableInts(optional MozMap<long?>? arg); + void passCastableObjectMozMap(MozMap<TestInterface> arg); + void passNullableCastableObjectMozMap(MozMap<TestInterface?> arg); + void passCastableObjectNullableMozMap(MozMap<TestInterface>? arg); + void passNullableCastableObjectNullableMozMap(MozMap<TestInterface?>? arg); + void passOptionalMozMap(optional MozMap<long> arg); + void passOptionalNullableMozMap(optional MozMap<long>? arg); + void passOptionalNullableMozMapWithDefaultValue(optional MozMap<long>? arg = null); + void passOptionalObjectMozMap(optional MozMap<TestInterface> arg); + void passExternalInterfaceMozMap(MozMap<TestExternalInterface> arg); + void passNullableExternalInterfaceMozMap(MozMap<TestExternalInterface?> arg); + void passStringMozMap(MozMap<DOMString> arg); + void passByteStringMozMap(MozMap<ByteString> arg); + void passMozMapOfMozMaps(MozMap<MozMap<long>> arg); + MozMap<long> receiveMozMap(); + MozMap<long>? receiveNullableMozMap(); + MozMap<long?> receiveMozMapOfNullableInts(); + MozMap<long?>? receiveNullableMozMapOfNullableInts(); + //XXXbz No support for MozMap of MozMaps return values yet. + //MozMap<MozMap<long>> receiveMozMapOfMozMaps(); + MozMap<any> receiveAnyMozMap(); + + // Typed array types + void passArrayBuffer(ArrayBuffer arg); + void passNullableArrayBuffer(ArrayBuffer? arg); + void passOptionalArrayBuffer(optional ArrayBuffer arg); + void passOptionalNullableArrayBuffer(optional ArrayBuffer? arg); + void passOptionalNullableArrayBufferWithDefaultValue(optional ArrayBuffer? arg= null); + void passArrayBufferView(ArrayBufferView arg); + void passInt8Array(Int8Array arg); + void passInt16Array(Int16Array arg); + void passInt32Array(Int32Array arg); + void passUint8Array(Uint8Array arg); + void passUint16Array(Uint16Array arg); + void passUint32Array(Uint32Array arg); + void passUint8ClampedArray(Uint8ClampedArray arg); + void passFloat32Array(Float32Array arg); + void passFloat64Array(Float64Array arg); + void passSequenceOfArrayBuffers(sequence<ArrayBuffer> arg); + void passSequenceOfNullableArrayBuffers(sequence<ArrayBuffer?> arg); + void passMozMapOfArrayBuffers(MozMap<ArrayBuffer> arg); + void passMozMapOfNullableArrayBuffers(MozMap<ArrayBuffer?> arg); + void passVariadicTypedArray(Float32Array... arg); + void passVariadicNullableTypedArray(Float32Array?... arg); + Uint8Array receiveUint8Array(); + attribute Uint8Array uint8ArrayAttr; + + // DOMString types + void passString(DOMString arg); + void passNullableString(DOMString? arg); + void passOptionalString(optional DOMString arg); + void passOptionalStringWithDefaultValue(optional DOMString arg = "abc"); + void passOptionalNullableString(optional DOMString? arg); + void passOptionalNullableStringWithDefaultValue(optional DOMString? arg = null); + void passVariadicString(DOMString... arg); + + // ByteString types + void passByteString(ByteString arg); + void passNullableByteString(ByteString? arg); + void passOptionalByteString(optional ByteString arg); + void passOptionalByteStringWithDefaultValue(optional ByteString arg = "abc"); + void passOptionalNullableByteString(optional ByteString? arg); + void passOptionalNullableByteStringWithDefaultValue(optional ByteString? arg = null); + void passVariadicByteString(ByteString... arg); + void passUnionByteString((ByteString or long) arg); + void passOptionalUnionByteString(optional (ByteString or long) arg); + void passOptionalUnionByteStringWithDefaultValue(optional (ByteString or long) arg = "abc"); + + // USVString types + void passSVS(USVString arg); + void passNullableSVS(USVString? arg); + void passOptionalSVS(optional USVString arg); + void passOptionalSVSWithDefaultValue(optional USVString arg = "abc"); + void passOptionalNullableSVS(optional USVString? arg); + void passOptionalNullableSVSWithDefaultValue(optional USVString? arg = null); + void passVariadicSVS(USVString... arg); + USVString receiveSVS(); + + // Enumerated types + void passEnum(TestEnum arg); + void passNullableEnum(TestEnum? arg); + void passOptionalEnum(optional TestEnum arg); + void passEnumWithDefault(optional TestEnum arg = "a"); + void passOptionalNullableEnum(optional TestEnum? arg); + void passOptionalNullableEnumWithDefaultValue(optional TestEnum? arg = null); + void passOptionalNullableEnumWithDefaultValue2(optional TestEnum? arg = "a"); + TestEnum receiveEnum(); + TestEnum? receiveNullableEnum(); + attribute TestEnum enumAttribute; + readonly attribute TestEnum readonlyEnumAttribute; + + // Callback types + void passCallback(TestCallback arg); + void passNullableCallback(TestCallback? arg); + void passOptionalCallback(optional TestCallback arg); + void passOptionalNullableCallback(optional TestCallback? arg); + void passOptionalNullableCallbackWithDefaultValue(optional TestCallback? arg = null); + TestCallback receiveCallback(); + TestCallback? receiveNullableCallback(); + void passNullableTreatAsNullCallback(TestTreatAsNullCallback? arg); + void passOptionalNullableTreatAsNullCallback(optional TestTreatAsNullCallback? arg); + void passOptionalNullableTreatAsNullCallbackWithDefaultValue(optional TestTreatAsNullCallback? arg = null); + + // Any types + void passAny(any arg); + void passVariadicAny(any... arg); + void passOptionalAny(optional any arg); + void passAnyDefaultNull(optional any arg = null); + void passSequenceOfAny(sequence<any> arg); + void passNullableSequenceOfAny(sequence<any>? arg); + void passOptionalSequenceOfAny(optional sequence<any> arg); + void passOptionalNullableSequenceOfAny(optional sequence<any>? arg); + void passOptionalSequenceOfAnyWithDefaultValue(optional sequence<any>? arg = null); + void passSequenceOfSequenceOfAny(sequence<sequence<any>> arg); + void passSequenceOfNullableSequenceOfAny(sequence<sequence<any>?> arg); + void passNullableSequenceOfNullableSequenceOfAny(sequence<sequence<any>?>? arg); + void passOptionalNullableSequenceOfNullableSequenceOfAny(optional sequence<sequence<any>?>? arg); + void passMozMapOfAny(MozMap<any> arg); + void passNullableMozMapOfAny(MozMap<any>? arg); + void passOptionalMozMapOfAny(optional MozMap<any> arg); + void passOptionalNullableMozMapOfAny(optional MozMap<any>? arg); + void passOptionalMozMapOfAnyWithDefaultValue(optional MozMap<any>? arg = null); + void passMozMapOfMozMapOfAny(MozMap<MozMap<any>> arg); + void passMozMapOfNullableMozMapOfAny(MozMap<MozMap<any>?> arg); + void passNullableMozMapOfNullableMozMapOfAny(MozMap<MozMap<any>?>? arg); + void passOptionalNullableMozMapOfNullableMozMapOfAny(optional MozMap<MozMap<any>?>? arg); + void passOptionalNullableMozMapOfNullableSequenceOfAny(optional MozMap<sequence<any>?>? arg); + void passOptionalNullableSequenceOfNullableMozMapOfAny(optional sequence<MozMap<any>?>? arg); + any receiveAny(); + + // object types + void passObject(object arg); + void passVariadicObject(object... arg); + void passNullableObject(object? arg); + void passVariadicNullableObject(object... arg); + void passOptionalObject(optional object arg); + void passOptionalNullableObject(optional object? arg); + void passOptionalNullableObjectWithDefaultValue(optional object? arg = null); + void passSequenceOfObject(sequence<object> arg); + void passSequenceOfNullableObject(sequence<object?> arg); + void passNullableSequenceOfObject(sequence<object>? arg); + void passOptionalNullableSequenceOfNullableSequenceOfObject(optional sequence<sequence<object>?>? arg); + void passOptionalNullableSequenceOfNullableSequenceOfNullableObject(optional sequence<sequence<object?>?>? arg); + void passMozMapOfObject(MozMap<object> arg); + object receiveObject(); + object? receiveNullableObject(); + + // Union types + void passUnion((object or long) arg); + // Some union tests are debug-only to avoid creating all those + // unused union types in opt builds. +#ifdef DEBUG + void passUnion2((long or boolean) arg); + void passUnion3((object or long or boolean) arg); + void passUnion4((Node or long or boolean) arg); + void passUnion5((object or boolean) arg); + void passUnion6((object or DOMString) arg); + void passUnion7((object or DOMString or long) arg); + void passUnion8((object or DOMString or boolean) arg); + void passUnion9((object or DOMString or long or boolean) arg); + void passUnion10(optional (EventInit or long) arg); + void passUnion11(optional (CustomEventInit or long) arg); + void passUnion12(optional (EventInit or long) arg = 5); + void passUnion13(optional (object or long?) arg = null); + void passUnion14(optional (object or long?) arg = 5); + void passUnion15((sequence<long> or long) arg); + void passUnion16(optional (sequence<long> or long) arg); + void passUnion17(optional (sequence<long>? or long) arg = 5); + void passUnion18((sequence<object> or long) arg); + void passUnion19(optional (sequence<object> or long) arg); + void passUnion20(optional (sequence<object> or long) arg = []); + void passUnion21((MozMap<long> or long) arg); + void passUnion22((MozMap<object> or long) arg); + void passUnion23((sequence<ImageData> or long) arg); + void passUnion24((sequence<ImageData?> or long) arg); + void passUnion25((sequence<sequence<ImageData>> or long) arg); + void passUnion26((sequence<sequence<ImageData?>> or long) arg); + void passUnion27(optional (sequence<DOMString> or EventInit) arg); + void passUnion28(optional (EventInit or sequence<DOMString>) arg); + void passUnionWithCallback((EventHandler or long) arg); + void passUnionWithByteString((ByteString or long) arg); + void passUnionWithMozMap((MozMap<DOMString> or DOMString) arg); + void passUnionWithMozMapAndSequence((MozMap<DOMString> or sequence<DOMString>) arg); + void passUnionWithSequenceAndMozMap((sequence<DOMString> or MozMap<DOMString>) arg); + void passUnionWithSVS((USVString or long) arg); +#endif + void passUnionWithNullable((object? or long) arg); + void passNullableUnion((object or long)? arg); + void passOptionalUnion(optional (object or long) arg); + void passOptionalNullableUnion(optional (object or long)? arg); + void passOptionalNullableUnionWithDefaultValue(optional (object or long)? arg = null); + //void passUnionWithInterfaces((TestInterface or TestExternalInterface) arg); + //void passUnionWithInterfacesAndNullable((TestInterface? or TestExternalInterface) arg); + //void passUnionWithSequence((sequence<object> or long) arg); + void passUnionWithArrayBuffer((ArrayBuffer or long) arg); + void 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. + void passUnionWithEnum((SupportedType or object) arg); + + // Trying to use a callback in a union won't include the test + // headers, unfortunately, so won't compile. + // void passUnionWithCallback((TestCallback or long) arg); + void passUnionWithObject((object or long) arg); + //void passUnionWithDict((Dict or long) arg); + + void passUnionWithDefaultValue1(optional (double or DOMString) arg = ""); + void passUnionWithDefaultValue2(optional (double or DOMString) arg = 1); + void passUnionWithDefaultValue3(optional (double or DOMString) arg = 1.5); + void passUnionWithDefaultValue4(optional (float or DOMString) arg = ""); + void passUnionWithDefaultValue5(optional (float or DOMString) arg = 1); + void passUnionWithDefaultValue6(optional (float or DOMString) arg = 1.5); + void passUnionWithDefaultValue7(optional (unrestricted double or DOMString) arg = ""); + void passUnionWithDefaultValue8(optional (unrestricted double or DOMString) arg = 1); + void passUnionWithDefaultValue9(optional (unrestricted double or DOMString) arg = 1.5); + void passUnionWithDefaultValue10(optional (unrestricted double or DOMString) arg = Infinity); + void passUnionWithDefaultValue11(optional (unrestricted float or DOMString) arg = ""); + void passUnionWithDefaultValue12(optional (unrestricted float or DOMString) arg = 1); + void passUnionWithDefaultValue13(optional (unrestricted float or DOMString) arg = Infinity); + void passUnionWithDefaultValue14(optional (double or ByteString) arg = ""); + void passUnionWithDefaultValue15(optional (double or ByteString) arg = 1); + void passUnionWithDefaultValue16(optional (double or ByteString) arg = 1.5); + void passUnionWithDefaultValue17(optional (double or SupportedType) arg = "text/html"); + void passUnionWithDefaultValue18(optional (double or SupportedType) arg = 1); + void passUnionWithDefaultValue19(optional (double or SupportedType) arg = 1.5); + + void passNullableUnionWithDefaultValue1(optional (double or DOMString)? arg = ""); + void passNullableUnionWithDefaultValue2(optional (double or DOMString)? arg = 1); + void passNullableUnionWithDefaultValue3(optional (double or DOMString)? arg = null); + void passNullableUnionWithDefaultValue4(optional (float or DOMString)? arg = ""); + void passNullableUnionWithDefaultValue5(optional (float or DOMString)? arg = 1); + void passNullableUnionWithDefaultValue6(optional (float or DOMString)? arg = null); + void passNullableUnionWithDefaultValue7(optional (unrestricted double or DOMString)? arg = ""); + void passNullableUnionWithDefaultValue8(optional (unrestricted double or DOMString)? arg = 1); + void passNullableUnionWithDefaultValue9(optional (unrestricted double or DOMString)? arg = null); + void passNullableUnionWithDefaultValue10(optional (unrestricted float or DOMString)? arg = ""); + void passNullableUnionWithDefaultValue11(optional (unrestricted float or DOMString)? arg = 1); + void passNullableUnionWithDefaultValue12(optional (unrestricted float or DOMString)? arg = null); + void passNullableUnionWithDefaultValue13(optional (double or ByteString)? arg = ""); + void passNullableUnionWithDefaultValue14(optional (double or ByteString)? arg = 1); + void passNullableUnionWithDefaultValue15(optional (double or ByteString)? arg = 1.5); + void passNullableUnionWithDefaultValue16(optional (double or ByteString)? arg = null); + void passNullableUnionWithDefaultValue17(optional (double or SupportedType)? arg = "text/html"); + void passNullableUnionWithDefaultValue18(optional (double or SupportedType)? arg = 1); + void passNullableUnionWithDefaultValue19(optional (double or SupportedType)? arg = 1.5); + void passNullableUnionWithDefaultValue20(optional (double or SupportedType)? arg = null); + + void passSequenceOfUnions(sequence<(CanvasPattern or CanvasGradient)> arg); + void passSequenceOfUnions2(sequence<(object or long)> arg); + void passVariadicUnion((CanvasPattern or CanvasGradient)... arg); + + void passSequenceOfNullableUnions(sequence<(CanvasPattern or CanvasGradient)?> arg); + void passVariadicNullableUnion((CanvasPattern or CanvasGradient)?... arg); + void passMozMapOfUnions(MozMap<(CanvasPattern or CanvasGradient)> arg); + // XXXbz no move constructor on some unions + // void passMozMapOfUnions2(MozMap<(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; + + // Date types + void passDate(Date arg); + void passNullableDate(Date? arg); + void passOptionalDate(optional Date arg); + void passOptionalNullableDate(optional Date? arg); + void passOptionalNullableDateWithDefaultValue(optional Date? arg = null); + void passDateSequence(sequence<Date> arg); + void passNullableDateSequence(sequence<Date?> arg); + void passDateMozMap(MozMap<Date> arg); + Date receiveDate(); + Date? receiveNullableDate(); + + // Promise types + void passPromise(Promise<any> arg); + void passNullablePromise(Promise<any>? arg); + void passOptionalPromise(optional Promise<any> arg); + void passOptionalNullablePromise(optional Promise<any>? arg); + void passOptionalNullablePromiseWithDefaultValue(optional Promise<any>? arg = null); + void passPromiseSequence(sequence<Promise<any>> arg); + void passNullablePromiseSequence(sequence<Promise<any>?> arg); + Promise<any> receivePromise(); + Promise<any> receiveAddrefedPromise(); + + // binaryNames tests + void methodRenamedFrom(); + [BinaryName="otherMethodRenamedTo"] + void otherMethodRenamedFrom(); + void methodRenamedFrom(byte argument); + readonly attribute byte attributeGetterRenamedFrom; + attribute byte attributeRenamedFrom; + [BinaryName="otherAttributeRenamedTo"] + attribute byte otherAttributeRenamedFrom; + + void passDictionary(optional Dict x); + void 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(); + void passOtherDictionary(optional GrandparentDict x); + void passSequenceOfDictionaries(sequence<Dict> x); + void passMozMapOfDictionaries(MozMap<GrandparentDict> x); + // No support for nullable dictionaries inside a sequence (nor should there be) + // void passSequenceOfNullableDictionaries(sequence<Dict?> x); + void passDictionaryOrLong(optional Dict x); + void passDictionaryOrLong(long x); + + void passDictContainingDict(optional DictContainingDict arg); + void passDictContainingSequence(optional DictContainingSequence arg); + DictContainingSequence receiveDictContainingSequence(); + void passVariadicDictionary(Dict... arg); + + // EnforceRange/Clamp tests + void dontEnforceRangeOrClamp(byte arg); + void doEnforceRange([EnforceRange] byte arg); + void doClamp([Clamp] byte arg); + [EnforceRange] attribute byte enforcedByte; + [Clamp] attribute byte clampedByte; + + // Typedefs + const myLong myLongConstant = 5; + void exerciseTypedefInterfaces1(AnotherNameForTestInterface arg); + AnotherNameForTestInterface exerciseTypedefInterfaces2(NullableTestInterface arg); + void exerciseTypedefInterfaces3(YetAnotherNameForTestInterface arg); + + // Deprecated methods and attributes + [Deprecated="GetAttributeNode"] + attribute boolean deprecatedAttribute; + [Deprecated="GetAttributeNode"] + void deprecatedMethod(boolean arg); + [Deprecated="GetAttributeNode"] + void deprecatedMethodWithContext(any arg); + + // Static methods and attributes + static attribute boolean staticAttribute; + static void staticMethod(boolean arg); + static void staticMethodWithContext(any arg); + + // Deprecated methods and attributes; + [Deprecated="GetAttributeNode"] + static attribute boolean staticDeprecatedAttribute; + [Deprecated="GetAttributeNode"] + static void staticDeprecatedMethod(boolean arg); + [Deprecated="GetAttributeNode"] + static void staticDeprecatedMethodWithContext(any arg); + + // Overload resolution tests + //void overload1(DOMString... strs); + boolean overload1(TestInterface arg); + TestInterface overload1(DOMString strs, TestInterface arg); + void overload2(TestInterface arg); + void overload2(optional Dict arg); + void overload2(boolean arg); + void overload2(DOMString arg); + void overload2(Date arg); + void overload3(TestInterface arg); + void overload3(TestCallback arg); + void overload3(boolean arg); + void overload4(TestInterface arg); + void overload4(TestCallbackInterface arg); + void overload4(DOMString arg); + void overload5(long arg); + void overload5(TestEnum arg); + void overload6(long arg); + void overload6(boolean arg); + void overload7(long arg); + void overload7(boolean arg); + void overload7(ByteString arg); + void overload8(long arg); + void overload8(TestInterface arg); + void overload9(long? arg); + void overload9(DOMString arg); + void overload10(long? arg); + void overload10(object arg); + void overload11(long arg); + void overload11(DOMString? arg); + void overload12(long arg); + void overload12(boolean? arg); + void overload13(long? arg); + void overload13(boolean arg); + void overload14(optional long arg); + void overload14(TestInterface arg); + void overload15(long arg); + void overload15(optional TestInterface arg); + void overload16(long arg); + void overload16(optional TestInterface? arg); + void overload17(sequence<long> arg); + void overload17(MozMap<long> arg); + void overload18(MozMap<DOMString> arg); + void overload18(sequence<DOMString> arg); + void overload19(sequence<long> arg); + void overload19(optional Dict arg); + void overload20(optional Dict arg); + void overload20(sequence<long> arg); + + // Variadic handling + void passVariadicThirdArg(DOMString arg1, long arg2, TestInterface... arg3); + + // Conditionally exposed methods/attributes + [Pref="abc.def"] + readonly attribute boolean prefable1; + [Pref="abc.def"] + readonly attribute boolean prefable2; + [Pref="ghi.jkl"] + readonly attribute boolean prefable3; + [Pref="ghi.jkl"] + readonly attribute boolean prefable4; + [Pref="abc.def"] + readonly attribute boolean prefable5; + [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + readonly attribute boolean prefable6; + [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + readonly attribute boolean prefable7; + [Pref="ghi.jkl", Func="nsGenericHTMLElement::TouchEventsEnabled"] + readonly attribute boolean prefable8; + [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + readonly attribute boolean prefable9; + [Pref="abc.def"] + void prefable10(); + [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + void prefable11(); + [Pref="abc.def", Func="TestFuncControlledMember"] + readonly attribute boolean prefable12; + [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + void prefable13(); + [Pref="abc.def", Func="TestFuncControlledMember"] + readonly attribute boolean prefable14; + [Func="TestFuncControlledMember"] + readonly attribute boolean prefable15; + [Func="TestFuncControlledMember"] + readonly attribute boolean prefable16; + [Pref="abc.def", Func="TestFuncControlledMember"] + void prefable17(); + [Func="TestFuncControlledMember"] + void prefable18(); + [Func="TestFuncControlledMember"] + void prefable19(); + + // Conditionally exposed methods/attributes involving [SecureContext] + [SecureContext] + readonly attribute boolean conditionalOnSecureContext1; + [SecureContext, Pref="abc.def"] + readonly attribute boolean conditionalOnSecureContext2; + [SecureContext, Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + readonly attribute boolean conditionalOnSecureContext3; + [SecureContext, Pref="abc.def", Func="TestFuncControlledMember"] + readonly attribute boolean conditionalOnSecureContext4; + [SecureContext] + void conditionalOnSecureContext5(); + [SecureContext, Pref="abc.def"] + void conditionalOnSecureContext6(); + [SecureContext, Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + void conditionalOnSecureContext7(); + [SecureContext, Pref="abc.def", Func="TestFuncControlledMember"] + void conditionalOnSecureContext8(); + + // Miscellania + [LenientThis] attribute long attrWithLenientThis; + [Unforgeable] readonly attribute long unforgeableAttr; + [Unforgeable, ChromeOnly] readonly attribute long unforgeableAttr2; + [Unforgeable] long unforgeableMethod(); + [Unforgeable, ChromeOnly] long unforgeableMethod2(); + stringifier; + void passRenamedInterface(TestRenamedInterface arg); + [PutForwards=writableByte] readonly attribute TestExampleInterface putForwardsAttr; + [PutForwards=writableByte, LenientThis] readonly attribute TestExampleInterface putForwardsAttr2; + [PutForwards=writableByte, ChromeOnly] readonly attribute TestExampleInterface putForwardsAttr3; + [Throws] void throwingMethod(); + [Throws] attribute boolean throwingAttr; + [GetterThrows] attribute boolean throwingGetterAttr; + [SetterThrows] attribute boolean throwingSetterAttr; + [NeedsSubjectPrincipal] void needsSubjectPrincipalMethod(); + [NeedsSubjectPrincipal] attribute boolean needsSubjectPrincipalAttr; + [NeedsCallerType] void needsCallerTypeMethod(); + [NeedsCallerType] attribute boolean needsCallerTypeAttr; + legacycaller short(unsigned long arg1, TestInterface arg2); + void passArgsWithDefaults(optional long arg1, + optional TestInterface? arg2 = null, + optional Dict arg3, optional double arg4 = 5.0, + optional float arg5); + attribute any jsonifierShouldSkipThis; + attribute TestParentInterface jsonifierShouldSkipThis2; + attribute TestCallbackInterface jsonifierShouldSkipThis3; + jsonifier; + + attribute byte dashed-attribute; + void dashed-method(); + + // If you add things here, add them to TestCodeGen and TestJSImplGen as well +}; + +interface TestExampleProxyInterface { + getter long longIndexedGetter(unsigned long ix); + setter creator void longIndexedSetter(unsigned long y, long z); + stringifier DOMString myStringifier(); + getter short shortNameGetter(DOMString nom); + deleter void (DOMString nomnom); + setter creator void shortNamedSetter(DOMString me, short value); +}; + +[Exposed=(Window,Worker)] +interface TestExampleWorkerInterface { + [NeedsSubjectPrincipal] void needsSubjectPrincipalMethod(); + [NeedsSubjectPrincipal] attribute boolean needsSubjectPrincipalAttr; + [NeedsCallerType] void needsCallerTypeMethod(); + [NeedsCallerType] attribute boolean needsCallerTypeAttr; +}; diff --git a/dom/bindings/test/TestFunctions.cpp b/dom/bindings/test/TestFunctions.cpp new file mode 100644 index 000000000..f05c92b48 --- /dev/null +++ b/dom/bindings/test/TestFunctions.cpp @@ -0,0 +1,94 @@ +/* -*- 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/TestFunctions.h" +#include "mozilla/dom/TestFunctionsBinding.h" +#include "nsStringBuffer.h" + +namespace mozilla { +namespace dom { + +/* static */ TestFunctions* +TestFunctions::Constructor(GlobalObject& aGlobal, ErrorResult& aRv) +{ + 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.SetStringBuffer(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. +} + +bool +TestFunctions::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, + JS::MutableHandle<JSObject*> aWrapper) +{ + return TestFunctionsBinding::Wrap(aCx, this, aGivenProto, aWrapper); +} + +} +} diff --git a/dom/bindings/test/TestFunctions.h b/dom/bindings/test/TestFunctions.h new file mode 100644 index 000000000..b35464824 --- /dev/null +++ b/dom/bindings/test/TestFunctions.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_TestFunctions_h +#define mozilla_dom_TestFunctions_h + +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/NonRefcountedDOMObject.h" +#include "nsString.h" + +namespace mozilla { +namespace dom { + +class Promise; +class PromiseReturner; + +class TestFunctions : public NonRefcountedDOMObject { +public: + static TestFunctions* Constructor(GlobalObject& aGlobal, ErrorResult& aRv); + + static void + ThrowUncatchableException(GlobalObject& aGlobal, ErrorResult& aRv); + + static Promise* + PassThroughPromise(GlobalObject& aGlobal, Promise& aPromise); + + 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); + + bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, + JS::MutableHandle<JSObject*> aWrapper); +private: + nsString mStringData; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_TestFunctions_h diff --git a/dom/bindings/test/TestInterfaceIterableDouble.cpp b/dom/bindings/test/TestInterfaceIterableDouble.cpp new file mode 100644 index 000000000..33a4c97d1 --- /dev/null +++ b/dom/bindings/test/TestInterfaceIterableDouble.cpp @@ -0,0 +1,82 @@ +/* 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 { +namespace 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>(NS_LITERAL_STRING("a"), + NS_LITERAL_STRING("b"))); + mValues.AppendElement(std::pair<nsString, nsString>(NS_LITERAL_STRING("c"), + NS_LITERAL_STRING("d"))); + mValues.AppendElement(std::pair<nsString, nsString>(NS_LITERAL_STRING("e"), + NS_LITERAL_STRING("f"))); +} + +//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 TestInterfaceIterableDoubleBinding::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 dom +} // namespace mozilla diff --git a/dom/bindings/test/TestInterfaceIterableDouble.h b/dom/bindings/test/TestInterfaceIterableDouble.h new file mode 100644 index 000000000..1e9ff7acd --- /dev/null +++ b/dom/bindings/test/TestInterfaceIterableDouble.h @@ -0,0 +1,51 @@ +/* -*- 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_TestInterfaceIterableDouble_h +#define mozilla_dom_TestInterfaceIterableDouble_h + +#include "nsWrapperCache.h" +#include "nsCOMPtr.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_SCRIPT_HOLDER_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() {} + 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 000000000..29151a4c5 --- /dev/null +++ b/dom/bindings/test/TestInterfaceIterableDoubleUnion.cpp @@ -0,0 +1,83 @@ +/* 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 "nsPIDOMWindow.h" +#include "mozilla/dom/BindingUtils.h" + +namespace mozilla { +namespace 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>(NS_LITERAL_STRING("long"), + a)); + a.SetAsString() = NS_LITERAL_STRING("a"); + mValues.AppendElement(std::pair<nsString, OwningStringOrLong>(NS_LITERAL_STRING("string"), + 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 TestInterfaceIterableDoubleUnionBinding::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 dom +} // namespace mozilla diff --git a/dom/bindings/test/TestInterfaceIterableDoubleUnion.h b/dom/bindings/test/TestInterfaceIterableDoubleUnion.h new file mode 100644 index 000000000..ff6ea2175 --- /dev/null +++ b/dom/bindings/test/TestInterfaceIterableDoubleUnion.h @@ -0,0 +1,51 @@ +/* -*- 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_TestInterfaceIterableDoubleUnion_h +#define mozilla_dom_TestInterfaceIterableDoubleUnion_h + +#include "nsWrapperCache.h" +#include "nsCOMPtr.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_SCRIPT_HOLDER_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() {} + 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 000000000..5f8d6c640 --- /dev/null +++ b/dom/bindings/test/TestInterfaceIterableSingle.cpp @@ -0,0 +1,77 @@ +/* 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 { +namespace 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 TestInterfaceIterableSingleBinding::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 dom +} // namespace mozilla diff --git a/dom/bindings/test/TestInterfaceIterableSingle.h b/dom/bindings/test/TestInterfaceIterableSingle.h new file mode 100644 index 000000000..a071ada8b --- /dev/null +++ b/dom/bindings/test/TestInterfaceIterableSingle.h @@ -0,0 +1,51 @@ +/* -*- 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_TestInterfaceIterableSingle_h +#define mozilla_dom_TestInterfaceIterableSingle_h + +#include "nsWrapperCache.h" +#include "nsCOMPtr.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_SCRIPT_HOLDER_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() {} + nsCOMPtr<nsPIDOMWindowInner> mParent; + nsTArray<int32_t> mValues; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_TestInterfaceIterableSingle_h diff --git a/dom/bindings/test/TestInterfaceJS.js b/dom/bindings/test/TestInterfaceJS.js new file mode 100644 index 000000000..1a5bf8e61 --- /dev/null +++ b/dom/bindings/test/TestInterfaceJS.js @@ -0,0 +1,166 @@ +/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; +const Cu = Components.utils; +const Ci = Components.interfaces; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +function TestInterfaceJS(anyArg, objectArg) {} + +TestInterfaceJS.prototype = { + classID: Components.ID("{2ac4e026-cf25-47d5-b067-78d553c3cad8}"), + contractID: "@mozilla.org/dom/test-interface-js;1", + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, + Ci.nsIDOMGlobalPropertyInitializer]), + + init: function(win) { this._win = win; }, + + __init: function (anyArg, objectArg, dictionaryArg) { + this._anyAttr = undefined; + this._objectAttr = null; + this._anyArg = anyArg; + this._objectArg = objectArg; + this._dictionaryArg = dictionaryArg; + this._cachedAttr = 15; + }, + + get anyArg() { return this._anyArg; }, + get objectArg() { return this._objectArg; }, + get dictionaryArg() { 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; }, + get dictionaryAttr() { return this._dictionaryAttr; }, + set dictionaryAttr(val) { this._dictionaryAttr = val; }, + pingPongAny: function(any) { return any; }, + pingPongObject: function(obj) { return obj; }, + pingPongObjectOrString: function(objectOrString) { return objectOrString; }, + pingPongDictionary: function(dict) { return dict; }, + pingPongDictionaryOrLong: function(dictOrLong) { return dictOrLong.anyMember || dictOrLong; }, + pingPongMap: function(map) { return JSON.stringify(map); }, + objectSequenceLength: function(seq) { return seq.length; }, + anySequenceLength: function(seq) { return seq.length; }, + + + getCallerPrincipal: function() { return Cu.getWebIDLCallerPrincipal().origin; }, + + convertSVS: function(svs) { return svs; }, + + pingPongUnion: function(x) { return x; }, + pingPongUnionContainingNull: function(x) { return x; }, + pingPongNullableUnion: function(x) { return x; }, + returnBadUnion: function(x) { return 3; }, + + get cachedAttr() { return this._cachedAttr; }, + setCachedAttr: function(n) { this._cachedAttr = n; }, + clearCachedAttrCache: function () { this.__DOM_IMPL__._clearCachedCachedAttrValue(); }, + + testSequenceOverload: function(arg) {}, + testSequenceUnion: function(arg) {}, + + testThrowError: function() { + throw new this._win.Error("We are an Error"); + }, + + testThrowDOMException: function() { + throw new this._win.DOMException("We are a DOMException", + "NotSupportedError"); + }, + + testThrowTypeError: function() { + throw new this._win.TypeError("We are a TypeError"); + }, + + testThrowCallbackError: function(callback) { + callback(); + }, + + testThrowXraySelfHosted: function() { + this._win.Array.indexOf(); + }, + + testThrowSelfHosted: function() { + Array.indexOf(); + }, + + testPromiseWithThrowingChromePromiseInit: function() { + return new this._win.Promise(function() { + noSuchMethodExistsYo1(); + }) + }, + + testPromiseWithThrowingContentPromiseInit: function(func) { + return new this._win.Promise(func); + }, + + testPromiseWithDOMExceptionThrowingPromiseInit: function() { + return new this._win.Promise(() => { + throw new this._win.DOMException("We are a second DOMException", + "NotFoundError"); + }) + }, + + testPromiseWithThrowingChromeThenFunction: function() { + return this._win.Promise.resolve(5).then(function() { + noSuchMethodExistsYo2(); + }); + }, + + testPromiseWithThrowingContentThenFunction: function(func) { + return this._win.Promise.resolve(10).then(func); + }, + + testPromiseWithDOMExceptionThrowingThenFunction: function() { + return this._win.Promise.resolve(5).then(() => { + throw new this._win.DOMException("We are a third DOMException", + "NetworkError"); + }); + }, + + testPromiseWithThrowingChromeThenable: function() { + var thenable = { + then: function() { + noSuchMethodExistsYo3() + } + }; + return new this._win.Promise(function(resolve) { + resolve(thenable) + }); + }, + + testPromiseWithThrowingContentThenable: function(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: function() { + 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); + } +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TestInterfaceJS]) diff --git a/dom/bindings/test/TestInterfaceJS.manifest b/dom/bindings/test/TestInterfaceJS.manifest new file mode 100644 index 000000000..161a42156 --- /dev/null +++ b/dom/bindings/test/TestInterfaceJS.manifest @@ -0,0 +1,4 @@ +component {2ac4e026-cf25-47d5-b067-78d553c3cad8} TestInterfaceJS.js +contract @mozilla.org/dom/test-interface-js;1 {2ac4e026-cf25-47d5-b067-78d553c3cad8} +component {4bc6f6f3-e005-4f0a-b42d-4d1663a9013a} TestInterfaceJSMaplike.js +contract @mozilla.org/dom/test-interface-js-maplike;1 {4bc6f6f3-e005-4f0a-b42d-4d1663a9013a} diff --git a/dom/bindings/test/TestInterfaceJSMaplike.js b/dom/bindings/test/TestInterfaceJSMaplike.js new file mode 100644 index 000000000..b108ef5b6 --- /dev/null +++ b/dom/bindings/test/TestInterfaceJSMaplike.js @@ -0,0 +1,38 @@ +/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; +const Cu = Components.utils; +const Ci = Components.interfaces; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +function TestInterfaceJSMaplike() {} + +TestInterfaceJSMaplike.prototype = { + classID: Components.ID("{4bc6f6f3-e005-4f0a-b42d-4d1663a9013a}"), + contractID: "@mozilla.org/dom/test-interface-js-maplike;1", + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, + Ci.nsIDOMGlobalPropertyInitializer]), + + init: function(win) { this._win = win; }, + + __init: function () {}, + + setInternal: function(aKey, aValue) { + return this.__DOM_IMPL__.__set(aKey, aValue); + }, + + deleteInternal: function(aKey) { + return this.__DOM_IMPL__.__delete(aKey); + }, + + clearInternal: function() { + return this.__DOM_IMPL__.__clear(); + } +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TestInterfaceJSMaplike]) diff --git a/dom/bindings/test/TestInterfaceMaplike.cpp b/dom/bindings/test/TestInterfaceMaplike.cpp new file mode 100644 index 000000000..4abace83c --- /dev/null +++ b/dom/bindings/test/TestInterfaceMaplike.cpp @@ -0,0 +1,84 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/TestInterfaceMaplike.h" +#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h" +#include "nsPIDOMWindow.h" +#include "mozilla/dom/BindingUtils.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceMaplike, mParent) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceMaplike) +NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceMaplike) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceMaplike) +NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY +NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +TestInterfaceMaplike::TestInterfaceMaplike(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 TestInterfaceMaplikeBinding::Wrap(aCx, this, aGivenProto); +} + +nsPIDOMWindowInner* +TestInterfaceMaplike::GetParentObject() const +{ + return mParent; +} + +void +TestInterfaceMaplike::SetInternal(const nsAString& aKey, int32_t aValue) +{ + ErrorResult rv; + TestInterfaceMaplikeBinding::MaplikeHelpers::Set(this, aKey, aValue, rv); +} + +void +TestInterfaceMaplike::ClearInternal() +{ + ErrorResult rv; + TestInterfaceMaplikeBinding::MaplikeHelpers::Clear(this, rv); +} + +bool +TestInterfaceMaplike::DeleteInternal(const nsAString& aKey) +{ + ErrorResult rv; + return TestInterfaceMaplikeBinding::MaplikeHelpers::Delete(this, aKey, rv); +} + +bool +TestInterfaceMaplike::HasInternal(const nsAString& aKey) +{ + ErrorResult rv; + return TestInterfaceMaplikeBinding::MaplikeHelpers::Has(this, aKey, rv); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/bindings/test/TestInterfaceMaplike.h b/dom/bindings/test/TestInterfaceMaplike.h new file mode 100644 index 000000000..c012a7a21 --- /dev/null +++ b/dom/bindings/test/TestInterfaceMaplike.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_TestInterfaceMaplike_h +#define mozilla_dom_TestInterfaceMaplike_h + +#include "nsWrapperCache.h" +#include "nsCOMPtr.h" + +class 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_SCRIPT_HOLDER_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); +private: + virtual ~TestInterfaceMaplike() {} + nsCOMPtr<nsPIDOMWindowInner> mParent; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_TestInterfaceMaplike_h diff --git a/dom/bindings/test/TestInterfaceMaplikeObject.cpp b/dom/bindings/test/TestInterfaceMaplikeObject.cpp new file mode 100644 index 000000000..3dc1ffdc4 --- /dev/null +++ b/dom/bindings/test/TestInterfaceMaplikeObject.cpp @@ -0,0 +1,88 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/TestInterfaceMaplikeObject.h" +#include "mozilla/dom/TestInterfaceMaplike.h" +#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h" +#include "nsPIDOMWindow.h" +#include "mozilla/dom/BindingUtils.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceMaplikeObject, mParent) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceMaplikeObject) +NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceMaplikeObject) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceMaplikeObject) +NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY +NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +TestInterfaceMaplikeObject::TestInterfaceMaplikeObject(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 TestInterfaceMaplikeObjectBinding::Wrap(aCx, this, aGivenProto); +} + +nsPIDOMWindowInner* +TestInterfaceMaplikeObject::GetParentObject() const +{ + return mParent; +} + +void +TestInterfaceMaplikeObject::SetInternal(const nsAString& aKey) +{ + RefPtr<TestInterfaceMaplike> p(new TestInterfaceMaplike(mParent)); + ErrorResult rv; + TestInterfaceMaplikeObjectBinding::MaplikeHelpers::Set(this, aKey, *p, rv); +} + +void +TestInterfaceMaplikeObject::ClearInternal() +{ + ErrorResult rv; + TestInterfaceMaplikeObjectBinding::MaplikeHelpers::Clear(this, rv); +} + +bool +TestInterfaceMaplikeObject::DeleteInternal(const nsAString& aKey) +{ + ErrorResult rv; + return TestInterfaceMaplikeObjectBinding::MaplikeHelpers::Delete(this, aKey, rv); +} + +bool +TestInterfaceMaplikeObject::HasInternal(const nsAString& aKey) +{ + ErrorResult rv; + return TestInterfaceMaplikeObjectBinding::MaplikeHelpers::Has(this, aKey, rv); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/bindings/test/TestInterfaceMaplikeObject.h b/dom/bindings/test/TestInterfaceMaplikeObject.h new file mode 100644 index 000000000..af4660c0d --- /dev/null +++ b/dom/bindings/test/TestInterfaceMaplikeObject.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_TestInterfaceMaplikeObject_h +#define mozilla_dom_TestInterfaceMaplikeObject_h + +#include "nsWrapperCache.h" +#include "nsCOMPtr.h" + +class 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 TestInterfaceMaplikeObject final : public nsISupports, + public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_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); +private: + virtual ~TestInterfaceMaplikeObject() {} + nsCOMPtr<nsPIDOMWindowInner> mParent; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_TestInterfaceMaplikeObject_h diff --git a/dom/bindings/test/TestInterfaceSetlike.cpp b/dom/bindings/test/TestInterfaceSetlike.cpp new file mode 100644 index 000000000..c9f556076 --- /dev/null +++ b/dom/bindings/test/TestInterfaceSetlike.cpp @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/TestInterfaceSetlike.h" +#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h" +#include "nsPIDOMWindow.h" +#include "mozilla/dom/BindingUtils.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceSetlike, mParent) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceSetlike) +NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceSetlike) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceSetlike) +NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY +NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +TestInterfaceSetlike::TestInterfaceSetlike(JSContext* aCx, + 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 TestInterfaceSetlikeBinding::Wrap(aCx, this, aGivenProto); +} + +nsPIDOMWindowInner* +TestInterfaceSetlike::GetParentObject() const +{ + return mParent; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/bindings/test/TestInterfaceSetlike.h b/dom/bindings/test/TestInterfaceSetlike.h new file mode 100644 index 000000000..c9f464960 --- /dev/null +++ b/dom/bindings/test/TestInterfaceSetlike.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_TestInterfaceSetlike_h +#define mozilla_dom_TestInterfaceSetlike_h + +#include "nsWrapperCache.h" +#include "nsCOMPtr.h" + +class 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_SCRIPT_HOLDER_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() {} + 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 000000000..5499553fa --- /dev/null +++ b/dom/bindings/test/TestInterfaceSetlikeNode.cpp @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/TestInterfaceSetlikeNode.h" +#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h" +#include "nsPIDOMWindow.h" +#include "mozilla/dom/BindingUtils.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceSetlikeNode, mParent) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceSetlikeNode) +NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceSetlikeNode) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceSetlikeNode) +NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY +NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +TestInterfaceSetlikeNode::TestInterfaceSetlikeNode(JSContext* aCx, + 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 TestInterfaceSetlikeNodeBinding::Wrap(aCx, this, aGivenProto); +} + +nsPIDOMWindowInner* +TestInterfaceSetlikeNode::GetParentObject() const +{ + return mParent; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/bindings/test/TestInterfaceSetlikeNode.h b/dom/bindings/test/TestInterfaceSetlikeNode.h new file mode 100644 index 000000000..05b14190e --- /dev/null +++ b/dom/bindings/test/TestInterfaceSetlikeNode.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_TestInterfaceSetlikeNode_h +#define mozilla_dom_TestInterfaceSetlikeNode_h + +#include "nsWrapperCache.h" +#include "nsCOMPtr.h" + +class 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_SCRIPT_HOLDER_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() {} + 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 000000000..a131dcdfe --- /dev/null +++ b/dom/bindings/test/TestJSImplGen.webidl @@ -0,0 +1,836 @@ +/* -*- 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 = void(); + +enum MyTestEnum { + "a", + "b" +}; + +// We don't support multiple constructors (bug 869268) or named constructors +// for JS-implemented WebIDL. +[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), + JSImplementation="@mozilla.org/test-js-impl-interface;1"] +interface TestJSImplInterface { + // Integer types + // XXXbz add tests for throwing versions of all the integer stuff + readonly attribute byte readonlyByte; + attribute byte writableByte; + void passByte(byte arg); + byte receiveByte(); + void passOptionalByte(optional byte arg); + void passOptionalByteBeforeRequired(optional byte arg1, byte arg2); + void passOptionalByteWithDefault(optional byte arg = 0); + void passOptionalByteWithDefaultBeforeRequired(optional byte arg1 = 0, byte arg2); + void passNullableByte(byte? arg); + void passOptionalNullableByte(optional byte? arg); + void passVariadicByte(byte... arg); + [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; + void passShort(short arg); + short receiveShort(); + void passOptionalShort(optional short arg); + void passOptionalShortWithDefault(optional short arg = 5); + + readonly attribute long readonlyLong; + attribute long writableLong; + void passLong(long arg); + long receiveLong(); + void passOptionalLong(optional long arg); + void passOptionalLongWithDefault(optional long arg = 7); + + readonly attribute long long readonlyLongLong; + attribute long long writableLongLong; + void passLongLong(long long arg); + long long receiveLongLong(); + void passOptionalLongLong(optional long long arg); + void passOptionalLongLongWithDefault(optional long long arg = -12); + + readonly attribute octet readonlyOctet; + attribute octet writableOctet; + void passOctet(octet arg); + octet receiveOctet(); + void passOptionalOctet(optional octet arg); + void passOptionalOctetWithDefault(optional octet arg = 19); + + readonly attribute unsigned short readonlyUnsignedShort; + attribute unsigned short writableUnsignedShort; + void passUnsignedShort(unsigned short arg); + unsigned short receiveUnsignedShort(); + void passOptionalUnsignedShort(optional unsigned short arg); + void passOptionalUnsignedShortWithDefault(optional unsigned short arg = 2); + + readonly attribute unsigned long readonlyUnsignedLong; + attribute unsigned long writableUnsignedLong; + void passUnsignedLong(unsigned long arg); + unsigned long receiveUnsignedLong(); + void passOptionalUnsignedLong(optional unsigned long arg); + void passOptionalUnsignedLongWithDefault(optional unsigned long arg = 6); + + readonly attribute unsigned long long readonlyUnsignedLongLong; + attribute unsigned long long writableUnsignedLongLong; + void passUnsignedLongLong(unsigned long long arg); + unsigned long long receiveUnsignedLongLong(); + void passOptionalUnsignedLongLong(optional unsigned long long arg); + void 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; + void 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] + void 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& + void passSelf(TestJSImplInterface arg); + void passNullableSelf(TestJSImplInterface? arg); + attribute TestJSImplInterface nonNullSelf; + attribute TestJSImplInterface? nullableSelf; + [Cached, Pure] + readonly attribute TestJSImplInterface cachedSelf; + // Optional arguments + void passOptionalSelf(optional TestJSImplInterface? arg); + void passOptionalNonNullSelf(optional TestJSImplInterface arg); + void 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(); + + // Non-castable interface types + IndirectlyImplementedInterface receiveOther(); + IndirectlyImplementedInterface? receiveNullableOther(); + IndirectlyImplementedInterface receiveWeakOther(); + IndirectlyImplementedInterface? receiveWeakNullableOther(); + + void passOther(IndirectlyImplementedInterface arg); + void passNullableOther(IndirectlyImplementedInterface? arg); + attribute IndirectlyImplementedInterface nonNullOther; + attribute IndirectlyImplementedInterface? nullableOther; + // Optional arguments + void passOptionalOther(optional IndirectlyImplementedInterface? arg); + void passOptionalNonNullOther(optional IndirectlyImplementedInterface arg); + void passOptionalOtherWithDefault(optional IndirectlyImplementedInterface? arg = null); + + // External interface types + TestExternalInterface receiveExternal(); + TestExternalInterface? receiveNullableExternal(); + TestExternalInterface receiveWeakExternal(); + TestExternalInterface? receiveWeakNullableExternal(); + void passExternal(TestExternalInterface arg); + void passNullableExternal(TestExternalInterface? arg); + attribute TestExternalInterface nonNullExternal; + attribute TestExternalInterface? nullableExternal; + // Optional arguments + void passOptionalExternal(optional TestExternalInterface? arg); + void passOptionalNonNullExternal(optional TestExternalInterface arg); + void passOptionalExternalWithDefault(optional TestExternalInterface? arg = null); + + // Callback interface types + TestCallbackInterface receiveCallbackInterface(); + TestCallbackInterface? receiveNullableCallbackInterface(); + TestCallbackInterface receiveWeakCallbackInterface(); + TestCallbackInterface? receiveWeakNullableCallbackInterface(); + void passCallbackInterface(TestCallbackInterface arg); + void passNullableCallbackInterface(TestCallbackInterface? arg); + attribute TestCallbackInterface nonNullCallbackInterface; + attribute TestCallbackInterface? nullableCallbackInterface; + // Optional arguments + void passOptionalCallbackInterface(optional TestCallbackInterface? arg); + void passOptionalNonNullCallbackInterface(optional TestCallbackInterface arg); + void passOptionalCallbackInterfaceWithDefault(optional TestCallbackInterface? arg = null); + + // Miscellaneous interface tests + IndirectlyImplementedInterface receiveConsequentialInterface(); + void passConsequentialInterface(IndirectlyImplementedInterface arg); + + // 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(); + void passSequence(sequence<long> arg); + void passNullableSequence(sequence<long>? arg); + void passSequenceOfNullableInts(sequence<long?> arg); + void passOptionalSequenceOfNullableInts(optional sequence<long?> arg); + void 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(); + void passCastableObjectSequence(sequence<TestJSImplInterface> arg); + void passNullableCastableObjectSequence(sequence<TestJSImplInterface?> arg); + void passCastableObjectNullableSequence(sequence<TestJSImplInterface>? arg); + void passNullableCastableObjectNullableSequence(sequence<TestJSImplInterface?>? arg); + void passOptionalSequence(optional sequence<long> arg); + void passOptionalSequenceWithDefaultValue(optional sequence<long> arg = []); + void passOptionalNullableSequence(optional sequence<long>? arg); + void passOptionalNullableSequenceWithDefaultValue(optional sequence<long>? arg = null); + void passOptionalNullableSequenceWithDefaultValue2(optional sequence<long>? arg = []); + void passOptionalObjectSequence(optional sequence<TestJSImplInterface> arg); + void passExternalInterfaceSequence(sequence<TestExternalInterface> arg); + void passNullableExternalInterfaceSequence(sequence<TestExternalInterface?> arg); + + sequence<DOMString> receiveStringSequence(); + sequence<ByteString> receiveByteStringSequence(); + // Callback interface problem. See bug 843261. + //void 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(); + + void passSequenceOfSequences(sequence<sequence<long>> arg); + void passSequenceOfSequencesOfSequences(sequence<sequence<sequence<long>>> arg); + //XXXbz No support for sequence of sequence return values yet. + //sequence<sequence<long>> receiveSequenceOfSequences(); + + // MozMap types + void passMozMap(MozMap<long> arg); + void passNullableMozMap(MozMap<long>? arg); + void passMozMapOfNullableInts(MozMap<long?> arg); + void passOptionalMozMapOfNullableInts(optional MozMap<long?> arg); + void passOptionalNullableMozMapOfNullableInts(optional MozMap<long?>? arg); + void passCastableObjectMozMap(MozMap<TestJSImplInterface> arg); + void passNullableCastableObjectMozMap(MozMap<TestJSImplInterface?> arg); + void passCastableObjectNullableMozMap(MozMap<TestJSImplInterface>? arg); + void passNullableCastableObjectNullableMozMap(MozMap<TestJSImplInterface?>? arg); + void passOptionalMozMap(optional MozMap<long> arg); + void passOptionalNullableMozMap(optional MozMap<long>? arg); + void passOptionalNullableMozMapWithDefaultValue(optional MozMap<long>? arg = null); + void passOptionalObjectMozMap(optional MozMap<TestJSImplInterface> arg); + void passExternalInterfaceMozMap(MozMap<TestExternalInterface> arg); + void passNullableExternalInterfaceMozMap(MozMap<TestExternalInterface?> arg); + void passStringMozMap(MozMap<DOMString> arg); + void passByteStringMozMap(MozMap<ByteString> arg); + void passMozMapOfMozMaps(MozMap<MozMap<long>> arg); + MozMap<long> receiveMozMap(); + MozMap<long>? receiveNullableMozMap(); + MozMap<long?> receiveMozMapOfNullableInts(); + MozMap<long?>? receiveNullableMozMapOfNullableInts(); + //XXXbz No support for MozMap of MozMaps return values yet. + //MozMap<MozMap<long>> receiveMozMapOfMozMaps(); + MozMap<any> receiveAnyMozMap(); + + // Typed array types + void passArrayBuffer(ArrayBuffer arg); + void passNullableArrayBuffer(ArrayBuffer? arg); + void passOptionalArrayBuffer(optional ArrayBuffer arg); + void passOptionalNullableArrayBuffer(optional ArrayBuffer? arg); + void passOptionalNullableArrayBufferWithDefaultValue(optional ArrayBuffer? arg= null); + void passArrayBufferView(ArrayBufferView arg); + void passInt8Array(Int8Array arg); + void passInt16Array(Int16Array arg); + void passInt32Array(Int32Array arg); + void passUint8Array(Uint8Array arg); + void passUint16Array(Uint16Array arg); + void passUint32Array(Uint32Array arg); + void passUint8ClampedArray(Uint8ClampedArray arg); + void passFloat32Array(Float32Array arg); + void passFloat64Array(Float64Array arg); + void passSequenceOfArrayBuffers(sequence<ArrayBuffer> arg); + void passSequenceOfNullableArrayBuffers(sequence<ArrayBuffer?> arg); + void passMozMapOfArrayBuffers(MozMap<ArrayBuffer> arg); + void passMozMapOfNullableArrayBuffers(MozMap<ArrayBuffer?> arg); + void passVariadicTypedArray(Float32Array... arg); + void passVariadicNullableTypedArray(Float32Array?... arg); + Uint8Array receiveUint8Array(); + attribute Uint8Array uint8ArrayAttr; + + // DOMString types + void passString(DOMString arg); + void passNullableString(DOMString? arg); + void passOptionalString(optional DOMString arg); + void passOptionalStringWithDefaultValue(optional DOMString arg = "abc"); + void passOptionalNullableString(optional DOMString? arg); + void passOptionalNullableStringWithDefaultValue(optional DOMString? arg = null); + void passVariadicString(DOMString... arg); + + // ByteString types + void passByteString(ByteString arg); + void passNullableByteString(ByteString? arg); + void passOptionalByteString(optional ByteString arg); + void passOptionalByteStringWithDefaultValue(optional ByteString arg = "abc"); + void passOptionalNullableByteString(optional ByteString? arg); + void passOptionalNullableByteStringWithDefaultValue(optional ByteString? arg = null); + void passVariadicByteString(ByteString... arg); + void passUnionByteString((ByteString or long) arg); + void passOptionalUnionByteString(optional (ByteString or long) arg); + void passOptionalUnionByteStringWithDefaultValue(optional (ByteString or long) arg = "abc"); + + // USVString types + void passSVS(USVString arg); + void passNullableSVS(USVString? arg); + void passOptionalSVS(optional USVString arg); + void passOptionalSVSWithDefaultValue(optional USVString arg = "abc"); + void passOptionalNullableSVS(optional USVString? arg); + void passOptionalNullableSVSWithDefaultValue(optional USVString? arg = null); + void passVariadicSVS(USVString... arg); + USVString receiveSVS(); + + // Enumerated types + void passEnum(MyTestEnum arg); + void passNullableEnum(MyTestEnum? arg); + void passOptionalEnum(optional MyTestEnum arg); + void passEnumWithDefault(optional MyTestEnum arg = "a"); + void passOptionalNullableEnum(optional MyTestEnum? arg); + void passOptionalNullableEnumWithDefaultValue(optional MyTestEnum? arg = null); + void passOptionalNullableEnumWithDefaultValue2(optional MyTestEnum? arg = "a"); + MyTestEnum receiveEnum(); + MyTestEnum? receiveNullableEnum(); + attribute MyTestEnum enumAttribute; + readonly attribute MyTestEnum readonlyEnumAttribute; + + // Callback types + void passCallback(MyTestCallback arg); + void passNullableCallback(MyTestCallback? arg); + void passOptionalCallback(optional MyTestCallback arg); + void passOptionalNullableCallback(optional MyTestCallback? arg); + void 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. + //void passNullableTreatAsNullCallback(TestTreatAsNullCallback? arg); + //void passOptionalNullableTreatAsNullCallback(optional TestTreatAsNullCallback? arg); + void passOptionalNullableTreatAsNullCallbackWithDefaultValue(optional TestTreatAsNullCallback? arg = null); + + // Any types + void passAny(any arg); + void passVariadicAny(any... arg); + void passOptionalAny(optional any arg); + void passAnyDefaultNull(optional any arg = null); + void passSequenceOfAny(sequence<any> arg); + void passNullableSequenceOfAny(sequence<any>? arg); + void passOptionalSequenceOfAny(optional sequence<any> arg); + void passOptionalNullableSequenceOfAny(optional sequence<any>? arg); + void passOptionalSequenceOfAnyWithDefaultValue(optional sequence<any>? arg = null); + void passSequenceOfSequenceOfAny(sequence<sequence<any>> arg); + void passSequenceOfNullableSequenceOfAny(sequence<sequence<any>?> arg); + void passNullableSequenceOfNullableSequenceOfAny(sequence<sequence<any>?>? arg); + void passOptionalNullableSequenceOfNullableSequenceOfAny(optional sequence<sequence<any>?>? arg); + void passMozMapOfAny(MozMap<any> arg); + void passNullableMozMapOfAny(MozMap<any>? arg); + void passOptionalMozMapOfAny(optional MozMap<any> arg); + void passOptionalNullableMozMapOfAny(optional MozMap<any>? arg); + void passOptionalMozMapOfAnyWithDefaultValue(optional MozMap<any>? arg = null); + void passMozMapOfMozMapOfAny(MozMap<MozMap<any>> arg); + void passMozMapOfNullableMozMapOfAny(MozMap<MozMap<any>?> arg); + void passNullableMozMapOfNullableMozMapOfAny(MozMap<MozMap<any>?>? arg); + void passOptionalNullableMozMapOfNullableMozMapOfAny(optional MozMap<MozMap<any>?>? arg); + void passOptionalNullableMozMapOfNullableSequenceOfAny(optional MozMap<sequence<any>?>? arg); + void passOptionalNullableSequenceOfNullableMozMapOfAny(optional sequence<MozMap<any>?>? arg); + any receiveAny(); + + // object types + void passObject(object arg); + void passVariadicObject(object... arg); + void passNullableObject(object? arg); + void passVariadicNullableObject(object... arg); + void passOptionalObject(optional object arg); + void passOptionalNullableObject(optional object? arg); + void passOptionalNullableObjectWithDefaultValue(optional object? arg = null); + void passSequenceOfObject(sequence<object> arg); + void passSequenceOfNullableObject(sequence<object?> arg); + void passNullableSequenceOfObject(sequence<object>? arg); + void passOptionalNullableSequenceOfNullableSequenceOfObject(optional sequence<sequence<object>?>? arg); + void passOptionalNullableSequenceOfNullableSequenceOfNullableObject(optional sequence<sequence<object?>?>? arg); + void passMozMapOfObject(MozMap<object> arg); + object receiveObject(); + object? receiveNullableObject(); + + // Union types + void passUnion((object or long) arg); + // Some union tests are debug-only to avoid creating all those + // unused union types in opt builds. +#ifdef DEBUG + void passUnion2((long or boolean) arg); + void passUnion3((object or long or boolean) arg); + void passUnion4((Node or long or boolean) arg); + void passUnion5((object or boolean) arg); + void passUnion6((object or DOMString) arg); + void passUnion7((object or DOMString or long) arg); + void passUnion8((object or DOMString or boolean) arg); + void passUnion9((object or DOMString or long or boolean) arg); + void passUnion10(optional (EventInit or long) arg); + void passUnion11(optional (CustomEventInit or long) arg); + void passUnion12(optional (EventInit or long) arg = 5); + void passUnion13(optional (object or long?) arg = null); + void passUnion14(optional (object or long?) arg = 5); + void passUnion15((sequence<long> or long) arg); + void passUnion16(optional (sequence<long> or long) arg); + void passUnion17(optional (sequence<long>? or long) arg = 5); + void passUnion18((sequence<object> or long) arg); + void passUnion19(optional (sequence<object> or long) arg); + void passUnion20(optional (sequence<object> or long) arg = []); + void passUnion21((MozMap<long> or long) arg); + void passUnion22((MozMap<object> or long) arg); + void passUnion23((sequence<ImageData> or long) arg); + void passUnion24((sequence<ImageData?> or long) arg); + void passUnion25((sequence<sequence<ImageData>> or long) arg); + void passUnion26((sequence<sequence<ImageData?>> or long) arg); + void passUnion27(optional (sequence<DOMString> or EventInit) arg); + void passUnion28(optional (EventInit or sequence<DOMString>) arg); + void passUnionWithCallback((EventHandler or long) arg); + void passUnionWithByteString((ByteString or long) arg); + void passUnionWithMozMap((MozMap<DOMString> or DOMString) arg); + void passUnionWithMozMapAndSequence((MozMap<DOMString> or sequence<DOMString>) arg); + void passUnionWithSequenceAndMozMap((sequence<DOMString> or MozMap<DOMString>) arg); + void passUnionWithSVS((USVString or long) arg); +#endif + void passUnionWithNullable((object? or long) arg); + void passNullableUnion((object or long)? arg); + void passOptionalUnion(optional (object or long) arg); + void passOptionalNullableUnion(optional (object or long)? arg); + void passOptionalNullableUnionWithDefaultValue(optional (object or long)? arg = null); + //void passUnionWithInterfaces((TestJSImplInterface or TestExternalInterface) arg); + //void passUnionWithInterfacesAndNullable((TestJSImplInterface? or TestExternalInterface) arg); + //void passUnionWithSequence((sequence<object> or long) arg); + void passUnionWithArrayBuffer((ArrayBuffer or long) arg); + void 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. + void passUnionWithEnum((SupportedType or object) arg); + + // Trying to use a callback in a union won't include the test + // headers, unfortunately, so won't compile. + // void passUnionWithCallback((MyTestCallback or long) arg); + void passUnionWithObject((object or long) arg); + //void passUnionWithDict((Dict or long) arg); + + void passUnionWithDefaultValue1(optional (double or DOMString) arg = ""); + void passUnionWithDefaultValue2(optional (double or DOMString) arg = 1); + void passUnionWithDefaultValue3(optional (double or DOMString) arg = 1.5); + void passUnionWithDefaultValue4(optional (float or DOMString) arg = ""); + void passUnionWithDefaultValue5(optional (float or DOMString) arg = 1); + void passUnionWithDefaultValue6(optional (float or DOMString) arg = 1.5); + void passUnionWithDefaultValue7(optional (unrestricted double or DOMString) arg = ""); + void passUnionWithDefaultValue8(optional (unrestricted double or DOMString) arg = 1); + void passUnionWithDefaultValue9(optional (unrestricted double or DOMString) arg = 1.5); + void passUnionWithDefaultValue10(optional (unrestricted double or DOMString) arg = Infinity); + void passUnionWithDefaultValue11(optional (unrestricted float or DOMString) arg = ""); + void passUnionWithDefaultValue12(optional (unrestricted float or DOMString) arg = 1); + void passUnionWithDefaultValue13(optional (unrestricted float or DOMString) arg = Infinity); + void passUnionWithDefaultValue14(optional (double or ByteString) arg = ""); + void passUnionWithDefaultValue15(optional (double or ByteString) arg = 1); + void passUnionWithDefaultValue16(optional (double or ByteString) arg = 1.5); + void passUnionWithDefaultValue17(optional (double or SupportedType) arg = "text/html"); + void passUnionWithDefaultValue18(optional (double or SupportedType) arg = 1); + void passUnionWithDefaultValue19(optional (double or SupportedType) arg = 1.5); + + void passNullableUnionWithDefaultValue1(optional (double or DOMString)? arg = ""); + void passNullableUnionWithDefaultValue2(optional (double or DOMString)? arg = 1); + void passNullableUnionWithDefaultValue3(optional (double or DOMString)? arg = null); + void passNullableUnionWithDefaultValue4(optional (float or DOMString)? arg = ""); + void passNullableUnionWithDefaultValue5(optional (float or DOMString)? arg = 1); + void passNullableUnionWithDefaultValue6(optional (float or DOMString)? arg = null); + void passNullableUnionWithDefaultValue7(optional (unrestricted double or DOMString)? arg = ""); + void passNullableUnionWithDefaultValue8(optional (unrestricted double or DOMString)? arg = 1); + void passNullableUnionWithDefaultValue9(optional (unrestricted double or DOMString)? arg = null); + void passNullableUnionWithDefaultValue10(optional (unrestricted float or DOMString)? arg = ""); + void passNullableUnionWithDefaultValue11(optional (unrestricted float or DOMString)? arg = 1); + void passNullableUnionWithDefaultValue12(optional (unrestricted float or DOMString)? arg = null); + void passNullableUnionWithDefaultValue13(optional (double or ByteString)? arg = ""); + void passNullableUnionWithDefaultValue14(optional (double or ByteString)? arg = 1); + void passNullableUnionWithDefaultValue15(optional (double or ByteString)? arg = 1.5); + void passNullableUnionWithDefaultValue16(optional (double or ByteString)? arg = null); + void passNullableUnionWithDefaultValue17(optional (double or SupportedType)? arg = "text/html"); + void passNullableUnionWithDefaultValue18(optional (double or SupportedType)? arg = 1); + void passNullableUnionWithDefaultValue19(optional (double or SupportedType)? arg = 1.5); + void passNullableUnionWithDefaultValue20(optional (double or SupportedType)? arg = null); + + void passSequenceOfUnions(sequence<(CanvasPattern or CanvasGradient)> arg); + void passSequenceOfUnions2(sequence<(object or long)> arg); + void passVariadicUnion((CanvasPattern or CanvasGradient)... arg); + + void passSequenceOfNullableUnions(sequence<(CanvasPattern or CanvasGradient)?> arg); + void passVariadicNullableUnion((CanvasPattern or CanvasGradient)?... arg); + void passMozMapOfUnions(MozMap<(CanvasPattern or CanvasGradient)> arg); + // XXXbz no move constructor on some unions + // void passMozMapOfUnions2(MozMap<(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; + + // Date types + void passDate(Date arg); + void passNullableDate(Date? arg); + void passOptionalDate(optional Date arg); + void passOptionalNullableDate(optional Date? arg); + void passOptionalNullableDateWithDefaultValue(optional Date? arg = null); + void passDateSequence(sequence<Date> arg); + void passNullableDateSequence(sequence<Date?> arg); + void passDateMozMap(MozMap<Date> arg); + Date receiveDate(); + Date? receiveNullableDate(); + + // Promise types + void passPromise(Promise<any> arg); + void passNullablePromise(Promise<any>? arg); + void passOptionalPromise(optional Promise<any> arg); + void passOptionalNullablePromise(optional Promise<any>? arg); + void passOptionalNullablePromiseWithDefaultValue(optional Promise<any>? arg = null); + void passPromiseSequence(sequence<Promise<any>> arg); + void passNullablePromiseSequence(sequence<Promise<any>?> arg); + Promise<any> receivePromise(); + Promise<any> receiveAddrefedPromise(); + + // binaryNames tests + void methodRenamedFrom(); + [BinaryName="otherMethodRenamedTo"] + void otherMethodRenamedFrom(); + void methodRenamedFrom(byte argument); + readonly attribute byte attributeGetterRenamedFrom; + attribute byte attributeRenamedFrom; + [BinaryName="otherAttributeRenamedTo"] + attribute byte otherAttributeRenamedFrom; + + void passDictionary(optional Dict x); + void 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(); + void passOtherDictionary(optional GrandparentDict x); + void passSequenceOfDictionaries(sequence<Dict> x); + void passMozMapOfDictionaries(MozMap<GrandparentDict> x); + // No support for nullable dictionaries inside a sequence (nor should there be) + // void passSequenceOfNullableDictionaries(sequence<Dict?> x); + void passDictionaryOrLong(optional Dict x); + void passDictionaryOrLong(long x); + + void passDictContainingDict(optional DictContainingDict arg); + void passDictContainingSequence(optional DictContainingSequence arg); + DictContainingSequence receiveDictContainingSequence(); + void passVariadicDictionary(Dict... arg); + + // EnforceRange/Clamp tests + void dontEnforceRangeOrClamp(byte arg); + void doEnforceRange([EnforceRange] byte arg); + void doClamp([Clamp] byte arg); + [EnforceRange] attribute byte enforcedByte; + [Clamp] attribute byte clampedByte; + + // Typedefs + const myLong myLongConstant = 5; + void exerciseTypedefInterfaces1(AnotherNameForTestJSImplInterface arg); + AnotherNameForTestJSImplInterface exerciseTypedefInterfaces2(NullableTestJSImplInterface arg); + void exerciseTypedefInterfaces3(YetAnotherNameForTestJSImplInterface arg); + + // Deprecated methods and attributes + [Deprecated="GetAttributeNode"] + attribute byte deprecatedAttribute; + [Deprecated="GetAttributeNode"] + byte deprecatedMethod(); + [Deprecated="GetAttributeNode"] + void deprecatedMethodWithContext(any arg); + + // Static methods and attributes + // FIXME: Bug 863952 Static things are not supported yet + /* + static attribute boolean staticAttribute; + static void staticMethod(boolean arg); + static void staticMethodWithContext(any arg); + + // Deprecated static methods and attributes + [Deprecated="GetAttributeNode"] + static attribute byte staticDeprecatedAttribute; + [Deprecated="GetAttributeNode"] + static byte staticDeprecatedMethod(); + [Deprecated="GetAttributeNode"] + static byte staticDeprecatedMethodWithContext(); + */ + + // Overload resolution tests + //void overload1(DOMString... strs); + boolean overload1(TestJSImplInterface arg); + TestJSImplInterface overload1(DOMString strs, TestJSImplInterface arg); + void overload2(TestJSImplInterface arg); + void overload2(optional Dict arg); + void overload2(boolean arg); + void overload2(DOMString arg); + void overload2(Date arg); + void overload3(TestJSImplInterface arg); + void overload3(MyTestCallback arg); + void overload3(boolean arg); + void overload4(TestJSImplInterface arg); + void overload4(TestCallbackInterface arg); + void overload4(DOMString arg); + void overload5(long arg); + void overload5(MyTestEnum arg); + void overload6(long arg); + void overload6(boolean arg); + void overload7(long arg); + void overload7(boolean arg); + void overload7(ByteString arg); + void overload8(long arg); + void overload8(TestJSImplInterface arg); + void overload9(long? arg); + void overload9(DOMString arg); + void overload10(long? arg); + void overload10(object arg); + void overload11(long arg); + void overload11(DOMString? arg); + void overload12(long arg); + void overload12(boolean? arg); + void overload13(long? arg); + void overload13(boolean arg); + void overload14(optional long arg); + void overload14(TestInterface arg); + void overload15(long arg); + void overload15(optional TestInterface arg); + void overload16(long arg); + void overload16(optional TestInterface? arg); + void overload17(sequence<long> arg); + void overload17(MozMap<long> arg); + void overload18(MozMap<DOMString> arg); + void overload18(sequence<DOMString> arg); + void overload19(sequence<long> arg); + void overload19(optional Dict arg); + void overload20(optional Dict arg); + void overload20(sequence<long> arg); + + // Variadic handling + void passVariadicThirdArg(DOMString arg1, long arg2, TestJSImplInterface... arg3); + + // Conditionally exposed methods/attributes + [Pref="abc.def"] + readonly attribute boolean prefable1; + [Pref="abc.def"] + readonly attribute boolean prefable2; + [Pref="ghi.jkl"] + readonly attribute boolean prefable3; + [Pref="ghi.jkl"] + readonly attribute boolean prefable4; + [Pref="abc.def"] + readonly attribute boolean prefable5; + [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + readonly attribute boolean prefable6; + [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + readonly attribute boolean prefable7; + [Pref="ghi.jkl", Func="nsGenericHTMLElement::TouchEventsEnabled"] + readonly attribute boolean prefable8; + [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + readonly attribute boolean prefable9; + [Pref="abc.def"] + void prefable10(); + [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + void prefable11(); + [Pref="abc.def", Func="TestFuncControlledMember"] + readonly attribute boolean prefable12; + [Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + void prefable13(); + [Pref="abc.def", Func="TestFuncControlledMember"] + readonly attribute boolean prefable14; + [Func="TestFuncControlledMember"] + readonly attribute boolean prefable15; + [Func="TestFuncControlledMember"] + readonly attribute boolean prefable16; + [Pref="abc.def", Func="TestFuncControlledMember"] + void prefable17(); + [Func="TestFuncControlledMember"] + void prefable18(); + [Func="TestFuncControlledMember"] + void prefable19(); + [Pref="abc.def", Func="TestFuncControlledMember", ChromeOnly] + void prefable20(); + + // Conditionally exposed methods/attributes involving [SecureContext] + [SecureContext] + readonly attribute boolean conditionalOnSecureContext1; + [SecureContext, Pref="abc.def"] + readonly attribute boolean conditionalOnSecureContext2; + [SecureContext, Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + readonly attribute boolean conditionalOnSecureContext3; + [SecureContext, Pref="abc.def", Func="TestFuncControlledMember"] + readonly attribute boolean conditionalOnSecureContext4; + [SecureContext] + void conditionalOnSecureContext5(); + [SecureContext, Pref="abc.def"] + void conditionalOnSecureContext6(); + [SecureContext, Pref="abc.def", Func="nsGenericHTMLElement::TouchEventsEnabled"] + void conditionalOnSecureContext7(); + [SecureContext, Pref="abc.def", Func="TestFuncControlledMember"] + void conditionalOnSecureContext8(); + + // Miscellania + [LenientThis] 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; + void passRenamedInterface(TestRenamedInterface arg); + [PutForwards=writableByte] readonly attribute TestJSImplInterface putForwardsAttr; + [PutForwards=writableByte, LenientThis] readonly attribute TestJSImplInterface putForwardsAttr2; + [PutForwards=writableByte, ChromeOnly] readonly attribute TestJSImplInterface putForwardsAttr3; + [Throws] void throwingMethod(); + [Throws] attribute boolean throwingAttr; + [GetterThrows] attribute boolean throwingGetterAttr; + [SetterThrows] attribute boolean throwingSetterAttr; + // NeedsSubjectPrincipal not supported on JS-implemented things for + // now, because we always pass in the caller principal anyway. + // [NeedsSubjectPrincipal] void needsSubjectPrincipalMethod(); + // [NeedsSubjectPrincipal] attribute boolean needsSubjectPrincipalAttr; + // legacycaller short(unsigned long arg1, TestInterface arg2); + void passArgsWithDefaults(optional long arg1, + optional TestInterface? arg2 = null, + optional Dict arg3, optional double arg4 = 5.0, + optional float arg5); + attribute any jsonifierShouldSkipThis; + attribute TestParentInterface jsonifierShouldSkipThis2; + attribute TestCallbackInterface jsonifierShouldSkipThis3; + jsonifier; + + attribute byte dashed-attribute; + void dashed-method(); + + // If you add things here, add them to TestCodeGen as well +}; + +[NavigatorProperty="TestNavigator", JSImplementation="@mozilla.org/test;1"] +interface TestNavigator { +}; + +[Constructor, NavigatorProperty="TestNavigatorWithConstructor", JSImplementation="@mozilla.org/test;1"] +interface TestNavigatorWithConstructor { +}; + +interface TestCImplementedInterface : TestJSImplInterface { +}; + +interface TestCImplementedInterface2 { +}; + +[NoInterfaceObject, + JSImplementation="@mozilla.org/test-js-impl-interface;2"] +interface TestJSImplNoInterfaceObject { + [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 000000000..e62dbd10b --- /dev/null +++ b/dom/bindings/test/TestJSImplInheritanceGen.webidl @@ -0,0 +1,29 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +[Constructor, JSImplementation="@mozilla.org/test-js-impl-interface2;1"] +interface TestJSImplInterface2 : TestCImplementedInterface { +}; + +[Constructor, JSImplementation="@mozilla.org/test-js-impl-interface3;1"] +interface TestJSImplInterface3 : TestCImplementedInterface2 { +}; + +// Important: TestJSImplInterface5 needs to come before TestJSImplInterface6 in +// this file to test what it's trying to test. +[Constructor, JSImplementation="@mozilla.org/test-js-impl-interface5;1"] +interface TestJSImplInterface5 : TestJSImplInterface6 { +}; + +// Important: TestJSImplInterface6 needs to come after TestJSImplInterface3 in +// this file to test what it's trying to test. +[Constructor, JSImplementation="@mozilla.org/test-js-impl-interface6;1"] +interface TestJSImplInterface6 : TestJSImplInterface3 { +}; + +[Constructor, JSImplementation="@mozilla.org/test-js-impl-interface4;1"] +interface TestJSImplInterface4 : EventTarget { +}; diff --git a/dom/bindings/test/TestTypedef.webidl b/dom/bindings/test/TestTypedef.webidl new file mode 100644 index 000000000..7f758c79e --- /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/chrome.ini b/dom/bindings/test/chrome.ini new file mode 100644 index 000000000..9fdbd7fd3 --- /dev/null +++ b/dom/bindings/test/chrome.ini @@ -0,0 +1,22 @@ +[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] +[test_proxies_via_xray.html] +[test_document_location_via_xray_cached.html] +[test_blacklisted_prerendering_function.xul] +support-files = + file_focuser.html + file_fullScreenPropertyAccessor.html +skip-if = e10s # prerendering doesn't work in e10s yet +[test_kill_longrunning_prerendered_content.xul] +skip-if = e10s # prerendering doesn't work in e10s yet +[test_bug1123516_maplikesetlikechrome.xul] +skip-if = debug == false +[test_bug1287912.html] diff --git a/dom/bindings/test/file_InstanceOf.html b/dom/bindings/test/file_InstanceOf.html new file mode 100644 index 000000000..487010fa4 --- /dev/null +++ b/dom/bindings/test/file_InstanceOf.html @@ -0,0 +1,12 @@ +<!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_bug775543.html b/dom/bindings/test/file_bug775543.html new file mode 100644 index 000000000..ee8c14c4d --- /dev/null +++ b/dom/bindings/test/file_bug775543.html @@ -0,0 +1,5 @@ +<body> +<script> +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 000000000..323acba66 --- /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 000000000..36b3f8a30 --- /dev/null +++ b/dom/bindings/test/file_dom_xrays.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html> + <script> + window.expando = 42; + window.shadowedIframe = 42; + Object.setPrototypeOf(window, Object.create(Window.prototype, + { + shadowedIframe: { value: 42 }, + iframe: { value: 42 }, + document: { value: 42 }, + addEventListener: { value: 42 }, + toString: { value: 42 } + })); + window.documentElement.expando = 42; + Object.defineProperty(window.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_focuser.html b/dom/bindings/test/file_focuser.html new file mode 100644 index 000000000..0d5240f95 --- /dev/null +++ b/dom/bindings/test/file_focuser.html @@ -0,0 +1,24 @@ +<!DOCTYPE HTML> +<div id="stage"></div> +<script> + function stage(str) { + var s = document.getElementById("stage"); + s.textContent = str; + } + stage("before"); + setTimeout(function() { + stage("in timeout"); + }); + setInterval(function() { + stage("in interval"); + }); + addEventListener("keydown", function() { + stage("keydown"); + }, false); + try { + focus(); + stage("after"); + } catch(e) { + stage("exception raised"); + } +</script> diff --git a/dom/bindings/test/file_fullScreenPropertyAccessor.html b/dom/bindings/test/file_fullScreenPropertyAccessor.html new file mode 100644 index 000000000..92a37e0ba --- /dev/null +++ b/dom/bindings/test/file_fullScreenPropertyAccessor.html @@ -0,0 +1,24 @@ +<!DOCTYPE HTML> +<div id="stage"></div> +<script> + function stage(str) { + var s = document.getElementById("stage"); + s.textContent = str; + } + stage("before"); + setTimeout(function() { + stage("in timeout"); + }); + setInterval(function() { + stage("in interval"); + }); + addEventListener("keydown", function() { + stage("keydown"); + }, false); + try { + window.fullScreen; + stage("after"); + } catch(e) { + stage("exception raised"); + } +</script> 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 000000000..2e9a31830 --- /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 000000000..91417aba0 --- /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 000000000..2cd322e74 --- /dev/null +++ b/dom/bindings/test/mochitest.ini @@ -0,0 +1,79 @@ +[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 + +[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 == false +[test_bug963382.html] +skip-if = debug == false +[test_bug1041646.html] +[test_bug1123875.html] +[test_barewordGetsWindow.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_interfaceName.html] +[test_interfaceToString.html] +[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_queryInterface.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] +[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_iterable.html] +skip-if = debug == false +[test_oom_reporting.html] +[test_domProxyArrayLengthGetter.html] +[test_exceptionSanitization.html] +skip-if = os == "android" +[test_stringBindings.html] +skip-if = debug == false diff --git a/dom/bindings/test/moz.build b/dom/bindings/test/moz.build new file mode 100644 index 000000000..7d0cb6c21 --- /dev/null +++ b/dom/bindings/test/moz.build @@ -0,0 +1,58 @@ +# -*- 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.update({ + 'IMPL_LIBXUL': True, + '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') + +EXTRA_COMPONENTS += [ + 'TestInterfaceJS.js', + 'TestInterfaceJS.manifest', + 'TestInterfaceJSMaplike.js' +] + +MOCHITEST_MANIFESTS += ['mochitest.ini'] + +MOCHITEST_CHROME_MANIFESTS += ['chrome.ini'] + +TEST_WEBIDL_FILES += [ + 'TestDictionary.webidl', + 'TestJSImplInheritanceGen.webidl', + 'TestTypedef.webidl', +] + +PREPROCESSED_TEST_WEBIDL_FILES += [ + 'TestCodeGen.webidl', + 'TestExampleGen.webidl', + 'TestJSImplGen.webidl', +] + +WEBIDL_EXAMPLE_INTERFACES += [ + 'TestExampleInterface', + 'TestExampleProxyInterface', + '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', +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/dom/bindings/test/test_ByteString.html b/dom/bindings/test/test_ByteString.html new file mode 100644 index 000000000..c7e632117 --- /dev/null +++ b/dom/bindings/test/test_ByteString.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=796850 +--> +<head> + <meta charset="utf-8"> + <title>Test for ByteString support</title> + <script type="application/javascript" 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(); + 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 000000000..514ec1b2a --- /dev/null +++ b/dom/bindings/test/test_InstanceOf.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=748983 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 748983</title> + <script type="application/javascript" 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 000000000..03147eb03 --- /dev/null +++ b/dom/bindings/test/test_Object.prototype_props.html @@ -0,0 +1,20 @@ +<!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> +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", "toSource", "toString", "toLocaleString", "valueOf", + "watch", "unwatch", "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_stacks.html b/dom/bindings/test/test_async_stacks.html new file mode 100644 index 000000000..8b655a14d --- /dev/null +++ b/dom/bindings/test/test_async_stacks.html @@ -0,0 +1,108 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1148593 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1148593</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** 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.indexOf(functionName) >= 0, "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', true]]}, + 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_barewordGetsWindow.html b/dom/bindings/test/test_barewordGetsWindow.html new file mode 100644 index 000000000..e098eea53 --- /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 type="application/javascript" 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; + } + get = desc.get; + ok(get, "Couldn't find document getter"); + Object.defineProperty(frames[0], "foo", { get: 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).textContent, "OLD", "Bareword should see own inner 1"); + is(qualifiedFunc(1).textContent, "NEW", + "Qualified should see current inner 1"); + // baseline + is(barewordFunc(100).textContent, "OLD", "Bareword should see own inner 2"); + is(qualifiedFunc(100).textContent, "NEW", + "Qualified should see current inner 2"); + // ion + is(barewordFunc(10000).textContent, "OLD", "Bareword should see own inner 3"); + is(qualifiedFunc(10000).textContent, "NEW", + "Qualified should see current inner 2"); + SimpleTest.finish(); + } + frames[0].location = "data:text/plain,NEW"; + } + + + + + </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="data:text/plain,OLD"></iframe> +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/bindings/test/test_blacklisted_prerendering_function.xul b/dom/bindings/test/test_blacklisted_prerendering_function.xul new file mode 100644 index 000000000..02a76d88d --- /dev/null +++ b/dom/bindings/test/test_blacklisted_prerendering_function.xul @@ -0,0 +1,124 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + onload="runTest();"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + + SimpleTest.waitForExplicitFinish(); + + function Listener(aBrowser, aPrerendered, aCallback) { + this.init(aBrowser, aPrerendered, aCallback); + } + + Listener.prototype = { + init: function(aBrowser, aPrerendered, aCallback) { + this.mBrowser = aBrowser; + this.mPrerendered = aPrerendered; + this.mCallback = aCallback; + }, + QueryInterface: function(aIID) { + if (aIID.equals(Components.interfaces.nsIWebProgressListener) || + aIID.equals(Components.interfaces.nsISupportsWeakReference) || + aIID.equals(Components.interfaces.nsISupports)) + return this; + throw Components.results.NS_NOINTERFACE; + }, + onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus) { + if ((aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP) && + (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_IS_DOCUMENT)) { + var doc = this.mBrowser.contentDocument; + var stage = doc.getElementById("stage"); + if (this.mPrerendered) { + is(stage.textContent, "before", "The blacklisted call should properly be intercepted in prerendering mode"); + } else { + // In normal mode, we may or may not have run the timeout and/or the interval. + switch (stage.textContent) { + case "after": + case "in timeout": + case "in interval": + ok(true, "The blacklisted call should work fine in normal mode"); + break; + default: + ok(false, "The blacklisted call should work fine in normal mode"); + break; + } + } + progress.removeProgressListener(progressListener); + + // Set three timeouts to see if the interval triggered + var self = this; + function checkInterval() { + var expected = self.mPrerendered ? "before" : "in interval"; + var desc = self.mPrerendered ? "No timer should be running" : "Timers should run as normal"; + is(stage.textContent, expected, desc); + // Now, dispatch a key event to the window and see if the keydown handler runs + synthesizeKey("a", {}, self.mBrowser.contentWindow); + expected = self.mPrerendered ? "before" : "keydown"; + desc = self.mPrerendered ? "No event handler should be running" : "Event handlers should run as normal"; + is(stage.textContent, expected, desc); + self.mCallback(); + } + setTimeout(function() { + setTimeout(function() { + setTimeout(function() { + checkInterval(); + }, 0); + }, 0); + }, 0); + } + }, + onProgressChange : function(aWebProgress, aRequest, + aCurSelfProgress, aMaxSelfProgress, + aCurTotalProgress, aMaxTotalProgress) {}, + onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags) {}, + onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage) {}, + onSecurityChange : function(aWebProgress, aRequest, aState) {}, + mBrowser: null, + mPrerendered: false, + mCallback: null + }; + + var progress, progressListener; + + function runTest() { + testStep(false, "file_focuser.html", function() { + testStep(true, "file_focuser.html", function() { + testStep(false, "file_fullScreenPropertyAccessor.html", function() { + testStep(true, "file_fullScreenPropertyAccessor.html", function() { + SimpleTest.finish(); + }); + }); + }); + }); + } + + function testStep(aPrerendered, aFileName, aCallback) { + var browser = document.getElementById(aPrerendered ? "prerendered" : "normal");; + progressListener = new Listener(browser, aPrerendered, aCallback); + var docShell = browser.docShell; + progress = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIWebProgress); + progress.addProgressListener(progressListener, + Components.interfaces.nsIWebProgress.NOTIFY_ALL); + browser.loadURI("chrome://mochitests/content/chrome/dom/bindings/test/" + aFileName); + } + +]]> +</script> + +<body id="html_body" xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1069719">Mozilla Bug 1069719</a> +<p id="display"></p> + +<pre id="test"> +</pre> +</body> +<browser prerendered="true" id="prerendered"/> +<browser id="normal"/> +</window> diff --git a/dom/bindings/test/test_bug1036214.html b/dom/bindings/test/test_bug1036214.html new file mode 100644 index 000000000..dd98eb482 --- /dev/null +++ b/dom/bindings/test/test_bug1036214.html @@ -0,0 +1,123 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1036214 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1036214</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for subsumes-checking |any| and |object| for js-implemented WebIDL. **/ + SimpleTest.waitForExplicitFinish(); + var xoObjects = []; + function setup() { + xoObjects.push(window[0]); + xoObjects.push(window[0].location); + xoObjects.push(SpecialPowers.unwrap(SpecialPowers.wrap(window[0]).document)); + xoObjects.push(SpecialPowers); + xoObjects.push(SpecialPowers.wrap); + SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]}, go); + } + + 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"], + 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.dictionaryArg.anyMember, 42, "dictionaryArg looks correct"); + is(t.dictionaryArg.objectMember.answer, 42, "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.dictionaryAttr = myDict; + is(t.dictionaryAttr.anyMember, 42, "ping-pong dictionary attribute works"); + is(t.dictionaryAttr.objectMember.answer, 42, "ping-pong dictionary attribute 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).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.pingPongMap({ someVal: 42, someOtherVal: "canary" })), "ping pong works with mozmap"); + 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) { + var blank = 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.dictionaryAttr = { anyMember: xoObj }, "dictionaryAttr any"); + checkThrows(() => t.dictionaryAttr = { 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.pingPongDictionaryOrLong({ objectMember: xoObj }), "unionable dictionary"); + checkThrows(() => t.pingPongDictionaryOrLong({ anyMember: xoObj }), "unionable dictionary"); + checkThrows(() => t.pingPongMap({ someMember: 42, someOtherMember: {}, crossOriginMember: xoObj }), "mozmap"); + 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://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 000000000..22baed454 --- /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 type="application/javascript" 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 000000000..18ede38ac --- /dev/null +++ b/dom/bindings/test/test_bug1123516_maplikesetlike.html @@ -0,0 +1,271 @@ +<!-- Any copyright is dedicated to the Public Domain. +- http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> + <head> + <title>Test Maplike Interface</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + <body> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]}, function() { + + base_properties = [["has", "function", 1], + ["entries", "function", 0], + ["keys", "function", 0], + ["values", "function", 0], + ["forEach", "function", 1], + ["size", "number"]]; + maplike_properties = base_properties.concat([["set", "function", 2]]); + setlike_properties = base_properties; + rw_properties = [["clear", "function", 0], + ["delete", "function", 1]]; + setlike_rw_properties = base_properties.concat(rw_properties).concat([["add", "function", 1]]); + maplike_rw_properties = maplike_properties.concat(rw_properties).concat([["get", "function",1]]); + var testExistence = function testExistence(prefix, obj, properties) { + for (var [name, type, args] of properties) { + // Properties are somewhere up the proto chain, hasOwnProperty won't work + isnot(obj[name], undefined, + `${prefix} object has property ${name}`); + + is(typeof obj[name], type, + `${prefix} object property ${name} is a ${type}`); + // Check function length + if (type == "function") { + is(obj[name].length, args, + `${prefix} object property ${name} is length ${args}`); + is(obj[name].name, name, + `${prefix} object method name is ${name}`); + } + + // Find where property is on proto chain, check for enumerablility there. + var owner = obj; + while (owner) { + var propDesc = Object.getOwnPropertyDescriptor(owner, name); + if (propDesc) { + ok(!propDesc.enumerable, + `${prefix} object property ${name} is not enumerable`); + break; + } + owner = Object.getPrototypeOf(owner); + } + } + } + + var m; + var testSet; + var testIndex; + // Simple map creation and functionality test + info("SimpleMap: Testing simple map creation and functionality"); + m = new TestInterfaceMaplike(); + ok(m, "SimpleMap: got a TestInterfaceMaplike object"); + testExistence("SimpleMap: ", m, maplike_rw_properties); + is(m.size, 0, "SimpleMap: size should be zero"); + ok(!m.has("test"), "SimpleMap: maplike has should return false"); + is(m.get("test"), undefined, "SimpleMap: maplike get should return undefined on bogus lookup"); + m1 = m.set("test", 1); + is(m, m1, "SimpleMap: return from set should be map object"); + is(m.size, 1, "SimpleMap: size should be 1"); + ok(m.has("test"), "SimpleMap: maplike has should return true"); + is(m.get("test"), 1, "SimpleMap: maplike get should return value entered"); + m2 = m.set("test2", 2); + is(m.size, 2, "SimpleMap: size should be 2"); + testSet = [["test", 1], ["test2", 2]]; + testIndex = 0; + m.forEach(function(v, k, o) { + "use strict"; + is(o, m, "SimpleMap: foreach obj is correct"); + is(k, testSet[testIndex][0], "SimpleMap: foreach map key: " + k + " = " + testSet[testIndex][0]); + is(v, testSet[testIndex][1], "SimpleMap: foreach map value: " + v + " = " + testSet[testIndex][1]); + testIndex += 1; + }); + is(testIndex, 2, "SimpleMap: foreach ran correct number of times"); + ok(m.has("test2"), "SimpleMap: maplike has should return true"); + is(m.get("test2"), 2, "SimpleMap: maplike get should return value entered"); + is(m.delete("test2"), true, "SimpleMap: maplike deletion should return boolean"); + is(m.size, 1, "SimpleMap: size should be 1"); + iterable = false; + for (var e of m) { + iterable = true; + is(e[0], "test", "SimpleMap: iterable first array element should be key"); + is(e[1], 1, "SimpleMap: iterable second array element should be value"); + } + is(m[Symbol.iterator].length, 0, "SimpleMap: @@iterator symbol is correct length"); + is(m[Symbol.iterator].name, "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 (var k of m.keys()) { + is(k, "test", "SimpleMap: first keys element should be 'test'"); + } + for (var v of m.values()) { + is(v, 1, "SimpleMap: first values elements should be 1"); + } + for (var e of m.entries()) { + is(e[0], "test", "SimpleMap: entries first array element should be 'test'"); + is(e[1], 1, "SimpleMap: entries second array element should be 1"); + } + m.clear(); + is(m.size, 0, "SimpleMap: size should be 0 after clear"); + + // Simple set creation and functionality test + info("SimpleSet: Testing simple set creation and functionality"); + m = new TestInterfaceSetlike(); + ok(m, "SimpleSet: got a TestInterfaceSetlike object"); + testExistence("SimpleSet: ", m, setlike_rw_properties); + is(m.size, 0, "SimpleSet: size should be zero"); + ok(!m.has("test"), "SimpleSet: maplike has should return false"); + m1 = m.add("test"); + is(m, m1, "SimpleSet: return from set should be map object"); + is(m.size, 1, "SimpleSet: size should be 1"); + ok(m.has("test"), "SimpleSet: maplike has should return true"); + m2 = m.add("test2"); + is(m.size, 2, "SimpleSet: size should be 2"); + testSet = ["test", "test2"]; + testIndex = 0; + m.forEach(function(v, k, o) { + "use strict"; + is(o, m, "SimpleSet: foreach obj is correct"); + is(k, testSet[testIndex], "SimpleSet: foreach set key: " + k + " = " + testSet[testIndex]); + testIndex += 1; + }); + is(testIndex, 2, "SimpleSet: foreach ran correct number of times"); + ok(m.has("test2"), "SimpleSet: maplike has should return true"); + is(m.delete("test2"), true, "SimpleSet: maplike deletion should return true"); + is(m.size, 1, "SimpleSet: size should be 1"); + iterable = false; + for (var e of m) { + iterable = true; + is(e, "test", "SimpleSet: iterable first array element should be key"); + } + is(m[Symbol.iterator].length, 0, "SimpleSet: @@iterator symbol is correct length"); + is(m[Symbol.iterator].name, "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 (var k of m.keys()) { + is(k, "test", "SimpleSet: first keys element should be 'test'"); + } + for (var v of m.values()) { + is(v, "test", "SimpleSet: first values elements should be 'test'"); + } + for (var e of m.entries()) { + is(e[0], "test", "SimpleSet: Entries first array element should be 'test'"); + is(e[1], "test", "SimpleSet: Entries second array element should be 'test'"); + } + m.clear(); + is(m.size, 0, "SimpleSet: size should be 0 after clear"); + + // Map convenience function test + info("Testing map convenience functions"); + m = new TestInterfaceMaplike(); + ok(m, "MapConvenience: got a TestInterfaceMaplike object"); + is(m.size, 0, "MapConvenience: size should be zero"); + ok(!m.hasInternal("test"), "MapConvenience: maplike hasInternal should return false"); + m.setInternal("test", 1); + is(m.size, 1, "MapConvenience: size should be 1"); + ok(m.hasInternal("test"), "MapConvenience: maplike hasInternal should return true"); + is(m.get("test"), 1, "MapConvenience: maplike get should return value entered"); + m2 = m.setInternal("test2", 2); + is(m.size, 2, "size should be 2"); + ok(m.hasInternal("test2"), "MapConvenience: maplike hasInternal should return true"); + is(m.get("test2"), 2, "MapConvenience: maplike get should return value entered"); + is(m.deleteInternal("test2"), true, "MapConvenience: maplike deleteInternal should return true"); + is(m.size, 1, "MapConvenience: size should be 1"); + m.clearInternal(); + is(m.size, 0, "MapConvenience: size should be 0 after clearInternal"); + + // Map convenience function test using objects and readonly + + info("Testing Map convenience function test using objects and readonly"); + m = new TestInterfaceMaplikeObject(); + ok(m, "ReadOnlyMapConvenience: got a TestInterfaceMaplikeObject object"); + is(m.size, 0, "ReadOnlyMapConvenience: size should be zero"); + is(m["set"], undefined, "ReadOnlyMapConvenience: readonly map, should be no set function"); + is(m["clear"], undefined, "ReadOnlyMapConvenience: readonly map, should be no clear function"); + is(m["delete"], undefined, "ReadOnlyMapConvenience: readonly map, should be no delete function"); + ok(!m.hasInternal("test"), "ReadOnlyMapConvenience: maplike hasInternal should return false"); + m.setInternal("test"); + is(m.size, 1, "size should be 1"); + ok(m.hasInternal("test"), "ReadOnlyMapConvenience: maplike hasInternal should return true"); + m2 = m.setInternal("test2"); + is(m.size, 2, "size should be 2"); + ok(m.hasInternal("test2"), "ReadOnlyMapConvenience: maplike hasInternal should return true"); + is(m.deleteInternal("test2"), true, "ReadOnlyMapConvenience: maplike deleteInternal should return true"); + is(m.size, 1, "ReadOnlyMapConvenience: size should be 1"); + m.clearInternal(); + is(m.size, 0, "ReadOnlyMapConvenience: size should be 0 after clearInternal"); + + // JS implemented map creation convenience function test + + info("JSMapConvenience: Testing JS implemented map creation convenience functions"); + m = new TestInterfaceJSMaplike(); + ok(m, "JSMapConvenience: got a TestInterfaceJSMaplike object"); + is(m.size, 0, "JSMapConvenience: size should be zero"); + ok(!m.has("test"), "JSMapConvenience: maplike has should return false"); + m.setInternal("test", 1); + is(m.size, 1, "JSMapConvenience: size should be 1"); + ok(m.has("test"), "JSMapConvenience: maplike has should return true"); + is(m.get("test"), 1, "JSMapConvenience: maplike get should return value entered"); + m2 = m.setInternal("test2", 2); + is(m.size, 2, "JSMapConvenience: size should be 2"); + ok(m.has("test2"), "JSMapConvenience: maplike has should return true"); + is(m.get("test2"), 2, "JSMapConvenience: maplike get should return value entered"); + is(m.deleteInternal("test2"), true, "JSMapConvenience: maplike deleteInternal should return true"); + is(m.size, 1, "JSMapConvenience: size should be 1"); + for (var k of m.keys()) { + is(k, "test", "JSMapConvenience: first keys element should be 'test'"); + } + for (var v of m.values()) { + is(v, 1, "JSMapConvenience: first values elements should be 1"); + } + for (var e of m.entries()) { + is(e[0], "test", "JSMapConvenience: entries first array element should be 'test'"); + is(e[1], 1, "JSMapConvenience: entries second array element should be 1"); + } + m.clearInternal(); + is(m.size, 0, "JSMapConvenience: size should be 0 after clearInternal"); + + // Test this override for forEach + info("ForEachThisOverride: Testing this override for forEach"); + m = new TestInterfaceMaplike(); + m.set("test", 1); + m.forEach(function(v, k, o) { + "use strict"; + is(o, m, "ForEachThisOverride: foreach obj is correct"); + is(this, 5, "ForEachThisOverride: 'this' value should be correct"); + }, 5); + + // Test defaulting arguments on maplike to undefined + info("MapArgsDefault: Testing maplike defaulting arguments to undefined"); + m = new TestInterfaceMaplike(); + m.set(); + is(m.size, 1, "MapArgsDefault: should have 1 entry"); + m.forEach(function(v, k) { + "use strict"; + is(typeof k, "string", "MapArgsDefault: key is a string"); + is(k, "undefined", "MapArgsDefault: key is the string undefined"); + is(v, 0, "MapArgsDefault: value is 0"); + }); + is(m.get(), 0, "MapArgsDefault: no argument to get() returns correct value"); + m.delete(); + is(m.size, 0, "MapArgsDefault: should have 0 entries"); + + // Test defaulting arguments on setlike to undefined + info("SetArgsDefault: Testing setlike defaulting arguments to undefined"); + m = new TestInterfaceSetlike(); + m.add(); + is(m.size, 1, "SetArgsDefault: should have 1 entry"); + m.forEach(function(v, k) { + "use strict"; + is(typeof k, "string", "SetArgsDefault: key is a string"); + is(k, "undefined", "SetArgsDefault: key is the string undefined"); + }); + m.delete(); + is(m.size, 0, "SetArgsDefault: should have 0 entries"); + + SimpleTest.finish(); + }); + </script> + </body> +</html> diff --git a/dom/bindings/test/test_bug1123516_maplikesetlikechrome.xul b/dom/bindings/test/test_bug1123516_maplikesetlikechrome.xul new file mode 100644 index 000000000..4bc45cddd --- /dev/null +++ b/dom/bindings/test/test_bug1123516_maplikesetlikechrome.xul @@ -0,0 +1,68 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1123516 +--> +<window title="Mozilla Bug 1123516" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <iframe id="t"></iframe> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1123516" + target="_blank">Mozilla Bug 1123516</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for Bug 1123516 **/ + const Cu = Components.utils; + function doTest() { + var win = $("t").contentWindow; + var sandbox = Components.utils.Sandbox(win, { sandboxPrototype: win }); + is(sandbox._content, undefined, "_content does nothing over Xray"); + // Test cross-compartment usage of maplike/setlike WebIDL structures. + SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]}, function() { + try { + var maplike = Components.utils.evalInSandbox("var m = new TestInterfaceMaplike(); m;", sandbox); + maplike.set("test2", 2); + is(maplike.get("test2"), 2, "Should be able to create and use maplike/setlike across compartments"); + var test = Components.utils.evalInSandbox("m.get('test2');", sandbox); + is(test, 2, "Maplike/setlike should still work in original compartment"); + is(maplike.size, 1, "Testing size retrieval across compartments"); + } catch(e) { + ok(false, "Shouldn't throw when working with cross-compartment maplike/setlike interfaces " + e) + }; + try { + var setlike = Components.utils.evalInSandbox("var m = new TestInterfaceSetlikeNode(); m.add(document.documentElement); m;", sandbox); + is(TestInterfaceSetlikeNode.prototype.has.call(setlike, win.document.documentElement), true, + "Cross-compartment unwrapping/comparison has works"); + // TODO: Should throw until iterators are handled by Xrays, Bug 1023984 + try { + var e = TestInterfaceSetlikeNode.prototype.keys.call(setlike); + ok(false, "Calling iterators via xrays should fail"); + } catch(e) { + ok(true, "Calling iterators via xrays should fail"); + } + + setlike.forEach((v,k,t) => { is(v, win.document.documentElement, "Cross-compartment forEach works"); }); + TestInterfaceSetlikeNode.prototype.forEach.call(setlike, + (v,k,t) => { is(v, win.document.documentElement, "Cross-compartment forEach works"); }); + is(TestInterfaceSetlikeNode.prototype.delete.call(setlike, win.document.documentElement), true, + "Cross-compartment unwrapping/comparison delete works"); + } catch(e) { + ok(false, "Shouldn't throw when working with cross-compartment maplike/setlike interfaces " + e) + }; + SimpleTest.finish(); + }); + } + + SimpleTest.waitForExplicitFinish(); + addLoadEvent(doTest); + ]]> + </script> +</window> diff --git a/dom/bindings/test/test_bug1123875.html b/dom/bindings/test/test_bug1123875.html new file mode 100644 index 000000000..5658091c4 --- /dev/null +++ b/dom/bindings/test/test_bug1123875.html @@ -0,0 +1,14 @@ +<!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> + 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 000000000..ae72b2316 --- /dev/null +++ b/dom/bindings/test/test_bug1287912.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1287912 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1287912</title> + <script type="application/javascript" 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_bug560072.html b/dom/bindings/test/test_bug560072.html new file mode 100644 index 000000000..82bb1c2c6 --- /dev/null +++ b/dom/bindings/test/test_bug560072.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=560072 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 560072</title> + <script type="application/javascript" 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(HTMLDocument.prototype, "body").get.call(document), + "Should get body out of property descriptor"); + +is(document.body, + Object.getOwnPropertyDescriptor(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 000000000..b4b3151d7 --- /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 type="application/javascript" 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 000000000..602a0cd7c --- /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 type="application/javascript" 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 000000000..2e3b1ea30 --- /dev/null +++ b/dom/bindings/test/test_bug773326.html @@ -0,0 +1,11 @@ +<!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> +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 000000000..d8df05f63 --- /dev/null +++ b/dom/bindings/test/test_bug775543.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=775543 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 775543</title> + <script type="application/javascript" 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"> + +/** 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 000000000..787bd28fe --- /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 type="application/javascript" 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 000000000..0ca2c7dad --- /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 type="application/javascript" 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 000000000..4b0633328 --- /dev/null +++ b/dom/bindings/test/test_bug862092.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=862092 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 862092</title> + <script type="application/javascript" 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_bug963382.html b/dom/bindings/test/test_bug963382.html new file mode 100644 index 000000000..f48d2e8b0 --- /dev/null +++ b/dom/bindings/test/test_bug963382.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=963382 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 963382</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for clearing cache attributes in JS-implemented WebIDL implementations. **/ + SimpleTest.waitForExplicitFinish(); + SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]}, go); + + function go() { + var t = new TestInterfaceJS(); + + // Test [Cached] attribute clearing. + is(t.cachedAttr, 15, "Initial value of number"); + + t.setCachedAttr(3); + is(t.cachedAttr, 15, "Setting the number on the inner JS object should not affect cached value"); + + t.clearCachedAttrCache(); + is(t.cachedAttr, 3, "Setting the number on the inner JS object should affect cached value after clearing the cache."); + + SimpleTest.finish(); + } + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=963382">Mozilla Bug 963382</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_across_document_open.html b/dom/bindings/test/test_callback_across_document_open.html new file mode 100644 index 000000000..2a505cefa --- /dev/null +++ b/dom/bindings/test/test_callback_across_document_open.html @@ -0,0 +1,21 @@ +<!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> + 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 000000000..d98ed87b2 --- /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 type="application/javascript" 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 000000000..a40b0b94f --- /dev/null +++ b/dom/bindings/test/test_callback_exceptions.html @@ -0,0 +1,17 @@ +<!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> +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 000000000..fc53c8747 --- /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 type="application/javascript" 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 000000000..7808631b6 --- /dev/null +++ b/dom/bindings/test/test_crossOriginWindowSymbolAccess.html @@ -0,0 +1,23 @@ +<!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 src="http://www1.w3c-test.org/common/blank.html"></iframe> +<script> +async_test(function (t) { + window.addEventListener("load", t.step_func( + function() { + assert_equals(document.querySelector("iframe").contentDocument, null, "Should have a crossorigin frame"); + assert_throws(new Error(), function() { + frames[0][Symbol.iterator]; + }, "Should throw exception on cross-origin Window symbol-named get"); + assert_throws(new Error(), function() { + frames[0].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 000000000..f8f5f6283 --- /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 type="application/javascript" 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 (var 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 (var 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 000000000..cdadc5063 --- /dev/null +++ b/dom/bindings/test/test_document_location_set_via_xray.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=905493 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 905493</title> + <script type="application/javascript" 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 000000000..20eef10fb --- /dev/null +++ b/dom/bindings/test/test_document_location_via_xray_cached.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1041731 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1041731</title> + <script type="application/javascript" 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 000000000..a62adff2e --- /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 type="application/javascript" 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 000000000..0700db2f8 --- /dev/null +++ b/dom/bindings/test/test_dom_xrays.html @@ -0,0 +1,231 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=787070 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 787070</title> + <script type="application/javascript" 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 **/ + +var Cu = Components.utils; + +// 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 \"" + name + "\" through Xrays"); + is(Object.getOwnPropertyDescriptor(obj, name), undefined, "getOwnPropertyDescriptor shouldn't see \"" + name + "\" through Xrays"); + ok(Object.keys(obj).indexOf(name) == -1, "Enumerating the Xray should not return \"" + name + "\""); + } else { + ok(obj.hasOwnProperty(name), "hasOwnProperty should see \"" + name + "\" through Xrays"); + var pd = Object.getOwnPropertyDescriptor(obj, name); + ok(pd, "getOwnPropertyDescriptor should see \"" + name + "\" through Xrays"); + if (pd && pd.get) { + is(pd.get.call(instance), value, "Should get the right value for \"" + name + "\" through Xrays"); + } else { + is(obj[name], value, "Should get the right value for \"" + name + "\" through Xrays"); + } + if (pd && pd.enumerable) { + ok(Object.keys(obj).indexOf("" + name) > -1, "Enumerating the Xray should return \"" + name + "\""); + } + } + } while ((obj = Object.getPrototypeOf(obj))); +} + +function checkWindowXrayProperty(obj, name, windowValue, windowPrototypeValue, namedPropertiesValue, eventTargetValue) +{ + checkXrayProperty(obj, name, [ windowValue, windowPrototypeValue, namedPropertiesValue, eventTargetValue ]); +} + +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"); + + // Xrays need to filter expandos. + checkWindowXrayProperty(win, "expando", undefined); + ok(!("expando" in win), "Xrays should filter expandos"); + + checkWindowXrayProperty(win, "shadowedIframe", undefined, undefined, doc.getElementById("shadowedIframe").contentWindow); + ok("shadowedIframe" in win, "Named properties should be exposed through Xrays"); + + // Named properties live on the named properties object for global objects. + checkWindowXrayProperty(win, "iframe", undefined, undefined, doc.getElementById("iframe").contentWindow); + ok("iframe" in win, "Named properties should be exposed through Xrays"); + + // Window properties live on the instance, shadowing the properties of the named property object. + checkWindowXrayProperty(win, "document", doc, undefined, doc.getElementById("document").contentWindow); + 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", win, undefined, doc.getElementById("self").contentWindow); + ok("self" in win, "WebIDL properties should be exposed through Xrays"); + + // Object.prototype is at the end of the prototype chain. + var obj = win; + 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", undefined, undefined, undefined, 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"); + + // 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("foo", true); + 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. + var 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"); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(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 000000000..e5dc519a0 --- /dev/null +++ b/dom/bindings/test/test_enums.html @@ -0,0 +1,15 @@ +<!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> +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 000000000..9a6ab6088 --- /dev/null +++ b/dom/bindings/test/test_exceptionSanitization.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1295322 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1295322</title> + <script type="application/javascript" 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"> + + /** Test for Bug 1295322 **/ + iframe = document.createElement('iframe'); + iframe.name = "eWin"; + document.body.appendChild(iframe); + + try{ + // NOTE: The idea here is to call something that will end up throwing an + // exception in a JS component and then propagate back to C++ code before + // returning to us. If opening a feed: URI stops doing that, we will need a + // new guinea pig here. + open('feed://java:script:codeshouldgohere','eWin'); + ok(false, "Should have thrown!"); + } 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"); + } + } + </script> +</body> +</html> diff --git a/dom/bindings/test/test_exceptionThrowing.html b/dom/bindings/test/test_exceptionThrowing.html new file mode 100644 index 000000000..376c2bc57 --- /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 type="application/javascript" 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 (var h of [f, g]) { + try { is(h(), count, "Should get count exceptions here too"); } catch (e) {} + } + ok(true, "We should get here"); + + var domthrows = function() { xhr.withCredentials = false; } + xhr.open("GET", ""); + xhr.send(); + + is(f(), count, "Should get count exceptions from getter"); + for (var 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 000000000..a0f0cabe6 --- /dev/null +++ b/dom/bindings/test/test_exception_messages.html @@ -0,0 +1,82 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=882653 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 882653</title> + <script type="application/javascript" 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)', + "Argument 1 of Node.appendChild is not an object.", + "bogus interface argument" ], + [ 'document.documentElement.appendChild(null)', + "Argument 1 of Node.appendChild is not an object.", + "null interface argument" ], + [ 'document.createTreeWalker(document).currentNode = 5', + "Value being assigned to TreeWalker.currentNode is not an object.", + "interface setter call" ], + [ 'document.documentElement.appendChild({})', + "Argument 1 of Node.appendChild does not implement interface Node.", + "wrong interface argument" ], + [ 'document.createTreeWalker(document).currentNode = {}', + "Value being assigned to TreeWalker.currentNode does not implement interface Node.", + "wrong interface setter call" ], + [ 'document.createElement("canvas").getContext("2d").fill("bogus")', + "Argument 1 of CanvasRenderingContext2D.fill 'bogus' 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(), new RegExp())', + "Argument 2 of TextDecoder.decode can't be converted to a dictionary.", + "regexp passed for a dictionary" ], + [ 'URL.createObjectURL(null, null)', + "Argument 1 is not valid for any of the 2-argument overloads of URL.createObjectURL.", + "overload resolution failure" ], + [ 'document.createElement("select").add({})', + "Argument 1 of HTMLSelectElement.add 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, "")', + "Argument 1 of CanvasGradient.addColorStop is not a finite floating-point value.", + "invalid float" ] + ]; + + for (var i = 0; i < tests.length; ++i) { + msg = "Correct exception should be thrown for " + tests[i][2]; + try { + 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 000000000..8a98a8fb6 --- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** 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. */ + var isE10S = !SpecialPowers.isMainProcess(); + if (!isE10S && SpecialPowers.getBoolPref("javascript.options.asyncstack")) { + asyncFrame = `Async*@${file}:153:3 +`; + } 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:7 +${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, 7, "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:7 +${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:7 +${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, 7, "Should have the right column number for TypeError"); + } + + try { + t.testThrowCallbackError(function() { Array.indexOf() }); + } 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.indexOf", + "Should also have the right message (3)"); + is(e.stack, + `doTest/<@${file}:92:45 +doTest@${file}:92:7 +${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, 45, "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:7 +${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:7 +${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 000000000..d0f599353 --- /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 type="application/javascript" 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: null }); + 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 passed to addIceCandidate!", + "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", "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 000000000..53969a23e --- /dev/null +++ b/dom/bindings/test/test_forOf.html @@ -0,0 +1,86 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=725907 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 725907</title> + <script type="application/javascript" 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); } + function isnot(a, b, msg) { SimpleTest.isnot(a, b, msg + msgSuffix); } + + var basket = document.getElementById("basket"); + var egg3 = document.createElement("span"); + egg3.id = "egg3"; + + var log = ''; + for (var 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 (var 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 (var 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; + for (var x of document.getElementsByClassName("hazardous-materials")) + count++; + is(count, 0, "'for (x of emptyNodeList)' loop should run zero times"); + + var log = ''; + for (var 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 000000000..c74b68216 --- /dev/null +++ b/dom/bindings/test/test_integers.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <script type="application/javascript" 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_interfaceName.html b/dom/bindings/test/test_interfaceName.html new file mode 100644 index 000000000..59828a2cf --- /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 type="application/javascript" 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 000000000..c97b2f63b --- /dev/null +++ b/dom/bindings/test/test_interfaceToString.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=742156 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 742156</title> + <script type="application/javascript" 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).replace("replace", "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 000000000..8ce818e76 --- /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 type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + <body> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]}, function() { + + base_properties = [["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; + // Simple single type iterable creation and functionality test + 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 (var i = 0; i < 3; ++i) { + var key = key_itr.next(); + var value = value_itr.next(); + var 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(value, index, obj) { + is(index, callsToForEachCallback, + `IterableSingle: Should have the right index at ${callsToForEachCallback} calls to forEach callback`); + is(value, values[index], + `IterableSingle: Should have the right value at ${callsToForEachCallback} calls to forEach callback`); + is(this, thisArg, + "IterableSingle: Should have the right this value for forEach callback"); + is(obj, itr, + "IterableSingle: Should have the right third arg for forEach callback"); + ++callsToForEachCallback; + }, thisArg); + is(callsToForEachCallback, 3, + "IterableSingle: Should have right total number of calls to forEach callback"); + + var key = key_itr.next(); + var value = value_itr.next(); + var entry = entries_itr.next(); + 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"]] + 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 (var i = 0; i < 3; ++i) { + var key = key_itr.next(); + var value = value_itr.next(); + var 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(value, key, obj) { + is(key, keys[callsToForEachCallback], + `IterableDouble: Should have the right key at ${callsToForEachCallback} calls to forEach callback`); + is(value, values[callsToForEachCallback], + `IterableDouble: Should have the right value at ${callsToForEachCallback} calls to forEach callback`); + is(this, thisArg, + "IterableDouble: Should have the right this value for forEach callback"); + is(obj, itr, + "IterableSingle: Should have the right third arg for forEach callback"); + ++callsToForEachCallback; + }, thisArg); + is(callsToForEachCallback, 3, + "IterableDouble: Should have right total number of calls to forEach callback"); + + var key = key_itr.next(); + var value = value_itr.next(); + var entry = entries_itr.next() + 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 TestInterfaceIterableDoubleIteratorPrototype]", + "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'"); + var elements = [["long", 1], ["string", "a"]] + 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 (var i = 0; i < elements.length; ++i) { + var key = key_itr.next(); + var value = value_itr.next(); + var 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(value, key, obj) { + is(key, keys[callsToForEachCallback], + `IterableDoubleUnion: Should have the right key at ${callsToForEachCallback} calls to forEach callback`); + is(value, 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"); + + var key = key_itr.next(); + var value = value_itr.next(); + var 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 TestInterfaceIterableDoubleUnionIteratorPrototype]", + "iterator prototype should have the right brand"); + + SimpleTest.finish(); + }); + </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 000000000..2854a3112 --- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1186696 **/ + SimpleTest.waitForExplicitFinish(); + + function doTest() { + var values = [ function() {}, 5, null, undefined, "some string", {} ]; + + while (values.length != 0) { + 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_kill_longrunning_prerendered_content.xul b/dom/bindings/test/test_kill_longrunning_prerendered_content.xul new file mode 100644 index 000000000..d86b15ad9 --- /dev/null +++ b/dom/bindings/test/test_kill_longrunning_prerendered_content.xul @@ -0,0 +1,85 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + onload="runTest();"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + + SimpleTest.waitForExplicitFinish(); + + function Listener(aBrowser, aPrerendered, aCallback) { + this.init(aBrowser, aPrerendered, aCallback); + } + + Listener.prototype = { + init: function(aBrowser, aCallback) { + this.mBrowser = aBrowser; + this.mCallback = aCallback; + }, + QueryInterface: function(aIID) { + if (aIID.equals(Components.interfaces.nsIWebProgressListener) || + aIID.equals(Components.interfaces.nsISupportsWeakReference) || + aIID.equals(Components.interfaces.nsISupports)) + return this; + throw Components.results.NS_NOINTERFACE; + }, + onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus) { + if ((aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP) && + (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_IS_DOCUMENT)) { + setTimeout(this.mCallback, 0); + } + }, + onProgressChange : function(aWebProgress, aRequest, + aCurSelfProgress, aMaxSelfProgress, + aCurTotalProgress, aMaxTotalProgress) {}, + onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags) {}, + onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage) {}, + onSecurityChange : function(aWebProgress, aRequest, aState) {}, + mBrowser: null, + mPrerendered: false, + mCallback: null + }; + + var progress, progressListener; + + function runTest() { + SpecialPowers.pushPrefEnv({ + "set": [ + ["dom.max_script_run_time", 1] + ] + }, function() { + test(function() { + ok("The page is successfully interrupted."); + SimpleTest.finish(); + }); + }); + } + + function test(aCallback) { + var browser = document.getElementById("prerendered");; + progressListener = new Listener(browser, aCallback); + var docShell = browser.docShell; + progress = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIWebProgress); + progress.addProgressListener(progressListener, + Components.interfaces.nsIWebProgress.NOTIFY_ALL); + browser.loadURI("data:text/html,<script>;for(;;);</script" + ">"); + } + +]]> +</script> + +<body id="html_body" xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1050456">Mozilla Bug 1050456</a> +<p id="display"></p> + +<pre id="test"> +</pre> +</body> +<browser prerendered="true" id="prerendered"/> +</window> diff --git a/dom/bindings/test/test_lenientThis.html b/dom/bindings/test/test_lenientThis.html new file mode 100644 index 000000000..cfbdcebcd --- /dev/null +++ b/dom/bindings/test/test_lenientThis.html @@ -0,0 +1,27 @@ +<!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> +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 000000000..306ee4f64 --- /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 type="application/javascript" 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 000000000..205ec89f9 --- /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 type="application/javascript" 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 000000000..641f78ab2 --- /dev/null +++ b/dom/bindings/test/test_named_getter_enumerability.html @@ -0,0 +1,40 @@ +<!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> +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"); + 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"); + 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"); + 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_oom_reporting.html b/dom/bindings/test/test_oom_reporting.html new file mode 100644 index 000000000..7323736e5 --- /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 type="application/javascript" 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_primitive_this.html b/dom/bindings/test/test_primitive_this.html new file mode 100644 index 000000000..d2b733dff --- /dev/null +++ b/dom/bindings/test/test_primitive_this.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=603201 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 603201</title> + <script type="application/javascript" 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: function() { + "use strict"; + is(this, 1); + return "getter"; + }}); + Object.defineProperty(Object.getPrototypeOf(nodes), "getter2", {get: function() { + "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 000000000..5428030c5 --- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** 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(); + /* Async parent frames from pushPrefEnv don't show up in e10s. */ + var isE10S = !SpecialPowers.isMainProcess(); + var asyncStack = SpecialPowers.getBoolPref("javascript.options.asyncstack"); + var ourFile = location.href; + var unwrapError = "Promise rejection value is a non-unwrappable cross-compartment wrapper."; + var parentFrame = (asyncStack && !isE10S) ? `Async*@${ourFile}:130:3 +` : ""; + + Promise.all([ + t.testPromiseWithThrowingChromePromiseInit().then( + ensurePromiseFail.bind(null, 1), + checkExn.bind(null, 49, "InternalError", unwrapError, + undefined, ourFile, 1, + `doTest@${ourFile}:49:7 +` + + 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:7 +` + + parentFrame)), + t.testPromiseWithThrowingChromeThenFunction().then( + ensurePromiseFail.bind(null, 3), + checkExn.bind(null, 0, "InternalError", unwrapError, undefined, "", 3, asyncStack ? (`Async*doTest@${ourFile}:67:7 +` + + 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:7 +` : "") + + parentFrame)), + t.testPromiseWithThrowingChromeThenable().then( + ensurePromiseFail.bind(null, 5), + checkExn.bind(null, 0, "InternalError", unwrapError, undefined, "", 5, asyncStack ? (`Async*doTest@${ourFile}:84:7 +` + + parentFrame) : "")), + t.testPromiseWithThrowingContentThenable({ + then: function() { thereIsNoSuchContentFunction3(); } + }).then( + ensurePromiseFail.bind(null, 6), + checkExn.bind(null, 90, "ReferenceError", + "thereIsNoSuchContentFunction3 is not defined", + undefined, ourFile, 6, + `doTest/<.then@${ourFile}:90:32 +` + (asyncStack ? `Async*doTest@${ourFile}:89:7\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:7 +` + + 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:7 +` + + 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:7 +` + + 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 000000000..59affe6c0 --- /dev/null +++ b/dom/bindings/test/test_proxies_via_xray.html @@ -0,0 +1,99 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1021066 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1021066</title> + <script type="application/javascript" 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_queryInterface.html b/dom/bindings/test/test_queryInterface.html new file mode 100644 index 000000000..076bf9e7d --- /dev/null +++ b/dom/bindings/test/test_queryInterface.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=827546 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 827546</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 827546 **/ + + var notEditable = document.createElement("div"); + var thrown; + try { + thrown = false; + SpecialPowers.do_QueryInterface(notEditable, "nsIDOMNSEditableElement"); + } catch (e) { + thrown = true; + } + ok(thrown, + "QI to nsIDOMNSEditableElement on a non-editable element should fail"); + + var editable = document.createElement("input"); + ok(SpecialPowers.do_QueryInterface(editable, "nsIDOMNSEditableElement"), + "Editable element needs to support QI to nsIDOMNSEditableElement"); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=827546">Mozilla Bug 827546</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_returnUnion.html b/dom/bindings/test/test_returnUnion.html new file mode 100644 index 000000000..5be10ba3c --- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** 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 000000000..80dfac4db --- /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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** 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 000000000..7132e5601 --- /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 type="application/javascript" 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; + + 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: function(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 000000000..52f56151d --- /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 type="application/javascript" 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 000000000..1895b0342 --- /dev/null +++ b/dom/bindings/test/test_stringBindings.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1334537 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1334537</title> + <script type="application/javascript" 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(asShortAString, 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"); + + 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 000000000..e952819a8 --- /dev/null +++ b/dom/bindings/test/test_throwing_method_noDCE.html @@ -0,0 +1,27 @@ +<!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> +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++) {} + 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_traceProtos.html b/dom/bindings/test/test_traceProtos.html new file mode 100644 index 000000000..17a5cb96d --- /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 type="application/javascript" 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 000000000..fbb6ceb66 --- /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 type="application/javascript" 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 000000000..419e6ac7d --- /dev/null +++ b/dom/bindings/test/test_unforgeablesonexpando.html @@ -0,0 +1,18 @@ +<!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> +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 000000000..cbb1e7971 --- /dev/null +++ b/dom/bindings/test/test_usvstring.html @@ -0,0 +1,41 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Test USVString</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="application/javascript"> +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]}, function() { + 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 000000000..1331a83f4 --- /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 type="application/javascript" 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> |