mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-07 15:57:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
27
dom/bindings/AtomList.h
Normal file
27
dom/bindings/AtomList.h
Normal file
|
|
@ -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
|
||||
535
dom/bindings/BindingDeclarations.h
Normal file
535
dom/bindings/BindingDeclarations.h
Normal file
|
|
@ -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__
|
||||
3549
dom/bindings/BindingUtils.cpp
Normal file
3549
dom/bindings/BindingUtils.cpp
Normal file
File diff suppressed because it is too large
Load diff
3441
dom/bindings/BindingUtils.h
Normal file
3441
dom/bindings/BindingUtils.h
Normal file
File diff suppressed because it is too large
Load diff
1701
dom/bindings/Bindings.conf
Normal file
1701
dom/bindings/Bindings.conf
Normal file
File diff suppressed because it is too large
Load diff
78
dom/bindings/CallbackFunction.h
Normal file
78
dom/bindings/CallbackFunction.h
Normal file
|
|
@ -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
|
||||
36
dom/bindings/CallbackInterface.cpp
Normal file
36
dom/bindings/CallbackInterface.cpp
Normal file
|
|
@ -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
|
||||
59
dom/bindings/CallbackInterface.h
Normal file
59
dom/bindings/CallbackInterface.h
Normal file
|
|
@ -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
|
||||
331
dom/bindings/CallbackObject.cpp
Normal file
331
dom/bindings/CallbackObject.cpp
Normal file
|
|
@ -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
|
||||
607
dom/bindings/CallbackObject.h
Normal file
607
dom/bindings/CallbackObject.h
Normal file
|
|
@ -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
|
||||
17364
dom/bindings/Codegen.py
Normal file
17364
dom/bindings/Codegen.py
Normal file
File diff suppressed because it is too large
Load diff
791
dom/bindings/Configuration.py
Normal file
791
dom/bindings/Configuration.py
Normal file
|
|
@ -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
|
||||
467
dom/bindings/DOMJSClass.h
Normal file
467
dom/bindings/DOMJSClass.h
Normal file
|
|
@ -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 */
|
||||
362
dom/bindings/DOMJSProxyHandler.cpp
Normal file
362
dom/bindings/DOMJSProxyHandler.cpp
Normal file
|
|
@ -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
|
||||
268
dom/bindings/DOMJSProxyHandler.h
Normal file
268
dom/bindings/DOMJSProxyHandler.h
Normal file
|
|
@ -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 */
|
||||
245
dom/bindings/DOMString.h
Normal file
245
dom/bindings/DOMString.h
Normal file
|
|
@ -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
|
||||
49
dom/bindings/Date.cpp
Normal file
49
dom/bindings/Date.cpp
Normal file
|
|
@ -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
|
||||
61
dom/bindings/Date.h
Normal file
61
dom/bindings/Date.h
Normal file
|
|
@ -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
|
||||
84
dom/bindings/ErrorIPCUtils.h
Normal file
84
dom/bindings/ErrorIPCUtils.h
Normal file
|
|
@ -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
|
||||
587
dom/bindings/ErrorResult.h
Normal file
587
dom/bindings/ErrorResult.h
Normal file
|
|
@ -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 */
|
||||
107
dom/bindings/Errors.msg
Normal file
107
dom/bindings/Errors.msg
Normal file
|
|
@ -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.")
|
||||
709
dom/bindings/Exceptions.cpp
Normal file
709
dom/bindings/Exceptions.cpp
Normal file
|
|
@ -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
|
||||
69
dom/bindings/Exceptions.h
Normal file
69
dom/bindings/Exceptions.h
Normal file
|
|
@ -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
|
||||
160
dom/bindings/FakeString.h
Normal file
160
dom/bindings/FakeString.h
Normal file
|
|
@ -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__ */
|
||||
84
dom/bindings/GenerateCSS2PropertiesWebIDL.py
Normal file
84
dom/bindings/GenerateCSS2PropertiesWebIDL.py
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
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()
|
||||
35
dom/bindings/IterableIterator.cpp
Normal file
35
dom/bindings/IterableIterator.cpp
Normal file
|
|
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
204
dom/bindings/IterableIterator.h
Normal file
204
dom/bindings/IterableIterator.h
Normal file
|
|
@ -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
|
||||
34
dom/bindings/JSSlots.h
Normal file
34
dom/bindings/JSSlots.h
Normal file
|
|
@ -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 */
|
||||
70
dom/bindings/Makefile.in
Normal file
70
dom/bindings/Makefile.in
Normal file
|
|
@ -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)
|
||||
121
dom/bindings/MozMap.h
Normal file
121
dom/bindings/MozMap.h
Normal file
|
|
@ -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
|
||||
37
dom/bindings/NonRefcountedDOMObject.h
Normal file
37
dom/bindings/NonRefcountedDOMObject.h
Normal file
|
|
@ -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__ */
|
||||
140
dom/bindings/Nullable.h
Normal file
140
dom/bindings/Nullable.h
Normal file
|
|
@ -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 */
|
||||
359
dom/bindings/PrimitiveConversions.h
Normal file
359
dom/bindings/PrimitiveConversions.h
Normal file
|
|
@ -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 */
|
||||
58
dom/bindings/RootedDictionary.h
Normal file
58
dom/bindings/RootedDictionary.h
Normal file
|
|
@ -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__ */
|
||||
73
dom/bindings/RootedOwningNonNull.h
Normal file
73
dom/bindings/RootedOwningNonNull.h
Normal file
|
|
@ -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__ */
|
||||
59
dom/bindings/RootedRefPtr.h
Normal file
59
dom/bindings/RootedRefPtr.h
Normal file
|
|
@ -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__ */
|
||||
176
dom/bindings/SimpleGlobalObject.cpp
Normal file
176
dom/bindings/SimpleGlobalObject.cpp
Normal file
|
|
@ -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
|
||||
99
dom/bindings/SimpleGlobalObject.h
Normal file
99
dom/bindings/SimpleGlobalObject.h
Normal file
|
|
@ -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__ */
|
||||
58
dom/bindings/StructuredClone.cpp
Normal file
58
dom/bindings/StructuredClone.cpp
Normal file
|
|
@ -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
|
||||
25
dom/bindings/StructuredClone.h
Normal file
25
dom/bindings/StructuredClone.h
Normal file
|
|
@ -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
|
||||
80
dom/bindings/ToJSValue.cpp
Normal file
80
dom/bindings/ToJSValue.cpp
Normal file
|
|
@ -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
|
||||
377
dom/bindings/ToJSValue.h
Normal file
377
dom/bindings/ToJSValue.h
Normal file
|
|
@ -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 */
|
||||
441
dom/bindings/TypedArray.h
Normal file
441
dom/bindings/TypedArray.h
Normal file
|
|
@ -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 */
|
||||
59
dom/bindings/UnionMember.h
Normal file
59
dom/bindings/UnionMember.h
Normal file
|
|
@ -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
|
||||
324
dom/bindings/WebIDLGlobalNameHash.cpp
Normal file
324
dom/bindings/WebIDLGlobalNameHash.cpp
Normal file
|
|
@ -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
|
||||
70
dom/bindings/WebIDLGlobalNameHash.h
Normal file
70
dom/bindings/WebIDLGlobalNameHash.h
Normal file
|
|
@ -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__
|
||||
41
dom/bindings/XrayExpandoClass.h
Normal file
41
dom/bindings/XrayExpandoClass.h
Normal file
|
|
@ -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 */
|
||||
16
dom/bindings/crashtests/1010658-1.html
Normal file
16
dom/bindings/crashtests/1010658-1.html
Normal file
|
|
@ -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>
|
||||
16
dom/bindings/crashtests/1010658-2.html
Normal file
16
dom/bindings/crashtests/1010658-2.html
Normal file
|
|
@ -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>
|
||||
11
dom/bindings/crashtests/769464.html
Normal file
11
dom/bindings/crashtests/769464.html
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<script>
|
||||
|
||||
function boom()
|
||||
{
|
||||
window.getComputedStyle(new Worker("404.js"));
|
||||
}
|
||||
|
||||
window.addEventListener("load", boom, false);
|
||||
|
||||
</script>
|
||||
11
dom/bindings/crashtests/822340-1.html
Normal file
11
dom/bindings/crashtests/822340-1.html
Normal file
|
|
@ -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>
|
||||
8
dom/bindings/crashtests/822340-2.html
Normal file
8
dom/bindings/crashtests/822340-2.html
Normal file
|
|
@ -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>
|
||||
5
dom/bindings/crashtests/832899.html
Normal file
5
dom/bindings/crashtests/832899.html
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<!DOCTYPE html>
|
||||
<script>
|
||||
var ev = document.createEvent("Events");
|
||||
EventTarget.prototype.dispatchEvent.call(navigator.connection, ev);
|
||||
</script>
|
||||
4
dom/bindings/crashtests/860551.html
Normal file
4
dom/bindings/crashtests/860551.html
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<script>
|
||||
SVGZoomAndPan instanceof SVGZoomAndPan
|
||||
</script>
|
||||
20
dom/bindings/crashtests/860591.html
Normal file
20
dom/bindings/crashtests/860591.html
Normal file
|
|
@ -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>
|
||||
19
dom/bindings/crashtests/862092.html
Normal file
19
dom/bindings/crashtests/862092.html
Normal file
|
|
@ -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>
|
||||
20
dom/bindings/crashtests/862610.html
Normal file
20
dom/bindings/crashtests/862610.html
Normal file
|
|
@ -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>
|
||||
22
dom/bindings/crashtests/869038.html
Normal file
22
dom/bindings/crashtests/869038.html
Normal file
|
|
@ -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>
|
||||
16
dom/bindings/crashtests/949940.html
Normal file
16
dom/bindings/crashtests/949940.html
Normal file
|
|
@ -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>
|
||||
12
dom/bindings/crashtests/crashtests.list
Normal file
12
dom/bindings/crashtests/crashtests.list
Normal file
|
|
@ -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
|
||||
123
dom/bindings/docs/index.rst
Normal file
123
dom/bindings/docs/index.rst
Normal file
|
|
@ -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``.
|
||||
55
dom/bindings/mach_commands.py
Normal file
55
dom/bindings/mach_commands.py
Normal file
|
|
@ -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"])
|
||||
161
dom/bindings/moz.build
Normal file
161
dom/bindings/moz.build
Normal file
|
|
@ -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',
|
||||
]
|
||||
583
dom/bindings/mozwebidlcodegen/__init__.py
Normal file
583
dom/bindings/mozwebidlcodegen/__init__.py
Normal file
|
|
@ -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
|
||||
3
dom/bindings/mozwebidlcodegen/test/Child.webidl
Normal file
3
dom/bindings/mozwebidlcodegen/test/Child.webidl
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
interface Child : Parent {
|
||||
void ChildBaz();
|
||||
};
|
||||
3
dom/bindings/mozwebidlcodegen/test/ExampleBinding.webidl
Normal file
3
dom/bindings/mozwebidlcodegen/test/ExampleBinding.webidl
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
/* These interfaces are hard-coded and need to be defined. */
|
||||
interface TestExampleInterface {};
|
||||
interface TestExampleProxyInterface {};
|
||||
3
dom/bindings/mozwebidlcodegen/test/Parent.webidl
Normal file
3
dom/bindings/mozwebidlcodegen/test/Parent.webidl
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
interface Parent {
|
||||
void MethodFoo();
|
||||
};
|
||||
13
dom/bindings/mozwebidlcodegen/test/TestEvent.webidl
Normal file
13
dom/bindings/mozwebidlcodegen/test/TestEvent.webidl
Normal file
|
|
@ -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;
|
||||
};
|
||||
298
dom/bindings/mozwebidlcodegen/test/test_mozwebidlcodegen.py
Normal file
298
dom/bindings/mozwebidlcodegen/test/test_mozwebidlcodegen.py
Normal file
|
|
@ -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()
|
||||
1
dom/bindings/parser/README
Normal file
1
dom/bindings/parser/README
Normal file
|
|
@ -0,0 +1 @@
|
|||
A WebIDL parser written in Python to be used in Mozilla.
|
||||
1
dom/bindings/parser/UPSTREAM
Normal file
1
dom/bindings/parser/UPSTREAM
Normal file
|
|
@ -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
|
||||
6874
dom/bindings/parser/WebIDL.py
Normal file
6874
dom/bindings/parser/WebIDL.py
Normal file
File diff suppressed because it is too large
Load diff
108
dom/bindings/parser/runtests.py
Normal file
108
dom/bindings/parser/runtests.py
Normal file
|
|
@ -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)
|
||||
14
dom/bindings/parser/tests/test_any_null.py
Normal file
14
dom/bindings/parser/tests/test_any_null.py
Normal file
|
|
@ -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.")
|
||||
|
|
@ -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.")
|
||||
14
dom/bindings/parser/tests/test_argument_novoid.py
Normal file
14
dom/bindings/parser/tests/test_argument_novoid.py
Normal file
|
|
@ -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.")
|
||||
81
dom/bindings/parser/tests/test_arraybuffer.py
Normal file
81
dom/bindings/parser/tests/test_arraybuffer.py
Normal file
|
|
@ -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")
|
||||
177
dom/bindings/parser/tests/test_attr.py
Normal file
177
dom/bindings/parser/tests/test_attr.py
Normal file
|
|
@ -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")
|
||||
67
dom/bindings/parser/tests/test_attr_sequence_type.py
Normal file
67
dom/bindings/parser/tests/test_attr_sequence_type.py
Normal file
|
|
@ -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")
|
||||
11
dom/bindings/parser/tests/test_builtin_filename.py
Normal file
11
dom/bindings/parser/tests/test_builtin_filename.py
Normal file
|
|
@ -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')
|
||||
41
dom/bindings/parser/tests/test_builtins.py
Normal file
41
dom/bindings/parser/tests/test_builtins.py
Normal file
|
|
@ -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")
|
||||
99
dom/bindings/parser/tests/test_bytestring.py
Normal file
99
dom/bindings/parser/tests/test_bytestring.py
Normal file
|
|
@ -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")
|
||||
34
dom/bindings/parser/tests/test_callback.py
Normal file
34
dom/bindings/parser/tests/test_callback.py
Normal file
|
|
@ -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")
|
||||
94
dom/bindings/parser/tests/test_callback_interface.py
Normal file
94
dom/bindings/parser/tests/test_callback_interface.py
Normal file
|
|
@ -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)
|
||||
110
dom/bindings/parser/tests/test_conditional_dictionary_member.py
Normal file
110
dom/bindings/parser/tests/test_conditional_dictionary_member.py
Normal file
|
|
@ -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)")
|
||||
80
dom/bindings/parser/tests/test_const.py
Normal file
80
dom/bindings/parser/tests/test_const.py
Normal file
|
|
@ -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.")
|
||||
|
||||
109
dom/bindings/parser/tests/test_constructor.py
Normal file
109
dom/bindings/parser/tests/test_constructor.py
Normal file
|
|
@ -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")
|
||||
|
|
@ -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 {
|
||||
};
|
||||
""")
|
||||
15
dom/bindings/parser/tests/test_date.py
Normal file
15
dom/bindings/parser/tests/test_date.py
Normal file
|
|
@ -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")
|
||||
15
dom/bindings/parser/tests/test_deduplicate.py
Normal file
15
dom/bindings/parser/tests/test_deduplicate.py
Normal file
|
|
@ -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.")
|
||||
555
dom/bindings/parser/tests/test_dictionary.py
Normal file
555
dom/bindings/parser/tests/test_dictionary.py
Normal file
|
|
@ -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")
|
||||
293
dom/bindings/parser/tests/test_distinguishability.py
Normal file
293
dom/bindings/parser/tests/test_distinguishability.py
Normal file
|
|
@ -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)
|
||||
14
dom/bindings/parser/tests/test_double_null.py
Normal file
14
dom/bindings/parser/tests/test_double_null.py
Normal file
|
|
@ -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.")
|
||||
84
dom/bindings/parser/tests/test_duplicate_qualifiers.py
Normal file
84
dom/bindings/parser/tests/test_duplicate_qualifiers.py
Normal file
|
|
@ -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.")
|
||||
14
dom/bindings/parser/tests/test_empty_enum.py
Normal file
14
dom/bindings/parser/tests/test_empty_enum.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -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")
|
||||
|
||||
93
dom/bindings/parser/tests/test_enum.py
Normal file
93
dom/bindings/parser/tests/test_enum.py
Normal file
|
|
@ -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")
|
||||
13
dom/bindings/parser/tests/test_enum_duplicate_values.py
Normal file
13
dom/bindings/parser/tests/test_enum_duplicate_values.py
Normal file
|
|
@ -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")
|
||||
20
dom/bindings/parser/tests/test_error_colno.py
Normal file
20
dom/bindings/parser/tests/test_error_colno.py
Normal file
|
|
@ -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.")
|
||||
28
dom/bindings/parser/tests/test_error_lineno.py
Normal file
28
dom/bindings/parser/tests/test_error_lineno.py
Normal file
|
|
@ -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.")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue