import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

27
dom/bindings/AtomList.h Normal file
View 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

View 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__

File diff suppressed because it is too large Load diff

3441
dom/bindings/BindingUtils.h Normal file

File diff suppressed because it is too large Load diff

1701
dom/bindings/Bindings.conf Normal file

File diff suppressed because it is too large Load diff

View 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

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load diff

View 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
View 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 */

View 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

View 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
View 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
View 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
View 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

View 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
View 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
View 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
View 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
View 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
View 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__ */

View 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()

View 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
}
}

View 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
View 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
View 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
View 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

View 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
View 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 */

View 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 */

View 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__ */

View 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__ */

View 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__ */

View 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

View 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__ */

View 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

View 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

View 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
View 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
View 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 */

View 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

View 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

View 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__

View 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 */

View 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>

View 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>

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<script>
function boom()
{
window.getComputedStyle(new Worker("404.js"));
}
window.addEventListener("load", boom, false);
</script>

View 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>

View 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>

View file

@ -0,0 +1,5 @@
<!DOCTYPE html>
<script>
var ev = document.createEvent("Events");
EventTarget.prototype.dispatchEvent.call(navigator.connection, ev);
</script>

View file

@ -0,0 +1,4 @@
<!DOCTYPE html>
<script>
SVGZoomAndPan instanceof SVGZoomAndPan
</script>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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
View 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``.

View 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
View 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',
]

View 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

View file

@ -0,0 +1,3 @@
interface Child : Parent {
void ChildBaz();
};

View file

@ -0,0 +1,3 @@
/* These interfaces are hard-coded and need to be defined. */
interface TestExampleInterface {};
interface TestExampleProxyInterface {};

View file

@ -0,0 +1,3 @@
interface Parent {
void MethodFoo();
};

View 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;
};

View 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()

View file

@ -0,0 +1 @@
A WebIDL parser written in Python to be used in Mozilla.

View 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

File diff suppressed because it is too large Load diff

View 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)

View 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.")

View file

@ -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.")

View 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.")

View 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")

View 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")

View 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")

View 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')

View 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")

View 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")

View 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")

View 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)

View 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)")

View 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.")

View 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")

View file

@ -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 {
};
""")

View 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")

View 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.")

View 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")

View 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)

View 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.")

View 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.")

View 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()

View file

@ -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")

View 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")

View 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")

View 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.")

View 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