diff options
Diffstat (limited to 'components/script/dom/bindings/codegen')
103 files changed, 30131 insertions, 0 deletions
diff --git a/components/script/dom/bindings/codegen/BindingGen.py b/components/script/dom/bindings/codegen/BindingGen.py new file mode 100644 index 00000000000..408280dacfb --- /dev/null +++ b/components/script/dom/bindings/codegen/BindingGen.py @@ -0,0 +1,52 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.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 +sys.path.append("./parser/") +sys.path.append("./ply/") +import os +import cPickle +import WebIDL +from Configuration import * +from CodegenRust import CGBindingRoot, replaceFileIfChanged + +def generate_binding_rs(config, outputprefix, webidlfile): + """ + |config| Is the configuration object. + |outputprefix| is a prefix to use for the header guards and filename. + """ + + filename = outputprefix + ".rs" + root = CGBindingRoot(config, outputprefix, webidlfile) + if replaceFileIfChanged(filename, root.define()): + print "Generating binding implementation: %s" % (filename) + +def main(): + # Parse arguments. + from optparse import OptionParser + usagestring = "usage: %prog configFile outputPrefix webIDLFile" + o = OptionParser(usage=usagestring) + o.add_option("--verbose-errors", action='store_true', default=False, + help="When an error happens, display the Python traceback.") + (options, args) = o.parse_args() + + if len(args) != 3: + o.error(usagestring) + configFile = os.path.normpath(args[0]) + outputPrefix = args[1] + webIDLFile = os.path.normpath(args[2]) + + # Load the parsing results + f = open('ParserResults.pkl', 'rb') + parserData = cPickle.load(f) + f.close() + + # Create the configuration data. + config = Configuration(configFile, parserData) + + # Generate the prototype classes. + generate_binding_rs(config, outputPrefix, webIDLFile); + +if __name__ == '__main__': + main() diff --git a/components/script/dom/bindings/codegen/BindingUtils.cpp b/components/script/dom/bindings/codegen/BindingUtils.cpp new file mode 100644 index 00000000000..27ac92e3596 --- /dev/null +++ b/components/script/dom/bindings/codegen/BindingUtils.cpp @@ -0,0 +1,633 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ +/* vim: set ts=2 sw=2 et tw=79: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 <stdarg.h> + +#include "BindingUtils.h" + +#include "WrapperFactory.h" +#include "xpcprivate.h" +#include "XPCQuickStubs.h" + +namespace mozilla { +namespace dom { + +JSErrorFormatString ErrorFormatString[] = { +#define MSG_DEF(_name, _argc, _str) \ + { _str, _argc, JSEXN_TYPEERR }, +#include "mozilla/dom/Errors.msg" +#undef MSG_DEF +}; + +const JSErrorFormatString* +GetErrorMessage(void* aUserRef, const char* aLocale, + const unsigned aErrorNumber) +{ + MOZ_ASSERT(aErrorNumber < ArrayLength(ErrorFormatString)); + return &ErrorFormatString[aErrorNumber]; +} + +bool +ThrowErrorMessage(JSContext* aCx, const ErrNum aErrorNumber, ...) +{ + va_list ap; + va_start(ap, aErrorNumber); + JS_ReportErrorNumberVA(aCx, GetErrorMessage, NULL, + static_cast<const unsigned>(aErrorNumber), ap); + va_end(ap); + return false; +} + +bool +DefineConstants(JSContext* cx, JSObject* obj, ConstantSpec* cs) +{ + for (; cs->name; ++cs) { + JSBool ok = + JS_DefineProperty(cx, obj, cs->name, cs->value, NULL, NULL, + JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); + if (!ok) { + return false; + } + } + return true; +} + +static inline bool +Define(JSContext* cx, JSObject* obj, JSFunctionSpec* spec) { + return JS_DefineFunctions(cx, obj, spec); +} +static inline bool +Define(JSContext* cx, JSObject* obj, JSPropertySpec* spec) { + return JS_DefineProperties(cx, obj, spec); +} +static inline bool +Define(JSContext* cx, JSObject* obj, ConstantSpec* spec) { + return DefineConstants(cx, obj, spec); +} + +template<typename T> +bool +DefinePrefable(JSContext* cx, JSObject* obj, Prefable<T>* props) +{ + MOZ_ASSERT(props); + MOZ_ASSERT(props->specs); + do { + // Define if enabled + if (props->enabled) { + if (!Define(cx, obj, props->specs)) { + return false; + } + } + } while ((++props)->specs); + return true; +} + +// We should use JSFunction objects for interface objects, but we need a custom +// hasInstance hook because we have new interface objects on prototype chains of +// old (XPConnect-based) bindings. Because Function.prototype.toString throws if +// passed a non-Function object we also need to provide our own toString method +// for interface objects. + +enum { + TOSTRING_CLASS_RESERVED_SLOT = 0, + TOSTRING_NAME_RESERVED_SLOT = 1 +}; + +JSBool +InterfaceObjectToString(JSContext* cx, unsigned argc, JS::Value *vp) +{ + JSObject* callee = JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)); + + JSObject* obj = JS_THIS_OBJECT(cx, vp); + if (!obj) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CONVERT_TO, + "null", "object"); + return false; + } + + jsval v = js::GetFunctionNativeReserved(callee, TOSTRING_CLASS_RESERVED_SLOT); + JSClass* clasp = static_cast<JSClass*>(JSVAL_TO_PRIVATE(v)); + + v = js::GetFunctionNativeReserved(callee, TOSTRING_NAME_RESERVED_SLOT); + JSString* jsname = static_cast<JSString*>(JSVAL_TO_STRING(v)); + size_t length; + const jschar* name = JS_GetInternedStringCharsAndLength(jsname, &length); + + if (js::GetObjectJSClass(obj) != clasp) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO, + NS_ConvertUTF16toUTF8(name).get(), "toString", + "object"); + return false; + } + + nsString str; + str.AppendLiteral("function "); + str.Append(name, length); + str.AppendLiteral("() {"); + str.Append('\n'); + str.AppendLiteral(" [native code]"); + str.Append('\n'); + str.AppendLiteral("}"); + + return xpc::NonVoidStringToJsval(cx, str, vp); +} + +static JSObject* +CreateInterfaceObject(JSContext* cx, JSObject* global, JSObject* receiver, + JSClass* constructorClass, JSNative constructorNative, + unsigned ctorNargs, JSObject* proto, + Prefable<JSFunctionSpec>* staticMethods, + Prefable<ConstantSpec>* constants, + const char* name) +{ + JSObject* constructor; + if (constructorClass) { + JSObject* functionProto = JS_GetFunctionPrototype(cx, global); + if (!functionProto) { + return NULL; + } + constructor = JS_NewObject(cx, constructorClass, functionProto, global); + } else { + MOZ_ASSERT(constructorNative); + JSFunction* fun = JS_NewFunction(cx, constructorNative, ctorNargs, + JSFUN_CONSTRUCTOR, global, name); + if (!fun) { + return NULL; + } + constructor = JS_GetFunctionObject(fun); + } + if (!constructor) { + return NULL; + } + + if (staticMethods && !DefinePrefable(cx, constructor, staticMethods)) { + return NULL; + } + + if (constructorClass) { + JSFunction* toString = js::DefineFunctionWithReserved(cx, constructor, + "toString", + InterfaceObjectToString, + 0, 0); + if (!toString) { + return NULL; + } + + JSObject* toStringObj = JS_GetFunctionObject(toString); + js::SetFunctionNativeReserved(toStringObj, TOSTRING_CLASS_RESERVED_SLOT, + PRIVATE_TO_JSVAL(constructorClass)); + + JSString *str = ::JS_InternString(cx, name); + if (!str) { + return NULL; + } + js::SetFunctionNativeReserved(toStringObj, TOSTRING_NAME_RESERVED_SLOT, + STRING_TO_JSVAL(str)); + } + + if (constants && !DefinePrefable(cx, constructor, constants)) { + return NULL; + } + + if (proto && !JS_LinkConstructorAndPrototype(cx, constructor, proto)) { + return NULL; + } + + JSBool alreadyDefined; + if (!JS_AlreadyHasOwnProperty(cx, receiver, name, &alreadyDefined)) { + return NULL; + } + + // This is Enumerable: False per spec. + if (!alreadyDefined && + !JS_DefineProperty(cx, receiver, name, OBJECT_TO_JSVAL(constructor), NULL, + NULL, 0)) { + return NULL; + } + + return constructor; +} + +static JSObject* +CreateInterfacePrototypeObject(JSContext* cx, JSObject* global, + JSObject* parentProto, JSClass* protoClass, + Prefable<JSFunctionSpec>* methods, + Prefable<JSPropertySpec>* properties, + Prefable<ConstantSpec>* constants) +{ + JSObject* ourProto = JS_NewObjectWithUniqueType(cx, protoClass, parentProto, + global); + if (!ourProto) { + return NULL; + } + + if (methods && !DefinePrefable(cx, ourProto, methods)) { + return NULL; + } + + if (properties && !DefinePrefable(cx, ourProto, properties)) { + return NULL; + } + + if (constants && !DefinePrefable(cx, ourProto, constants)) { + return NULL; + } + + return ourProto; +} + +JSObject* +CreateInterfaceObjects(JSContext* cx, JSObject* global, JSObject *receiver, + JSObject* protoProto, JSClass* protoClass, + JSClass* constructorClass, JSNative constructor, + unsigned ctorNargs, const DOMClass* domClass, + Prefable<JSFunctionSpec>* methods, + Prefable<JSPropertySpec>* properties, + Prefable<ConstantSpec>* constants, + Prefable<JSFunctionSpec>* staticMethods, const char* name) +{ + MOZ_ASSERT(protoClass || constructorClass || constructor, + "Need at least one class or a constructor!"); + MOZ_ASSERT(!(methods || properties) || protoClass, + "Methods or properties but no protoClass!"); + MOZ_ASSERT(!staticMethods || constructorClass || constructor, + "Static methods but no constructorClass or constructor!"); + MOZ_ASSERT(bool(name) == bool(constructorClass || constructor), + "Must have name precisely when we have an interface object"); + MOZ_ASSERT(!constructorClass || !constructor); + + JSObject* proto; + if (protoClass) { + proto = CreateInterfacePrototypeObject(cx, global, protoProto, protoClass, + methods, properties, constants); + if (!proto) { + return NULL; + } + + js::SetReservedSlot(proto, DOM_PROTO_INSTANCE_CLASS_SLOT, + JS::PrivateValue(const_cast<DOMClass*>(domClass))); + } + else { + proto = NULL; + } + + JSObject* interface; + if (constructorClass || constructor) { + interface = CreateInterfaceObject(cx, global, receiver, constructorClass, + constructor, ctorNargs, proto, + staticMethods, constants, name); + if (!interface) { + return NULL; + } + } + + return protoClass ? proto : interface; +} + +static bool +NativeInterface2JSObjectAndThrowIfFailed(XPCLazyCallContext& aLccx, + JSContext* aCx, + JS::Value* aRetval, + xpcObjectHelper& aHelper, + const nsIID* aIID, + bool aAllowNativeWrapper) +{ + nsresult rv; + if (!XPCConvert::NativeInterface2JSObject(aLccx, aRetval, NULL, aHelper, aIID, + NULL, aAllowNativeWrapper, &rv)) { + // I can't tell if NativeInterface2JSObject throws JS exceptions + // or not. This is a sloppy stab at the right semantics; the + // method really ought to be fixed to behave consistently. + if (!JS_IsExceptionPending(aCx)) { + Throw<true>(aCx, NS_FAILED(rv) ? rv : NS_ERROR_UNEXPECTED); + } + return false; + } + return true; +} + +bool +DoHandleNewBindingWrappingFailure(JSContext* cx, JSObject* scope, + nsISupports* value, JS::Value* vp) +{ + if (JS_IsExceptionPending(cx)) { + return false; + } + + XPCLazyCallContext lccx(JS_CALLER, cx, scope); + + if (value) { + xpcObjectHelper helper(value); + return NativeInterface2JSObjectAndThrowIfFailed(lccx, cx, vp, helper, NULL, + true); + } + + return Throw<true>(cx, NS_ERROR_XPC_BAD_CONVERT_JS); +} + +// Can only be called with the immediate prototype of the instance object. Can +// only be called on the prototype of an object known to be a DOM instance. +JSBool +InstanceClassHasProtoAtDepth(JSHandleObject protoObject, uint32_t protoID, + uint32_t depth) +{ + const DOMClass* domClass = static_cast<DOMClass*>( + js::GetReservedSlot(protoObject, DOM_PROTO_INSTANCE_CLASS_SLOT).toPrivate()); + return (uint32_t)domClass->mInterfaceChain[depth] == protoID; +} + +// Only set allowNativeWrapper to false if you really know you need it, if in +// doubt use true. Setting it to false disables security wrappers. +bool +XPCOMObjectToJsval(JSContext* cx, JSObject* scope, xpcObjectHelper &helper, + const nsIID* iid, bool allowNativeWrapper, JS::Value* rval) +{ + XPCLazyCallContext lccx(JS_CALLER, cx, scope); + + if (!NativeInterface2JSObjectAndThrowIfFailed(lccx, cx, rval, helper, iid, + allowNativeWrapper)) { + return false; + } + +#ifdef DEBUG + JSObject* jsobj = JSVAL_TO_OBJECT(*rval); + if (jsobj && !js::GetObjectParent(jsobj)) + NS_ASSERTION(js::GetObjectClass(jsobj)->flags & JSCLASS_IS_GLOBAL, + "Why did we recreate this wrapper?"); +#endif + + return true; +} + +JSBool +QueryInterface(JSContext* cx, unsigned argc, JS::Value* vp) +{ + JS::Value thisv = JS_THIS(cx, vp); + if (thisv == JSVAL_NULL) + return false; + + // Get the object. It might be a security wrapper, in which case we do a checked + // unwrap. + JSObject* origObj = JSVAL_TO_OBJECT(thisv); + JSObject* obj = js::UnwrapObjectChecked(cx, origObj); + if (!obj) + return false; + + nsISupports* native; + if (!UnwrapDOMObjectToISupports(obj, native)) { + return Throw<true>(cx, NS_ERROR_FAILURE); + } + + if (argc < 1) { + return Throw<true>(cx, NS_ERROR_XPC_NOT_ENOUGH_ARGS); + } + + JS::Value* argv = JS_ARGV(cx, vp); + if (!argv[0].isObject()) { + return Throw<true>(cx, NS_ERROR_XPC_BAD_CONVERT_JS); + } + + nsIJSIID* iid; + xpc_qsSelfRef iidRef; + if (NS_FAILED(xpc_qsUnwrapArg<nsIJSIID>(cx, argv[0], &iid, &iidRef.ptr, + &argv[0]))) { + return Throw<true>(cx, NS_ERROR_XPC_BAD_CONVERT_JS); + } + MOZ_ASSERT(iid); + + if (iid->GetID()->Equals(NS_GET_IID(nsIClassInfo))) { + nsresult rv; + nsCOMPtr<nsIClassInfo> ci = do_QueryInterface(native, &rv); + if (NS_FAILED(rv)) { + return Throw<true>(cx, rv); + } + + return WrapObject(cx, origObj, ci, &NS_GET_IID(nsIClassInfo), vp); + } + + // Lie, otherwise we need to check classinfo or QI + *vp = thisv; + return true; +} + +JSBool +ThrowingConstructor(JSContext* cx, unsigned argc, JS::Value* vp) +{ + return ThrowErrorMessage(cx, MSG_ILLEGAL_CONSTRUCTOR); +} + +bool +XrayResolveProperty(JSContext* cx, JSObject* wrapper, jsid id, + JSPropertyDescriptor* desc, + // And the things we need to determine the descriptor + Prefable<JSFunctionSpec>* methods, + jsid* methodIds, + JSFunctionSpec* methodSpecs, + size_t methodCount, + Prefable<JSPropertySpec>* attributes, + jsid* attributeIds, + JSPropertySpec* attributeSpecs, + size_t attributeCount, + Prefable<ConstantSpec>* constants, + jsid* constantIds, + ConstantSpec* constantSpecs, + size_t constantCount) +{ + for (size_t prefIdx = 0; prefIdx < methodCount; ++prefIdx) { + MOZ_ASSERT(methods[prefIdx].specs); + if (methods[prefIdx].enabled) { + // Set i to be the index into our full list of ids/specs that we're + // looking at now. + size_t i = methods[prefIdx].specs - methodSpecs; + for ( ; methodIds[i] != JSID_VOID; ++i) { + if (id == methodIds[i]) { + JSFunction *fun = JS_NewFunctionById(cx, methodSpecs[i].call.op, + methodSpecs[i].nargs, 0, + wrapper, id); + if (!fun) { + return false; + } + SET_JITINFO(fun, methodSpecs[i].call.info); + JSObject *funobj = JS_GetFunctionObject(fun); + desc->value.setObject(*funobj); + desc->attrs = methodSpecs[i].flags; + desc->obj = wrapper; + desc->setter = nullptr; + desc->getter = nullptr; + return true; + } + } + } + } + + for (size_t prefIdx = 0; prefIdx < attributeCount; ++prefIdx) { + MOZ_ASSERT(attributes[prefIdx].specs); + if (attributes[prefIdx].enabled) { + // Set i to be the index into our full list of ids/specs that we're + // looking at now. + size_t i = attributes[prefIdx].specs - attributeSpecs; + for ( ; attributeIds[i] != JSID_VOID; ++i) { + if (id == attributeIds[i]) { + // Because of centralization, we need to make sure we fault in the + // JitInfos as well. At present, until the JSAPI changes, the easiest + // way to do this is wrap them up as functions ourselves. + desc->attrs = attributeSpecs[i].flags & ~JSPROP_NATIVE_ACCESSORS; + // They all have getters, so we can just make it. + JSObject *global = JS_GetGlobalForObject(cx, wrapper); + JSFunction *fun = JS_NewFunction(cx, (JSNative)attributeSpecs[i].getter.op, + 0, 0, global, NULL); + if (!fun) + return false; + SET_JITINFO(fun, attributeSpecs[i].getter.info); + JSObject *funobj = JS_GetFunctionObject(fun); + desc->getter = js::CastAsJSPropertyOp(funobj); + desc->attrs |= JSPROP_GETTER; + if (attributeSpecs[i].setter.op) { + // We have a setter! Make it. + fun = JS_NewFunction(cx, (JSNative)attributeSpecs[i].setter.op, + 1, 0, global, NULL); + if (!fun) + return false; + SET_JITINFO(fun, attributeSpecs[i].setter.info); + funobj = JS_GetFunctionObject(fun); + desc->setter = js::CastAsJSStrictPropertyOp(funobj); + desc->attrs |= JSPROP_SETTER; + } else { + desc->setter = NULL; + } + desc->obj = wrapper; + return true; + } + } + } + } + + for (size_t prefIdx = 0; prefIdx < constantCount; ++prefIdx) { + MOZ_ASSERT(constants[prefIdx].specs); + if (constants[prefIdx].enabled) { + // Set i to be the index into our full list of ids/specs that we're + // looking at now. + size_t i = constants[prefIdx].specs - constantSpecs; + for ( ; constantIds[i] != JSID_VOID; ++i) { + if (id == constantIds[i]) { + desc->attrs = JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT; + desc->obj = wrapper; + desc->value = constantSpecs[i].value; + return true; + } + } + } + } + + return true; +} + +bool +XrayEnumerateProperties(JS::AutoIdVector& props, + Prefable<JSFunctionSpec>* methods, + jsid* methodIds, + JSFunctionSpec* methodSpecs, + size_t methodCount, + Prefable<JSPropertySpec>* attributes, + jsid* attributeIds, + JSPropertySpec* attributeSpecs, + size_t attributeCount, + Prefable<ConstantSpec>* constants, + jsid* constantIds, + ConstantSpec* constantSpecs, + size_t constantCount) +{ + for (size_t prefIdx = 0; prefIdx < methodCount; ++prefIdx) { + MOZ_ASSERT(methods[prefIdx].specs); + if (methods[prefIdx].enabled) { + // Set i to be the index into our full list of ids/specs that we're + // looking at now. + size_t i = methods[prefIdx].specs - methodSpecs; + for ( ; methodIds[i] != JSID_VOID; ++i) { + if ((methodSpecs[i].flags & JSPROP_ENUMERATE) && + !props.append(methodIds[i])) { + return false; + } + } + } + } + + for (size_t prefIdx = 0; prefIdx < attributeCount; ++prefIdx) { + MOZ_ASSERT(attributes[prefIdx].specs); + if (attributes[prefIdx].enabled) { + // Set i to be the index into our full list of ids/specs that we're + // looking at now. + size_t i = attributes[prefIdx].specs - attributeSpecs; + for ( ; attributeIds[i] != JSID_VOID; ++i) { + if ((attributeSpecs[i].flags & JSPROP_ENUMERATE) && + !props.append(attributeIds[i])) { + return false; + } + } + } + } + + for (size_t prefIdx = 0; prefIdx < constantCount; ++prefIdx) { + MOZ_ASSERT(constants[prefIdx].specs); + if (constants[prefIdx].enabled) { + // Set i to be the index into our full list of ids/specs that we're + // looking at now. + size_t i = constants[prefIdx].specs - constantSpecs; + for ( ; constantIds[i] != JSID_VOID; ++i) { + if (!props.append(constantIds[i])) { + return false; + } + } + } + } + + return true; +} + +bool +GetPropertyOnPrototype(JSContext* cx, JSObject* proxy, jsid id, bool* found, + JS::Value* vp) +{ + JSObject* proto; + if (!js::GetObjectProto(cx, proxy, &proto)) { + return false; + } + if (!proto) { + *found = false; + return true; + } + + JSBool hasProp; + if (!JS_HasPropertyById(cx, proto, id, &hasProp)) { + return false; + } + + *found = hasProp; + if (!hasProp || !vp) { + return true; + } + + return JS_ForwardGetPropertyTo(cx, proto, id, proxy, vp); +} + +bool +HasPropertyOnPrototype(JSContext* cx, JSObject* proxy, DOMProxyHandler* handler, + jsid id) +{ + Maybe<JSAutoCompartment> ac; + if (xpc::WrapperFactory::IsXrayWrapper(proxy)) { + proxy = js::UnwrapObject(proxy); + ac.construct(cx, proxy); + } + MOZ_ASSERT(js::IsProxy(proxy) && js::GetProxyHandler(proxy) == handler); + + bool found; + // We ignore an error from GetPropertyOnPrototype. + return !GetPropertyOnPrototype(cx, proxy, id, &found, NULL) || found; +} + +} // namespace dom +} // namespace mozilla diff --git a/components/script/dom/bindings/codegen/BindingUtils.h b/components/script/dom/bindings/codegen/BindingUtils.h new file mode 100644 index 00000000000..ee9d6c3691c --- /dev/null +++ b/components/script/dom/bindings/codegen/BindingUtils.h @@ -0,0 +1,1151 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ +/* vim: set ts=2 sw=2 et tw=79: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_BindingUtils_h__ +#define mozilla_dom_BindingUtils_h__ + +#include "mozilla/dom/DOMJSClass.h" +#include "mozilla/dom/DOMJSProxyHandler.h" +#include "mozilla/dom/workers/Workers.h" +#include "mozilla/ErrorResult.h" + +#include "jsapi.h" +#include "jsfriendapi.h" +#include "jswrapper.h" + +#include "nsIXPConnect.h" +#include "qsObjectHelper.h" +#include "xpcpublic.h" +#include "nsTraceRefcnt.h" +#include "nsWrapperCacheInlines.h" +#include "mozilla/Likely.h" + +// nsGlobalWindow implements nsWrapperCache, but doesn't always use it. Don't +// try to use it without fixing that first. +class nsGlobalWindow; + +namespace mozilla { +namespace dom { + +enum ErrNum { +#define MSG_DEF(_name, _argc, _str) \ + _name, +#include "mozilla/dom/Errors.msg" +#undef MSG_DEF + Err_Limit +}; + +bool +ThrowErrorMessage(JSContext* aCx, const ErrNum aErrorNumber, ...); + +template<bool mainThread> +inline bool +Throw(JSContext* cx, nsresult rv) +{ + using mozilla::dom::workers::exceptions::ThrowDOMExceptionForNSResult; + + // XXX Introduce exception machinery. + if (mainThread) { + xpc::Throw(cx, rv); + } else { + if (!JS_IsExceptionPending(cx)) { + ThrowDOMExceptionForNSResult(cx, rv); + } + } + return false; +} + +template<bool mainThread> +inline bool +ThrowMethodFailedWithDetails(JSContext* cx, const ErrorResult& rv, + const char* /* ifaceName */, + const char* /* memberName */) +{ + return Throw<mainThread>(cx, rv.ErrorCode()); +} + +inline bool +IsDOMClass(const JSClass* clasp) +{ + return clasp->flags & JSCLASS_IS_DOMJSCLASS; +} + +inline bool +IsDOMClass(const js::Class* clasp) +{ + return IsDOMClass(Jsvalify(clasp)); +} + +// It's ok for eRegularDOMObject and eProxyDOMObject to be the same, but +// eNonDOMObject should always be different from the other two. This enum +// shouldn't be used to differentiate between non-proxy and proxy bindings. +enum DOMObjectSlot { + eNonDOMObject = -1, + eRegularDOMObject = DOM_OBJECT_SLOT, + eProxyDOMObject = DOM_PROXY_OBJECT_SLOT +}; + +template <class T> +inline T* +UnwrapDOMObject(JSObject* obj, DOMObjectSlot slot) +{ + MOZ_ASSERT(slot != eNonDOMObject, + "Don't pass non-DOM objects to this function"); + +#ifdef DEBUG + if (IsDOMClass(js::GetObjectClass(obj))) { + MOZ_ASSERT(slot == eRegularDOMObject); + } else { + MOZ_ASSERT(js::IsObjectProxyClass(js::GetObjectClass(obj)) || + js::IsFunctionProxyClass(js::GetObjectClass(obj))); + MOZ_ASSERT(js::GetProxyHandler(obj)->family() == ProxyFamily()); + MOZ_ASSERT(IsNewProxyBinding(js::GetProxyHandler(obj))); + MOZ_ASSERT(slot == eProxyDOMObject); + } +#endif + + JS::Value val = js::GetReservedSlot(obj, slot); + // XXXbz/khuey worker code tries to unwrap interface objects (which have + // nothing here). That needs to stop. + // XXX We don't null-check UnwrapObject's result; aren't we going to crash + // anyway? + if (val.isUndefined()) { + return NULL; + } + + return static_cast<T*>(val.toPrivate()); +} + +// Only use this with a new DOM binding object (either proxy or regular). +inline const DOMClass* +GetDOMClass(JSObject* obj) +{ + js::Class* clasp = js::GetObjectClass(obj); + if (IsDOMClass(clasp)) { + return &DOMJSClass::FromJSClass(clasp)->mClass; + } + + js::BaseProxyHandler* handler = js::GetProxyHandler(obj); + MOZ_ASSERT(handler->family() == ProxyFamily()); + MOZ_ASSERT(IsNewProxyBinding(handler)); + return &static_cast<DOMProxyHandler*>(handler)->mClass; +} + +inline DOMObjectSlot +GetDOMClass(JSObject* obj, const DOMClass*& result) +{ + js::Class* clasp = js::GetObjectClass(obj); + if (IsDOMClass(clasp)) { + result = &DOMJSClass::FromJSClass(clasp)->mClass; + return eRegularDOMObject; + } + + if (js::IsObjectProxyClass(clasp) || js::IsFunctionProxyClass(clasp)) { + js::BaseProxyHandler* handler = js::GetProxyHandler(obj); + if (handler->family() == ProxyFamily() && IsNewProxyBinding(handler)) { + result = &static_cast<DOMProxyHandler*>(handler)->mClass; + return eProxyDOMObject; + } + } + + return eNonDOMObject; +} + +inline bool +UnwrapDOMObjectToISupports(JSObject* obj, nsISupports*& result) +{ + const DOMClass* clasp; + DOMObjectSlot slot = GetDOMClass(obj, clasp); + if (slot == eNonDOMObject || !clasp->mDOMObjectIsISupports) { + return false; + } + + result = UnwrapDOMObject<nsISupports>(obj, slot); + return true; +} + +inline bool +IsDOMObject(JSObject* obj) +{ + js::Class* clasp = js::GetObjectClass(obj); + return IsDOMClass(clasp) || + ((js::IsObjectProxyClass(clasp) || js::IsFunctionProxyClass(clasp)) && + (js::GetProxyHandler(obj)->family() == ProxyFamily() && + IsNewProxyBinding(js::GetProxyHandler(obj)))); +} + +// Some callers don't want to set an exception when unwrapping fails +// (for example, overload resolution uses unwrapping to tell what sort +// of thing it's looking at). +// U must be something that a T* can be assigned to (e.g. T* or an nsRefPtr<T>). +template <prototypes::ID PrototypeID, class T, typename U> +inline nsresult +UnwrapObject(JSContext* cx, JSObject* obj, U& value) +{ + /* First check to see whether we have a DOM object */ + const DOMClass* domClass; + DOMObjectSlot slot = GetDOMClass(obj, domClass); + if (slot == eNonDOMObject) { + /* Maybe we have a security wrapper or outer window? */ + if (!js::IsWrapper(obj)) { + /* Not a DOM object, not a wrapper, just bail */ + return NS_ERROR_XPC_BAD_CONVERT_JS; + } + + obj = xpc::Unwrap(cx, obj, false); + if (!obj) { + return NS_ERROR_XPC_SECURITY_MANAGER_VETO; + } + MOZ_ASSERT(!js::IsWrapper(obj)); + slot = GetDOMClass(obj, domClass); + if (slot == eNonDOMObject) { + /* We don't have a DOM object */ + return NS_ERROR_XPC_BAD_CONVERT_JS; + } + } + + /* This object is a DOM object. Double-check that it is safely + castable to T by checking whether it claims to inherit from the + class identified by protoID. */ + if (domClass->mInterfaceChain[PrototypeTraits<PrototypeID>::Depth] == + PrototypeID) { + value = UnwrapDOMObject<T>(obj, slot); + return NS_OK; + } + + /* It's the wrong sort of DOM object */ + return NS_ERROR_XPC_BAD_CONVERT_JS; +} + +inline bool +IsArrayLike(JSContext* cx, JSObject* obj) +{ + MOZ_ASSERT(obj); + // For simplicity, check for security wrappers up front. In case we + // have a security wrapper, don't forget to enter the compartment of + // the underlying object after unwrapping. + Maybe<JSAutoCompartment> ac; + if (js::IsWrapper(obj)) { + obj = xpc::Unwrap(cx, obj, false); + if (!obj) { + // Let's say it's not + return false; + } + + ac.construct(cx, obj); + } + + // XXXbz need to detect platform objects (including listbinding + // ones) with indexGetters here! + return JS_IsArrayObject(cx, obj) || JS_IsTypedArrayObject(obj, cx); +} + +inline bool +IsPlatformObject(JSContext* cx, JSObject* obj) +{ + // XXXbz Should be treating list-binding objects as platform objects + // too? The one consumer so far wants non-array-like platform + // objects, so listbindings that have an indexGetter should test + // false from here. Maybe this function should have a different + // name? + MOZ_ASSERT(obj); + // Fast-path the common case + JSClass* clasp = js::GetObjectJSClass(obj); + if (IsDOMClass(clasp)) { + return true; + } + // Now for simplicity check for security wrappers before anything else + if (js::IsWrapper(obj)) { + obj = xpc::Unwrap(cx, obj, false); + if (!obj) { + // Let's say it's not + return false; + } + clasp = js::GetObjectJSClass(obj); + } + return IS_WRAPPER_CLASS(js::Valueify(clasp)) || IsDOMClass(clasp) || + JS_IsArrayBufferObject(obj, cx); +} + +// U must be something that a T* can be assigned to (e.g. T* or an nsRefPtr<T>). +template <class T, typename U> +inline nsresult +UnwrapObject(JSContext* cx, JSObject* obj, U& value) +{ + return UnwrapObject<static_cast<prototypes::ID>( + PrototypeIDMap<T>::PrototypeID), T>(cx, obj, value); +} + +const size_t kProtoOrIfaceCacheCount = + prototypes::id::_ID_Count + constructors::id::_ID_Count; + +inline void +AllocateProtoOrIfaceCache(JSObject* obj) +{ + MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL); + MOZ_ASSERT(js::GetReservedSlot(obj, DOM_PROTOTYPE_SLOT).isUndefined()); + + // Important: The () at the end ensure zero-initialization + JSObject** protoOrIfaceArray = new JSObject*[kProtoOrIfaceCacheCount](); + + js::SetReservedSlot(obj, DOM_PROTOTYPE_SLOT, + JS::PrivateValue(protoOrIfaceArray)); +} + +inline void +TraceProtoOrIfaceCache(JSTracer* trc, JSObject* obj) +{ + MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL); + + if (!HasProtoOrIfaceArray(obj)) + return; + JSObject** protoOrIfaceArray = GetProtoOrIfaceArray(obj); + for (size_t i = 0; i < kProtoOrIfaceCacheCount; ++i) { + JSObject* proto = protoOrIfaceArray[i]; + if (proto) { + JS_CALL_OBJECT_TRACER(trc, proto, "protoOrIfaceArray[i]"); + } + } +} + +inline void +DestroyProtoOrIfaceCache(JSObject* obj) +{ + MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL); + + JSObject** protoOrIfaceArray = GetProtoOrIfaceArray(obj); + + delete [] protoOrIfaceArray; +} + +struct ConstantSpec +{ + const char* name; + JS::Value value; +}; + +/** + * Add constants to an object. + */ +bool +DefineConstants(JSContext* cx, JSObject* obj, ConstantSpec* cs); + +template<typename T> +struct Prefable { + // A boolean indicating whether this set of specs is enabled + bool enabled; + // 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. + T* specs; +}; + +/* + * Create a DOM interface object (if constructorClass is non-null) and/or a + * DOM interface prototype object (if protoClass is non-null). + * + * global is used as the parent of the interface object and the interface + * prototype object + * receiver is the object on which we need to define the interface object as a + * property + * protoProto is the prototype to use for the interface prototype object. + * protoClass is the JSClass to use for the interface prototype object. + * This is null if we should not create an interface prototype + * object. + * constructorClass is the JSClass to use for the interface object. + * This is null if we should not create an interface object or + * if it should be a function object. + * constructor is the JSNative to use as a constructor. If this is non-null, it + * should be used as a JSNative to back the interface object, which + * should be a Function. If this is null, then we should create an + * object of constructorClass, unless that's also null, in which + * case we should not create an interface object at all. + * ctorNargs is the length of the constructor function; 0 if no constructor + * instanceClass is the JSClass of instance objects for this class. This can + * be null if this is not a concrete proto. + * methods and properties are to be defined on the interface prototype object; + * these arguments are allowed to be null if there are no + * methods or properties respectively. + * constants are to be defined on the interface object and on the interface + * prototype object; allowed to be null if there are no constants. + * staticMethods are to be defined on the interface object; allowed to be null + * if there are no static methods. + * + * At least one of protoClass and constructorClass should be non-null. + * If constructorClass is non-null, the resulting interface object will be + * defined on the given global with property name |name|, which must also be + * non-null. + * + * returns the interface prototype object if protoClass is non-null, else it + * returns the interface object. + */ +JSObject* +CreateInterfaceObjects(JSContext* cx, JSObject* global, JSObject* receiver, + JSObject* protoProto, JSClass* protoClass, + JSClass* constructorClass, JSNative constructor, + unsigned ctorNargs, const DOMClass* domClass, + Prefable<JSFunctionSpec>* methods, + Prefable<JSPropertySpec>* properties, + Prefable<ConstantSpec>* constants, + Prefable<JSFunctionSpec>* staticMethods, const char* name); + +template <class T> +inline bool +WrapNewBindingObject(JSContext* cx, JSObject* scope, T* value, JS::Value* vp) +{ + JSObject* obj = value->GetWrapper(); + if (obj && js::GetObjectCompartment(obj) == js::GetObjectCompartment(scope)) { + *vp = JS::ObjectValue(*obj); + return true; + } + + if (!obj) { + bool triedToWrap; + obj = value->WrapObject(cx, scope, &triedToWrap); + if (!obj) { + // At this point, obj is null, so just return false. We could + // try to communicate triedToWrap to the caller, but in practice + // callers seem to be testing JS_IsExceptionPending(cx) to + // figure out whether WrapObject() threw instead. + return false; + } + } + + // When called via XrayWrapper, we end up here while running in the + // chrome compartment. But the obj we have would be created in + // whatever the content compartment is. So at this point we need to + // make sure it's correctly wrapped for the compartment of |scope|. + // cx should already be in the compartment of |scope| here. + MOZ_ASSERT(js::IsObjectInContextCompartment(scope, cx)); + *vp = JS::ObjectValue(*obj); + return JS_WrapValue(cx, vp); +} + +// Helper for smart pointers (nsAutoPtr/nsRefPtr/nsCOMPtr). +template <template <typename> class SmartPtr, class T> +inline bool +WrapNewBindingObject(JSContext* cx, JSObject* scope, const SmartPtr<T>& value, + JS::Value* vp) +{ + return WrapNewBindingObject(cx, scope, value.get(), vp); +} + +template <class T> +inline bool +WrapNewBindingNonWrapperCachedObject(JSContext* cx, JSObject* scope, T* value, + JS::Value* vp) +{ + // We try to wrap in the compartment of the underlying object of "scope" + JSObject* obj; + { + // scope for the JSAutoCompartment so that we restore the compartment + // before we call JS_WrapValue. + Maybe<JSAutoCompartment> ac; + if (js::IsWrapper(scope)) { + scope = xpc::Unwrap(cx, scope, false); + if (!scope) + return false; + ac.construct(cx, scope); + } + + obj = value->WrapObject(cx, scope); + } + + // We can end up here in all sorts of compartments, per above. Make + // sure to JS_WrapValue! + *vp = JS::ObjectValue(*obj); + return JS_WrapValue(cx, vp); +} + +// Helper for smart pointers (nsAutoPtr/nsRefPtr/nsCOMPtr). +template <template <typename> class SmartPtr, typename T> +inline bool +WrapNewBindingNonWrapperCachedObject(JSContext* cx, JSObject* scope, + const SmartPtr<T>& value, JS::Value* vp) +{ + return WrapNewBindingNonWrapperCachedObject(cx, scope, value.get(), vp); +} + +/** + * A method to handle new-binding wrap failure, by possibly falling back to + * wrapping as a non-new-binding object. + */ +bool +DoHandleNewBindingWrappingFailure(JSContext* cx, JSObject* scope, + nsISupports* value, JS::Value* vp); + +/** + * An easy way to call the above when you have a value which + * multiply-inherits from nsISupports. + */ +template <class T> +bool +HandleNewBindingWrappingFailure(JSContext* cx, JSObject* scope, T* value, + JS::Value* vp) +{ + nsCOMPtr<nsISupports> val; + CallQueryInterface(value, getter_AddRefs(val)); + return DoHandleNewBindingWrappingFailure(cx, scope, val, vp); +} + +// Helper for smart pointers (nsAutoPtr/nsRefPtr/nsCOMPtr). +template <template <typename> class SmartPtr, class T> +MOZ_ALWAYS_INLINE bool +HandleNewBindingWrappingFailure(JSContext* cx, JSObject* scope, + const SmartPtr<T>& value, JS::Value* vp) +{ + return HandleNewBindingWrappingFailure(cx, scope, value.get(), vp); +} + +struct EnumEntry { + const char* value; + size_t length; +}; + +template<bool Fatal> +inline bool +EnumValueNotFound(JSContext* cx, const jschar* chars, size_t length, + const char* type) +{ + return false; +} + +template<> +inline bool +EnumValueNotFound<false>(JSContext* cx, const jschar* chars, size_t length, + const char* type) +{ + // TODO: Log a warning to the console. + return true; +} + +template<> +inline bool +EnumValueNotFound<true>(JSContext* cx, const jschar* chars, size_t length, + const char* type) +{ + NS_LossyConvertUTF16toASCII deflated(static_cast<const PRUnichar*>(chars), + length); + return ThrowErrorMessage(cx, MSG_INVALID_ENUM_VALUE, deflated.get(), type); +} + + +template<bool InvalidValueFatal> +inline int +FindEnumStringIndex(JSContext* cx, JS::Value v, const EnumEntry* values, + const char* type, bool* ok) +{ + // JS_StringEqualsAscii is slow as molasses, so don't use it here. + JSString* str = JS_ValueToString(cx, v); + if (!str) { + *ok = false; + return 0; + } + JS::Anchor<JSString*> anchor(str); + size_t length; + const jschar* chars = JS_GetStringCharsAndLength(cx, str, &length); + if (!chars) { + *ok = false; + return 0; + } + int i = 0; + for (const EnumEntry* value = values; value->value; ++value, ++i) { + if (length != value->length) { + continue; + } + + bool equal = true; + const char* val = value->value; + for (size_t j = 0; j != length; ++j) { + if (unsigned(val[j]) != unsigned(chars[j])) { + equal = false; + break; + } + } + + if (equal) { + *ok = true; + return i; + } + } + + *ok = EnumValueNotFound<InvalidValueFatal>(cx, chars, length, type); + return -1; +} + +inline nsWrapperCache* +GetWrapperCache(nsWrapperCache* cache) +{ + return cache; +} + +inline nsWrapperCache* +GetWrapperCache(nsGlobalWindow* not_allowed); + +inline nsWrapperCache* +GetWrapperCache(void* p) +{ + return NULL; +} + +struct ParentObject { + template<class T> + ParentObject(T* aObject) : + mObject(aObject), + mWrapperCache(GetWrapperCache(aObject)) + {} + + template<class T, template<typename> class SmartPtr> + ParentObject(const SmartPtr<T>& aObject) : + mObject(aObject.get()), + mWrapperCache(GetWrapperCache(aObject.get())) + {} + + ParentObject(nsISupports* aObject, nsWrapperCache* aCache) : + mObject(aObject), + mWrapperCache(aCache) + {} + + nsISupports* const mObject; + nsWrapperCache* const mWrapperCache; +}; + +inline nsWrapperCache* +GetWrapperCache(const ParentObject& aParentObject) +{ + return aParentObject.mWrapperCache; +} + +template<class T> +inline nsISupports* +GetParentPointer(T* aObject) +{ + return ToSupports(aObject); +} + +inline nsISupports* +GetParentPointer(const ParentObject& aObject) +{ + return ToSupports(aObject.mObject); +} + +template<class T> +inline void +ClearWrapper(T* p, nsWrapperCache* cache) +{ + cache->ClearWrapper(); +} + +template<class T> +inline void +ClearWrapper(T* p, void*) +{ + nsWrapperCache* cache; + CallQueryInterface(p, &cache); + ClearWrapper(p, cache); +} + +// Can only be called with the immediate prototype of the instance object. Can +// only be called on the prototype of an object known to be a DOM instance. +JSBool +InstanceClassHasProtoAtDepth(JSHandleObject protoObject, uint32_t protoID, + uint32_t depth); + +// Only set allowNativeWrapper to false if you really know you need it, if in +// doubt use true. Setting it to false disables security wrappers. +bool +XPCOMObjectToJsval(JSContext* cx, JSObject* scope, xpcObjectHelper &helper, + const nsIID* iid, bool allowNativeWrapper, JS::Value* rval); + +template<class T> +inline bool +WrapObject(JSContext* cx, JSObject* scope, T* p, nsWrapperCache* cache, + const nsIID* iid, JS::Value* vp) +{ + if (xpc_FastGetCachedWrapper(cache, scope, vp)) + return true; + qsObjectHelper helper(p, cache); + return XPCOMObjectToJsval(cx, scope, helper, iid, true, vp); +} + +template<class T> +inline bool +WrapObject(JSContext* cx, JSObject* scope, T* p, const nsIID* iid, + JS::Value* vp) +{ + return WrapObject(cx, scope, p, GetWrapperCache(p), iid, vp); +} + +template<class T> +inline bool +WrapObject(JSContext* cx, JSObject* scope, T* p, JS::Value* vp) +{ + return WrapObject(cx, scope, p, NULL, vp); +} + +template<class T> +inline bool +WrapObject(JSContext* cx, JSObject* scope, nsCOMPtr<T> &p, const nsIID* iid, + JS::Value* vp) +{ + return WrapObject(cx, scope, p.get(), iid, vp); +} + +template<class T> +inline bool +WrapObject(JSContext* cx, JSObject* scope, nsCOMPtr<T> &p, JS::Value* vp) +{ + return WrapObject(cx, scope, p, NULL, vp); +} + +template<class T> +inline bool +WrapObject(JSContext* cx, JSObject* scope, nsRefPtr<T> &p, const nsIID* iid, + JS::Value* vp) +{ + return WrapObject(cx, scope, p.get(), iid, vp); +} + +template<class T> +inline bool +WrapObject(JSContext* cx, JSObject* scope, nsRefPtr<T> &p, JS::Value* vp) +{ + return WrapObject(cx, scope, p, NULL, vp); +} + +template<> +inline bool +WrapObject<JSObject>(JSContext* cx, JSObject* scope, JSObject* p, JS::Value* vp) +{ + vp->setObjectOrNull(p); + return true; +} + +template<typename T> +static inline JSObject* +WrapNativeParent(JSContext* cx, JSObject* scope, const T& p) +{ + if (!GetParentPointer(p)) + return scope; + + nsWrapperCache* cache = GetWrapperCache(p); + JSObject* obj; + if (cache && (obj = cache->GetWrapper())) { +#ifdef DEBUG + qsObjectHelper helper(GetParentPointer(p), cache); + JS::Value debugVal; + + bool ok = XPCOMObjectToJsval(cx, scope, helper, NULL, false, &debugVal); + NS_ASSERTION(ok && JSVAL_TO_OBJECT(debugVal) == obj, + "Unexpected object in nsWrapperCache"); +#endif + return obj; + } + + qsObjectHelper helper(GetParentPointer(p), cache); + JS::Value v; + return XPCOMObjectToJsval(cx, scope, helper, NULL, false, &v) ? + JSVAL_TO_OBJECT(v) : + NULL; +} + +static inline bool +InternJSString(JSContext* cx, jsid& id, const char* chars) +{ + if (JSString *str = ::JS_InternString(cx, chars)) { + id = INTERNED_STRING_TO_JSID(cx, str); + return true; + } + return false; +} + +// Spec needs a name property +template <typename Spec> +static bool +InitIds(JSContext* cx, Prefable<Spec>* prefableSpecs, jsid* ids) +{ + MOZ_ASSERT(prefableSpecs); + MOZ_ASSERT(prefableSpecs->specs); + do { + // We ignore whether the set of ids is enabled and just intern all the IDs, + // because this is only done once per application runtime. + Spec* spec = prefableSpecs->specs; + do { + if (!InternJSString(cx, *ids, spec->name)) { + return false; + } + } while (++ids, (++spec)->name); + + // We ran out of ids for that pref. Put a JSID_VOID in on the id + // corresponding to the list terminator for the pref. + *ids = JSID_VOID; + ++ids; + } while ((++prefableSpecs)->specs); + + return true; +} + +JSBool +QueryInterface(JSContext* cx, unsigned argc, JS::Value* vp); +JSBool +ThrowingConstructor(JSContext* cx, unsigned argc, JS::Value* vp); + +bool +GetPropertyOnPrototype(JSContext* cx, JSObject* proxy, jsid id, bool* found, + JS::Value* vp); + +bool +HasPropertyOnPrototype(JSContext* cx, JSObject* proxy, DOMProxyHandler* handler, + jsid id); + +template<class T> +class NonNull +{ +public: + NonNull() +#ifdef DEBUG + : inited(false) +#endif + {} + + operator T&() { + MOZ_ASSERT(inited); + MOZ_ASSERT(ptr, "NonNull<T> was set to null"); + return *ptr; + } + + operator const 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; + } + +protected: + T* ptr; +#ifdef DEBUG + bool inited; +#endif +}; + +template<class T> +class OwningNonNull +{ +public: + OwningNonNull() +#ifdef DEBUG + : inited(false) +#endif + {} + + operator T&() { + MOZ_ASSERT(inited); + MOZ_ASSERT(ptr, "OwningNonNull<T> was set to null"); + return *ptr; + } + + void operator=(T* t) { + init(t); + } + + void operator=(const already_AddRefed<T>& t) { + init(t); + } + +protected: + template<typename U> + void init(U t) { + ptr = t; + MOZ_ASSERT(ptr); +#ifdef DEBUG + inited = true; +#endif + } + + nsRefPtr<T> ptr; +#ifdef DEBUG + bool inited; +#endif +}; + +// A struct that has the same layout as an nsDependentString but much +// faster constructor and destructor behavior +struct FakeDependentString { + FakeDependentString() : + mFlags(nsDependentString::F_TERMINATED) + { + } + + void SetData(const nsDependentString::char_type* aData, + nsDependentString::size_type aLength) { + MOZ_ASSERT(mFlags == nsDependentString::F_TERMINATED); + mData = aData; + mLength = aLength; + } + + void Truncate() { + mData = nsDependentString::char_traits::sEmptyBuffer; + mLength = 0; + } + + void SetNull() { + Truncate(); + mFlags |= nsDependentString::F_VOIDED; + } + + const nsAString* ToAStringPtr() const { + return reinterpret_cast<const nsDependentString*>(this); + } + + nsAString* ToAStringPtr() { + return reinterpret_cast<nsDependentString*>(this); + } + + operator const nsAString& () const { + return *reinterpret_cast<const nsDependentString*>(this); + } + +private: + const nsDependentString::char_type* mData; + nsDependentString::size_type mLength; + uint32_t mFlags; + + // A class to use for our static asserts to ensure our object layout + // matches that of nsDependentString. + class DependentStringAsserter; + friend class DependentStringAsserter; + + class DepedentStringAsserter : public nsDependentString { + public: + static void StaticAsserts() { + MOZ_STATIC_ASSERT(sizeof(FakeDependentString) == sizeof(nsDependentString), + "Must have right object size"); + MOZ_STATIC_ASSERT(offsetof(FakeDependentString, mData) == + offsetof(DepedentStringAsserter, mData), + "Offset of mData should match"); + MOZ_STATIC_ASSERT(offsetof(FakeDependentString, mLength) == + offsetof(DepedentStringAsserter, mLength), + "Offset of mLength should match"); + MOZ_STATIC_ASSERT(offsetof(FakeDependentString, mFlags) == + offsetof(DepedentStringAsserter, mFlags), + "Offset of mFlags should match"); + } + }; +}; + +enum StringificationBehavior { + eStringify, + eEmpty, + eNull +}; + +// pval must not be null and must point to a rooted JS::Value +static inline bool +ConvertJSValueToString(JSContext* cx, const JS::Value& v, JS::Value* pval, + StringificationBehavior nullBehavior, + StringificationBehavior undefinedBehavior, + FakeDependentString& result) +{ + JSString *s; + if (v.isString()) { + s = v.toString(); + } else { + StringificationBehavior behavior; + if (v.isNull()) { + behavior = nullBehavior; + } else if (v.isUndefined()) { + behavior = undefinedBehavior; + } else { + behavior = eStringify; + } + + if (behavior != eStringify) { + if (behavior == eEmpty) { + result.Truncate(); + } else { + result.SetNull(); + } + return true; + } + + s = JS_ValueToString(cx, v); + if (!s) { + return false; + } + pval->setString(s); // Root the new string. + } + + size_t len; + const jschar *chars = JS_GetStringCharsZAndLength(cx, s, &len); + if (!chars) { + return false; + } + + result.SetData(chars, len); + return true; +} + +// Class for representing optional arguments. +template<typename T> +class Optional { +public: + Optional() {} + + bool WasPassed() const { + return !mImpl.empty(); + } + + void Construct() { + mImpl.construct(); + } + + template <class T1, class T2> + void Construct(const T1 &t1, const T2 &t2) { + mImpl.construct(t1, t2); + } + + const T& Value() const { + return mImpl.ref(); + } + + T& Value() { + return mImpl.ref(); + } + +private: + // Forbid copy-construction and assignment + Optional(const Optional& other) MOZ_DELETE; + const Optional &operator=(const Optional &other) MOZ_DELETE; + + Maybe<T> mImpl; +}; + +// Specialization for strings. +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; + } + + void operator=(const FakeDependentString* str) { + MOZ_ASSERT(str); + mStr = str->ToAStringPtr(); + mPassed = true; + } + + const nsAString& Value() const { + MOZ_ASSERT(WasPassed()); + return *mStr; + } + +private: + // Forbid copy-construction and assignment + Optional(const Optional& other) MOZ_DELETE; + const Optional &operator=(const Optional &other) MOZ_DELETE; + + bool mPassed; + const nsAString* mStr; +}; + +// Class for representing sequences in arguments. We use an auto array that can +// hold 16 elements, to avoid having to allocate in common cases. 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 AutoFallibleTArray<T, 16> +{ +public: + Sequence() : AutoFallibleTArray<T, 16>() {} +}; + +// Class for holding the type of members of a union. The union type has an enum +// to keep track of which of its UnionMembers has been constructed. +template<class T> +class UnionMember { + AlignedStorage2<T> storage; + +public: + T& SetValue() { + new (storage.addr()) T(); + return *storage.addr(); + } + const T& Value() const { + return *storage.addr(); + } + void Destroy() { + storage.addr()->~T(); + } +}; + +// Implementation of the bits that XrayWrapper needs +bool +XrayResolveProperty(JSContext* cx, JSObject* wrapper, jsid id, + JSPropertyDescriptor* desc, + // And the things we need to determine the descriptor + Prefable<JSFunctionSpec>* methods, + jsid* methodIds, + JSFunctionSpec* methodSpecs, + size_t methodCount, + Prefable<JSPropertySpec>* attributes, + jsid* attributeIds, + JSPropertySpec* attributeSpecs, + size_t attributeCount, + Prefable<ConstantSpec>* constants, + jsid* constantIds, + ConstantSpec* constantSpecs, + size_t constantCount); + +bool +XrayEnumerateProperties(JS::AutoIdVector& props, + Prefable<JSFunctionSpec>* methods, + jsid* methodIds, + JSFunctionSpec* methodSpecs, + size_t methodCount, + Prefable<JSPropertySpec>* attributes, + jsid* attributeIds, + JSPropertySpec* attributeSpecs, + size_t attributeCount, + Prefable<ConstantSpec>* constants, + jsid* constantIds, + ConstantSpec* constantSpecs, + size_t constantCount); + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_BindingUtils_h__ */ diff --git a/components/script/dom/bindings/codegen/Bindings.conf b/components/script/dom/bindings/codegen/Bindings.conf new file mode 100644 index 00000000000..f8119bc71f5 --- /dev/null +++ b/components/script/dom/bindings/codegen/Bindings.conf @@ -0,0 +1,28 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# DOM Bindings Configuration. +# +# The WebIDL interfaces are defined in dom/webidls. For each such interface, +# there is a corresponding entry in the configuration table below. +# The configuration table maps each interface name to a |descriptor|. +# +# Valid fields for all descriptors: +# * createGlobal: True for global objects. +# * outerObjectHook: string to use in place of default value for outerObject and thisObject +# JS class hooks + +DOMInterfaces = { + +'EventListener': { + 'nativeType': 'EventListenerBinding::EventListener', +}, +'Window': { + 'outerObjectHook': 'Some(bindings::utils::outerize_global)', +}, + +#FIXME(jdm): This should be 'register': False, but then we don't generate enum types +'TestBinding': {}, + +} diff --git a/components/script/dom/bindings/codegen/Codegen.py b/components/script/dom/bindings/codegen/Codegen.py new file mode 100644 index 00000000000..6d2cc0bde36 --- /dev/null +++ b/components/script/dom/bindings/codegen/Codegen.py @@ -0,0 +1,5788 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# Common codegen classes. + +import os +import string +import operator + +from WebIDL import * +from Configuration import NoSuchDescriptorError + +AUTOGENERATED_WARNING_COMMENT = \ + "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n" +ADDPROPERTY_HOOK_NAME = '_addProperty' +FINALIZE_HOOK_NAME = '_finalize' +TRACE_HOOK_NAME = '_trace' +CONSTRUCT_HOOK_NAME = '_constructor' +HASINSTANCE_HOOK_NAME = '_hasInstance' + +def replaceFileIfChanged(filename, newContents): + """ + Read a copy of the old file, so that we don't touch it if it hasn't changed. + Returns True if the file was updated, false otherwise. + """ + oldFileContents = "" + try: + oldFile = open(filename, 'rb') + oldFileContents = ''.join(oldFile.readlines()) + oldFile.close() + except: + pass + + if newContents == oldFileContents: + return False + + f = open(filename, 'wb') + f.write(newContents) + f.close() + +def toStringBool(arg): + return str(not not arg).lower() + +def toBindingNamespace(arg): + return re.sub("((_workers)?$)", "Binding\\1", arg); + +class CGThing(): + """ + Abstract base class for things that spit out code. + """ + def __init__(self): + pass # Nothing for now + def declare(self): + """Produce code for a header file.""" + assert(False) # Override me! + def define(self): + """Produce code for a cpp file.""" + assert(False) # Override me! + +class CGNativePropertyHooks(CGThing): + """ + Generate a NativePropertyHooks for a given descriptor + """ + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + def declare(self): + if self.descriptor.workers: + return "" + return "extern const NativePropertyHooks NativeHooks;\n" + def define(self): + if self.descriptor.workers: + return "" + if self.descriptor.concrete and self.descriptor.proxy: + resolveOwnProperty = "ResolveOwnProperty" + enumerateOwnProperties = "EnumerateOwnProperties" + else: + enumerateOwnProperties = resolveOwnProperty = "NULL" + parent = self.descriptor.interface.parent + parentHooks = ("&" + toBindingNamespace(parent.identifier.name) + "::NativeHooks" + if parent else 'NULL') + return """ +const NativePropertyHooks NativeHooks = { %s, ResolveProperty, %s, EnumerateProperties, %s }; +""" % (resolveOwnProperty, enumerateOwnProperties, parentHooks) + +def DOMClass(descriptor): + protoList = ['prototypes::id::' + proto for proto in descriptor.prototypeChain] + # Pad out the list to the right length with _ID_Count so we + # guarantee that all the lists are the same length. _ID_Count + # is never the ID of any prototype, so it's safe to use as + # padding. + protoList.extend(['prototypes::id::_ID_Count'] * (descriptor.config.maxProtoChainLength - len(protoList))) + prototypeChainString = ', '.join(protoList) + nativeHooks = "NULL" if descriptor.workers else "&NativeHooks" + return """{ + { %s }, + %s, %s +}""" % (prototypeChainString, toStringBool(descriptor.nativeIsISupports), + nativeHooks) + +class CGDOMJSClass(CGThing): + """ + Generate a DOMJSClass for a given descriptor + """ + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + def declare(self): + return "extern DOMJSClass Class;\n" + def define(self): + traceHook = TRACE_HOOK_NAME if self.descriptor.customTrace else 'NULL' + return """ +DOMJSClass Class = { + { "%s", + JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(1), + %s, /* addProperty */ + JS_PropertyStub, /* delProperty */ + JS_PropertyStub, /* getProperty */ + JS_StrictPropertyStub, /* setProperty */ + JS_EnumerateStub, + JS_ResolveStub, + JS_ConvertStub, + %s, /* finalize */ + NULL, /* checkAccess */ + NULL, /* call */ + NULL, /* hasInstance */ + NULL, /* construct */ + %s, /* trace */ + JSCLASS_NO_INTERNAL_MEMBERS + }, + %s +}; +""" % (self.descriptor.interface.identifier.name, + ADDPROPERTY_HOOK_NAME if self.descriptor.concrete and not self.descriptor.workers and self.descriptor.wrapperCache else 'JS_PropertyStub', + FINALIZE_HOOK_NAME, traceHook, + CGIndenter(CGGeneric(DOMClass(self.descriptor))).define()) + +class CGPrototypeJSClass(CGThing): + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + def declare(self): + # We're purely for internal consumption + return "" + def define(self): + return """static JSClass PrototypeClass = { + "%sPrototype", + JSCLASS_HAS_RESERVED_SLOTS(1), + JS_PropertyStub, /* addProperty */ + JS_PropertyStub, /* delProperty */ + JS_PropertyStub, /* getProperty */ + JS_StrictPropertyStub, /* setProperty */ + JS_EnumerateStub, + JS_ResolveStub, + JS_ConvertStub, + NULL, /* finalize */ + NULL, /* checkAccess */ + NULL, /* call */ + NULL, /* hasInstance */ + NULL, /* construct */ + NULL, /* trace */ + JSCLASS_NO_INTERNAL_MEMBERS +}; +""" % (self.descriptor.interface.identifier.name) + +class CGInterfaceObjectJSClass(CGThing): + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + def declare(self): + # We're purely for internal consumption + return "" + def define(self): + if not self.descriptor.hasInstanceInterface: + return "" + ctorname = "NULL" if not self.descriptor.interface.ctor() else CONSTRUCT_HOOK_NAME + hasinstance = HASINSTANCE_HOOK_NAME + return """ +static JSClass InterfaceObjectClass = { + "Function", 0, + JS_PropertyStub, /* addProperty */ + JS_PropertyStub, /* delProperty */ + JS_PropertyStub, /* getProperty */ + JS_StrictPropertyStub, /* setProperty */ + JS_EnumerateStub, + JS_ResolveStub, + JS_ConvertStub, + NULL, /* finalize */ + NULL, /* checkAccess */ + %s, /* call */ + %s, /* hasInstance */ + %s, /* construct */ + NULL, /* trace */ + JSCLASS_NO_INTERNAL_MEMBERS +}; +""" % (ctorname, hasinstance, ctorname) + +class CGList(CGThing): + """ + Generate code for a list of GCThings. Just concatenates them together, with + an optional joiner string. "\n" is a common joiner. + """ + def __init__(self, children, joiner=""): + CGThing.__init__(self) + self.children = children + self.joiner = joiner + def append(self, child): + self.children.append(child) + def prepend(self, child): + self.children.insert(0, child) + def join(self, generator): + return self.joiner.join(filter(lambda s: len(s) > 0, (child for child in generator))) + def declare(self): + return self.join(child.declare() for child in self.children if child is not None) + def define(self): + return self.join(child.define() for child in self.children if child is not None) + +class CGGeneric(CGThing): + """ + A class that spits out a fixed string into the codegen. Can spit out a + separate string for the declaration too. + """ + def __init__(self, define="", declare=""): + self.declareText = declare + self.defineText = define + def declare(self): + return self.declareText + def define(self): + return self.defineText + +# We'll want to insert the indent at the beginnings of lines, but we +# don't want to indent empty lines. So only indent lines that have a +# non-newline character on them. +lineStartDetector = re.compile("^(?=[^\n#])", re.MULTILINE) +class CGIndenter(CGThing): + """ + A class that takes another CGThing and generates code that indents that + CGThing by some number of spaces. The default indent is two spaces. + """ + def __init__(self, child, indentLevel=2, declareOnly=False): + CGThing.__init__(self) + self.child = child + self.indent = " " * indentLevel + self.declareOnly = declareOnly + def declare(self): + decl = self.child.declare() + if decl is not "": + return re.sub(lineStartDetector, self.indent, decl) + else: + return "" + def define(self): + defn = self.child.define() + if defn is not "" and not self.declareOnly: + return re.sub(lineStartDetector, self.indent, defn) + else: + return defn + +class CGWrapper(CGThing): + """ + Generic CGThing that wraps other CGThings with pre and post text. + """ + def __init__(self, child, pre="", post="", declarePre=None, + declarePost=None, definePre=None, definePost=None, + declareOnly=False, defineOnly=False, reindent=False): + CGThing.__init__(self) + self.child = child + self.declarePre = declarePre or pre + self.declarePost = declarePost or post + self.definePre = definePre or pre + self.definePost = definePost or post + self.declareOnly = declareOnly + self.defineOnly = defineOnly + self.reindent = reindent + def declare(self): + if self.defineOnly: + return '' + decl = self.child.declare() + if self.reindent: + # We don't use lineStartDetector because we don't want to + # insert whitespace at the beginning of our _first_ line. + decl = stripTrailingWhitespace( + decl.replace("\n", "\n" + (" " * len(self.declarePre)))) + return self.declarePre + decl + self.declarePost + def define(self): + if self.declareOnly: + return '' + defn = self.child.define() + if self.reindent: + # We don't use lineStartDetector because we don't want to + # insert whitespace at the beginning of our _first_ line. + defn = stripTrailingWhitespace( + defn.replace("\n", "\n" + (" " * len(self.definePre)))) + return self.definePre + defn + self.definePost + +class CGIfWrapper(CGWrapper): + def __init__(self, child, condition): + pre = CGWrapper(CGGeneric(condition), pre="if (", post=") {\n", + reindent=True) + CGWrapper.__init__(self, CGIndenter(child), pre=pre.define(), + post="\n}") + +class CGNamespace(CGWrapper): + def __init__(self, namespace, child, declareOnly=False): + pre = "namespace %s {\n" % namespace + post = "} // namespace %s\n" % namespace + CGWrapper.__init__(self, child, pre=pre, post=post, + declareOnly=declareOnly) + @staticmethod + def build(namespaces, child, declareOnly=False): + """ + Static helper method to build multiple wrapped namespaces. + """ + if not namespaces: + return CGWrapper(child, declareOnly=declareOnly) + inner = CGNamespace.build(namespaces[1:], child, declareOnly=declareOnly) + return CGNamespace(namespaces[0], inner, declareOnly=declareOnly) + +class CGIncludeGuard(CGWrapper): + """ + Generates include guards for a header. + """ + def __init__(self, prefix, child): + """|prefix| is the filename without the extension.""" + define = 'mozilla_dom_%s_h__' % prefix + CGWrapper.__init__(self, child, + declarePre='#ifndef %s\n#define %s\n\n' % (define, define), + declarePost='\n#endif // %s\n' % define) + +def getTypes(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()) + 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()) + return types + +class CGHeaders(CGWrapper): + """ + Generates the appropriate include statements. + """ + def __init__(self, descriptors, dictionaries, declareIncludes, + defineIncludes, child): + """ + Builds a set of includes to cover |descriptors|. + + Also includes the files in |declareIncludes| in the header + file and the files in |defineIncludes| in the .cpp. + """ + + # Determine the filenames for which we need headers. + interfaceDeps = [d.interface for d in descriptors] + ancestors = [] + for iface in interfaceDeps: + while iface.parent: + ancestors.append(iface.parent) + iface = iface.parent + interfaceDeps.extend(ancestors) + bindingIncludes = set(self.getDeclarationFilename(d) for d in interfaceDeps) + + # Grab all the implementation declaration files we need. + implementationIncludes = set(d.headerFile for d in descriptors) + + # Now find all the things we'll need as arguments because we + # need to wrap or unwrap them. + bindingHeaders = set() + for d in descriptors: + types = getTypes(d) + for dictionary in dictionaries: + curDict = dictionary + while curDict: + types.extend([m.type for m in curDict.members]) + curDict = curDict.parent + + for t in types: + if t.unroll().isUnion(): + # UnionConversions.h includes UnionTypes.h + bindingHeaders.add("mozilla/dom/UnionConversions.h") + elif t.unroll().isInterface(): + if t.unroll().isSpiderMonkeyInterface(): + bindingHeaders.add("jsfriendapi.h") + bindingHeaders.add("mozilla/dom/TypedArray.h") + else: + typeDesc = d.getDescriptor(t.unroll().inner.identifier.name) + if typeDesc is not None: + implementationIncludes.add(typeDesc.headerFile) + bindingHeaders.add(self.getDeclarationFilename(typeDesc.interface)) + elif t.unroll().isDictionary(): + bindingHeaders.add(self.getDeclarationFilename(t.unroll().inner)) + + declareIncludes = set(declareIncludes) + for d in dictionaries: + if d.parent: + declareIncludes.add(self.getDeclarationFilename(d.parent)) + bindingHeaders.add(self.getDeclarationFilename(d)) + + # Let the machinery do its thing. + def _includeString(includes): + return ''.join(['#include "%s"\n' % i for i in includes]) + '\n' + CGWrapper.__init__(self, child, + declarePre=_includeString(sorted(declareIncludes)), + definePre=_includeString(sorted(set(defineIncludes) | + bindingIncludes | + bindingHeaders | + implementationIncludes))) + @staticmethod + def getDeclarationFilename(decl): + # Use our local version of the header, not the exported one, so that + # test bindings, which don't export, will work correctly. + basename = os.path.basename(decl.filename()) + return basename.replace('.webidl', 'Binding.h') + +def SortedTuples(l): + """ + Sort a list of tuples based on the first item in the tuple + """ + return sorted(l, key=operator.itemgetter(0)) + +def SortedDictValues(d): + """ + Returns a list of values from the dict sorted by key. + """ + # Create a list of tuples containing key and value, sorted on key. + d = SortedTuples(d.items()) + # We're only interested in the values. + return (i[1] for i in d) + +def UnionTypes(descriptors): + """ + Returns a tuple containing a set of header filenames to include, a set of + tuples containing a type declaration and a boolean if the type is a struct + for member types of the unions and a CGList containing CGUnionStructs for + every union. + """ + + # Now find all the things we'll need as arguments and return values because + # we need to wrap or unwrap them. + headers = set() + declarations = set() + unionStructs = dict() + for d in descriptors: + if d.interface.isExternal(): + continue + + for t in getTypes(d): + t = t.unroll() + if t.isUnion(): + name = str(t) + if not name in unionStructs: + unionStructs[name] = CGUnionStruct(t, d) + for f in t.flatMemberTypes: + f = f.unroll() + if f.isInterface(): + if f.isSpiderMonkeyInterface(): + headers.add("jsfriendapi.h") + headers.add("mozilla/dom/TypedArray.h") + else: + typeDesc = d.getDescriptor(f.inner.identifier.name) + if typeDesc is not None: + declarations.add((typeDesc.nativeType, False)) + elif f.isDictionary(): + declarations.add((f.inner.identifier.name, True)) + + return (headers, declarations, CGList(SortedDictValues(unionStructs), "\n")) + +def UnionConversions(descriptors): + """ + Returns a CGThing to declare all union argument conversion helper structs. + """ + # Now find all the things we'll need as arguments because we + # need to unwrap them. + unionConversions = dict() + for d in descriptors: + if d.interface.isExternal(): + continue + + def addUnionTypes(type): + if type.isUnion(): + type = type.unroll() + name = str(type) + if not name in unionConversions: + unionConversions[name] = CGUnionConversionStruct(type, d) + + members = [m for m in d.interface.members] + if d.interface.ctor(): + members.append(d.interface.ctor()) + signatures = [s for m in members if m.isMethod() for s in m.signatures()] + for s in signatures: + assert len(s) == 2 + (_, arguments) = s + for a in arguments: + addUnionTypes(a.type) + + for m in members: + if m.isAttr() and not m.readonly: + addUnionTypes(m.type) + + return CGWrapper(CGList(SortedDictValues(unionConversions), "\n"), + post="\n\n") + +class Argument(): + """ + A class for outputting the type and name of an argument + """ + def __init__(self, argType, name): + self.argType = argType + self.name = name + def __str__(self): + return self.argType + ' ' + self.name + +class CGAbstractMethod(CGThing): + """ + An abstract class for generating code for a method. Subclasses + should override definition_body to create the actual code. + + descriptor is the descriptor for the interface the method is associated with + + name is the name of the method as a string + + returnType is the IDLType of the return value + + args is a list of Argument objects + + inline should be True to generate an inline method, whose body is + part of the declaration. + + alwaysInline should be True to generate an inline method annotated with + MOZ_ALWAYS_INLINE. + + static should be True to generate a static method, which only has + a definition. + + If templateArgs is not None it should be a list of strings containing + template arguments, and the function will be templatized using those + arguments. + """ + def __init__(self, descriptor, name, returnType, args, inline=False, alwaysInline=False, static=False, templateArgs=None): + CGThing.__init__(self) + self.descriptor = descriptor + self.name = name + self.returnType = returnType + self.args = args + self.inline = inline + self.alwaysInline = alwaysInline + self.static = static + self.templateArgs = templateArgs + def _argstring(self): + return ', '.join([str(a) for a in self.args]) + def _template(self): + if self.templateArgs is None: + return '' + return 'template <%s>\n' % ', '.join(self.templateArgs) + def _decorators(self): + decorators = [] + if self.alwaysInline: + decorators.append('MOZ_ALWAYS_INLINE') + elif self.inline: + decorators.append('inline') + if self.static: + decorators.append('static') + decorators.append(self.returnType) + maybeNewline = " " if self.inline else "\n" + return ' '.join(decorators) + maybeNewline + def declare(self): + if self.inline: + return self._define() + return "%s%s%s(%s);\n" % (self._template(), self._decorators(), self.name, self._argstring()) + def _define(self): + return self.definition_prologue() + "\n" + self.definition_body() + self.definition_epilogue() + def define(self): + return "" if self.inline else self._define() + def definition_prologue(self): + return "%s%s%s(%s)\n{" % (self._template(), self._decorators(), + self.name, self._argstring()) + def definition_epilogue(self): + return "\n}\n" + def definition_body(self): + assert(False) # Override me! + +class CGAbstractStaticMethod(CGAbstractMethod): + """ + Abstract base class for codegen of implementation-only (no + declaration) static methods. + """ + def __init__(self, descriptor, name, returnType, args): + CGAbstractMethod.__init__(self, descriptor, name, returnType, args, + inline=False, static=True) + def declare(self): + # We only have implementation + return "" + +class CGAbstractClassHook(CGAbstractStaticMethod): + """ + Meant for implementing JSClass hooks, like Finalize or Trace. Does very raw + 'this' unwrapping as it assumes that the unwrapped type is always known. + """ + def __init__(self, descriptor, name, returnType, args): + CGAbstractStaticMethod.__init__(self, descriptor, name, returnType, + args) + + def definition_body_prologue(self): + return """ + %s* self = UnwrapDOMObject<%s>(obj, eRegularDOMObject); +""" % (self.descriptor.nativeType, self.descriptor.nativeType) + + def definition_body(self): + return self.definition_body_prologue() + self.generate_code() + + def generate_code(self): + # Override me + assert(False) + +class CGAddPropertyHook(CGAbstractClassHook): + """ + A hook for addProperty, used to preserve our wrapper from GC. + """ + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), Argument('JSHandleObject', 'obj'), + Argument('JSHandleId', 'id'), Argument('JSMutableHandleValue', 'vp')] + CGAbstractClassHook.__init__(self, descriptor, ADDPROPERTY_HOOK_NAME, + 'JSBool', args) + + def generate_code(self): + # FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=774279 + # Using a real trace hook might enable us to deal with non-nsISupports + # wrappercached things here. + assert self.descriptor.nativeIsISupports + return """ nsContentUtils::PreserveWrapper(reinterpret_cast<nsISupports*>(self), self); + return true;""" + +def finalizeHook(descriptor, hookName, context): + if descriptor.customFinalize: + return """if (self) { + self->%s(%s); +}""" % (hookName, context) + clearWrapper = "ClearWrapper(self, self);\n" if descriptor.wrapperCache else "" + if descriptor.workers: + release = "self->Release();" + else: + assert descriptor.nativeIsISupports + release = """XPCJSRuntime *rt = nsXPConnect::GetRuntimeInstance(); +if (rt) { + rt->DeferredRelease(reinterpret_cast<nsISupports*>(self)); +} else { + NS_RELEASE(self); +}""" + return clearWrapper + release + +class CGClassFinalizeHook(CGAbstractClassHook): + """ + A hook for finalize, used to release our native object. + """ + def __init__(self, descriptor): + args = [Argument('JSFreeOp*', 'fop'), Argument('JSObject*', 'obj')] + CGAbstractClassHook.__init__(self, descriptor, FINALIZE_HOOK_NAME, + 'void', args) + + def generate_code(self): + return CGIndenter(CGGeneric(finalizeHook(self.descriptor, self.name, self.args[0].name))).define() + +class CGClassTraceHook(CGAbstractClassHook): + """ + A hook to trace through our native object; used for GC and CC + """ + def __init__(self, descriptor): + args = [Argument('JSTracer*', 'trc'), Argument('JSObject*', 'obj')] + CGAbstractClassHook.__init__(self, descriptor, TRACE_HOOK_NAME, 'void', + args) + + def generate_code(self): + return """ if (self) { + self->%s(%s); + }""" % (self.name, self.args[0].name) + +class CGClassConstructHook(CGAbstractStaticMethod): + """ + JS-visible constructor for our objects + """ + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), Argument('unsigned', 'argc'), Argument('JS::Value*', 'vp')] + CGAbstractStaticMethod.__init__(self, descriptor, CONSTRUCT_HOOK_NAME, + 'JSBool', args) + self._ctor = self.descriptor.interface.ctor() + + def define(self): + if not self._ctor: + return "" + return CGAbstractStaticMethod.define(self) + + def definition_body(self): + return self.generate_code() + + def generate_code(self): + preamble = """ + JSObject* obj = JS_GetGlobalForObject(cx, JSVAL_TO_OBJECT(JS_CALLEE(cx, vp))); +""" + if self.descriptor.workers: + preArgs = ["cx", "obj"] + else: + preamble += """ + nsISupports* global; + xpc_qsSelfRef globalRef; + { + nsresult rv; + JS::Value val = OBJECT_TO_JSVAL(obj); + rv = xpc_qsUnwrapArg<nsISupports>(cx, val, &global, &globalRef.ptr, &val); + if (NS_FAILED(rv)) { + return Throw<true>(cx, NS_ERROR_XPC_BAD_CONVERT_JS); + } + } +""" + preArgs = ["global"] + + name = self._ctor.identifier.name + nativeName = MakeNativeName(self.descriptor.binaryNames.get(name, name)) + callGenerator = CGMethodCall(preArgs, nativeName, True, + self.descriptor, self._ctor) + return preamble + callGenerator.define(); + +class CGClassHasInstanceHook(CGAbstractStaticMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), Argument('JSHandleObject', 'obj'), + Argument('JSMutableHandleValue', 'vp'), Argument('JSBool*', 'bp')] + CGAbstractStaticMethod.__init__(self, descriptor, HASINSTANCE_HOOK_NAME, + 'JSBool', args) + + def define(self): + if not self.descriptor.hasInstanceInterface: + return "" + return CGAbstractStaticMethod.define(self) + + def definition_body(self): + return self.generate_code() + + def generate_code(self): + return """ if (!vp.isObject()) { + *bp = false; + return true; + } + + jsval protov; + if (!JS_GetProperty(cx, obj, "prototype", &protov)) + return false; + if (!protov.isObject()) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_PROTOTYPE, + "%s"); + return false; + } + JSObject *objProto = &protov.toObject(); + + JSObject* instance = &vp.toObject(); + JSObject* proto; + if (!JS_GetPrototype(cx, instance, &proto)) + return false; + while (proto) { + if (proto == objProto) { + *bp = true; + return true; + } + if (!JS_GetPrototype(cx, proto, &proto)) + return false; + } + + nsISupports* native = + nsContentUtils::XPConnect()->GetNativeOfWrapper(cx, instance); + nsCOMPtr<%s> qiResult = do_QueryInterface(native); + *bp = !!qiResult; + return true; +""" % (self.descriptor.name, self.descriptor.hasInstanceInterface) + +def isChromeOnly(m): + return m.getExtendedAttribute("ChromeOnly") + +class PropertyDefiner: + """ + A common superclass for defining things on prototype objects. + + Subclasses should implement generateArray to generate the actual arrays of + things we're defining. They should also set self.chrome to the list of + things exposed to chrome and self.regular to the list of things exposed to + web pages. self.chrome must be a superset of self.regular but also include + all the ChromeOnly stuff. + """ + def __init__(self, descriptor, name): + self.descriptor = descriptor + self.name = name + # self.prefCacheData will store an array of (prefname, bool*) + # pairs for our bool var caches. generateArray will fill it + # in as needed. + self.prefCacheData = [] + def hasChromeOnly(self): + return len(self.chrome) > len(self.regular) + def hasNonChromeOnly(self): + return len(self.regular) > 0 + def variableName(self, chrome): + if chrome and self.hasChromeOnly(): + return "sChrome" + self.name + if self.hasNonChromeOnly(): + return "s" + self.name + return "NULL" + def usedForXrays(self, chrome): + # We only need Xrays for methods, attributes and constants. And we only + # need them for the non-chrome ones if we have no chromeonly things. + # Otherwise (we have chromeonly attributes) we need Xrays for the chrome + # methods/attributes/constants. Finally, in workers there are no Xrays. + return ((self.name is "Methods" or self.name is "Attributes" or + self.name is "Constants") and + chrome == self.hasChromeOnly() and + not self.descriptor.workers) + + def __str__(self): + # We only need to generate id arrays for things that will end + # up used via ResolveProperty or EnumerateProperties. + str = self.generateArray(self.regular, self.variableName(False), + self.usedForXrays(False)) + if self.hasChromeOnly(): + str += self.generateArray(self.chrome, self.variableName(True), + self.usedForXrays(True)) + return str + + @staticmethod + def getControllingPref(interfaceMember): + prefName = interfaceMember.getExtendedAttribute("Pref") + if prefName is None: + return None + # It's a list of strings + assert(len(prefName) is 1) + assert(prefName[0] is not None) + return prefName[0] + + def generatePrefableArray(self, array, name, specTemplate, specTerminator, + specType, getPref, getDataTuple, doIdArrays): + """ + This method generates our various arrays. + + array is an array of interface members as passed to generateArray + + name is the name as passed to generateArray + + specTemplate is a template for each entry of the spec array + + specTerminator is a terminator for the spec array (inserted every time + our controlling pref changes and at the end of the array) + + specType is the actual typename of our spec + + getPref is a callback function that takes an array entry and returns + the corresponding pref value. + + getDataTuple is a callback function that takes an array entry and + returns a tuple suitable for substitution into specTemplate. + """ + + # We want to generate a single list of specs, but with specTerminator + # inserted at every point where the pref name controlling the member + # changes. That will make sure the order of the properties as exposed + # on the interface and interface prototype objects does not change when + # pref control is added to members while still allowing us to define all + # the members in the smallest number of JSAPI calls. + assert(len(array) is not 0) + lastPref = getPref(array[0]) # So we won't put a specTerminator + # at the very front of the list. + specs = [] + prefableSpecs = [] + if doIdArrays: + prefableIds = [] + + prefableTemplate = ' { true, &%s[%d] }' + prefCacheTemplate = '&%s[%d].enabled' + def switchToPref(props, pref): + # Remember the info about where our pref-controlled + # booleans live. + if pref is not None: + props.prefCacheData.append( + (pref, prefCacheTemplate % (name, len(prefableSpecs))) + ) + # Set up pointers to the new sets of specs and ids + # inside prefableSpecs and prefableIds + prefableSpecs.append(prefableTemplate % + (name + "_specs", len(specs))) + + switchToPref(self, lastPref) + + for member in array: + curPref = getPref(member) + if lastPref != curPref: + # Terminate previous list + specs.append(specTerminator) + # And switch to our new pref + switchToPref(self, curPref) + lastPref = curPref + # And the actual spec + specs.append(specTemplate % getDataTuple(member)) + specs.append(specTerminator) + prefableSpecs.append(" { false, NULL }"); + + arrays = (("static %s %s_specs[] = {\n" + + ',\n'.join(specs) + "\n" + + "};\n\n" + + "static Prefable<%s> %s[] = {\n" + + ',\n'.join(prefableSpecs) + "\n" + + "};\n\n") % (specType, name, specType, name)) + if doIdArrays: + arrays += ("static jsid %s_ids[%i] = { JSID_VOID };\n\n" % + (name, len(specs))) + return arrays + + +# The length of a method is the maximum of the lengths of the +# argument lists of all its overloads. +def methodLength(method): + signatures = method.signatures() + return max([len(arguments) for (retType, arguments) in signatures]) + +class MethodDefiner(PropertyDefiner): + """ + A class for defining methods on a prototype object. + """ + def __init__(self, descriptor, name, static): + PropertyDefiner.__init__(self, descriptor, name) + + # FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=772822 + # We should be able to check for special operations without an + # identifier. For now we check if the name starts with __ + methods = [m for m in descriptor.interface.members if + m.isMethod() and m.isStatic() == static and + not m.isIdentifierLess()] + self.chrome = [{"name": m.identifier.name, + "length": methodLength(m), + "flags": "JSPROP_ENUMERATE", + "pref": PropertyDefiner.getControllingPref(m) } + for m in methods] + self.regular = [{"name": m.identifier.name, + "length": methodLength(m), + "flags": "JSPROP_ENUMERATE", + "pref": PropertyDefiner.getControllingPref(m) } + for m in methods if not isChromeOnly(m)] + + # FIXME Check for an existing iterator on the interface first. + if any(m.isGetter() and m.isIndexed() for m in methods): + self.chrome.append({"name": 'iterator', + "methodInfo": False, + "nativeName": "JS_ArrayIterator", + "length": 0, + "flags": "JSPROP_ENUMERATE", + "pref": None }) + self.regular.append({"name": 'iterator', + "methodInfo": False, + "nativeName": "JS_ArrayIterator", + "length": 0, + "flags": "JSPROP_ENUMERATE", + "pref": None }) + + if not descriptor.interface.parent and not static and not descriptor.workers: + self.chrome.append({"name": 'QueryInterface', + "methodInfo": False, + "length": 1, + "flags": "0", + "pref": None }) + + if static: + if not descriptor.interface.hasInterfaceObject(): + # static methods go on the interface object + assert not self.hasChromeOnly() and not self.hasNonChromeOnly() + else: + if not descriptor.interface.hasInterfacePrototypeObject(): + # non-static methods go on the interface prototype object + assert not self.hasChromeOnly() and not self.hasNonChromeOnly() + + def generateArray(self, array, name, doIdArrays): + if len(array) == 0: + return "" + + def pref(m): + return m["pref"] + + def specData(m): + if m.get("methodInfo", True): + jitinfo = ("&%s_methodinfo" % m["name"]) + accessor = "genericMethod" + else: + jitinfo = "nullptr" + accessor = m.get("nativeName", m["name"]) + return (m["name"], accessor, jitinfo, m["length"], m["flags"]) + + return self.generatePrefableArray( + array, name, + ' JS_FNINFO("%s", %s, %s, %s, %s)', + ' JS_FS_END', + 'JSFunctionSpec', + pref, specData, doIdArrays) + +class AttrDefiner(PropertyDefiner): + def __init__(self, descriptor, name): + PropertyDefiner.__init__(self, descriptor, name) + self.name = name + self.chrome = [m for m in descriptor.interface.members if m.isAttr()] + self.regular = [m for m in self.chrome if not isChromeOnly(m)] + + def generateArray(self, array, name, doIdArrays): + if len(array) == 0: + return "" + + def flags(attr): + return "JSPROP_SHARED | JSPROP_ENUMERATE | JSPROP_NATIVE_ACCESSORS" + + def getter(attr): + native = ("genericLenientGetter" if attr.hasLenientThis() + else "genericGetter") + return ("{(JSPropertyOp)%(native)s, &%(name)s_getterinfo}" + % {"name" : attr.identifier.name, + "native" : native}) + + def setter(attr): + if attr.readonly: + return "JSOP_NULLWRAPPER" + native = ("genericLenientSetter" if attr.hasLenientThis() + else "genericSetter") + return ("{(JSStrictPropertyOp)%(native)s, &%(name)s_setterinfo}" + % {"name" : attr.identifier.name, + "native" : native}) + + def specData(attr): + return (attr.identifier.name, flags(attr), getter(attr), + setter(attr)) + + return self.generatePrefableArray( + array, name, + ' { "%s", 0, %s, %s, %s}', + ' { 0, 0, 0, JSOP_NULLWRAPPER, JSOP_NULLWRAPPER }', + 'JSPropertySpec', + PropertyDefiner.getControllingPref, specData, doIdArrays) + +class ConstDefiner(PropertyDefiner): + """ + A class for definining constants on the interface object + """ + def __init__(self, descriptor, name): + PropertyDefiner.__init__(self, descriptor, name) + self.name = name + self.chrome = [m for m in descriptor.interface.members if m.isConst()] + self.regular = [m for m in self.chrome if not isChromeOnly(m)] + + def generateArray(self, array, name, doIdArrays): + if len(array) == 0: + return "" + + def specData(const): + return (const.identifier.name, + convertConstIDLValueToJSVal(const.value)) + + return self.generatePrefableArray( + array, name, + ' { "%s", %s }', + ' { 0, JSVAL_VOID }', + 'ConstantSpec', + PropertyDefiner.getControllingPref, specData, doIdArrays) + +class PropertyArrays(): + def __init__(self, descriptor): + self.staticMethods = MethodDefiner(descriptor, "StaticMethods", True) + self.methods = MethodDefiner(descriptor, "Methods", False) + self.attrs = AttrDefiner(descriptor, "Attributes") + self.consts = ConstDefiner(descriptor, "Constants") + + @staticmethod + def arrayNames(): + return [ "staticMethods", "methods", "attrs", "consts" ] + + @staticmethod + def xrayRelevantArrayNames(): + return [ "methods", "attrs", "consts" ] + + def hasChromeOnly(self): + return reduce(lambda b, a: b or getattr(self, a).hasChromeOnly(), + self.arrayNames(), False) + def variableNames(self, chrome): + names = {} + for array in self.arrayNames(): + names[array] = getattr(self, array).variableName(chrome) + return names + def __str__(self): + define = "" + for array in self.arrayNames(): + define += str(getattr(self, array)) + return define + +class CGCreateInterfaceObjectsMethod(CGAbstractMethod): + """ + Generate the CreateInterfaceObjects method for an interface descriptor. + + properties should be a PropertyArrays instance. + """ + def __init__(self, descriptor, properties): + args = [Argument('JSContext*', 'aCx'), Argument('JSObject*', 'aGlobal'), + Argument('JSObject*', 'aReceiver')] + CGAbstractMethod.__init__(self, descriptor, 'CreateInterfaceObjects', 'JSObject*', args) + self.properties = properties + def definition_body(self): + protoChain = self.descriptor.prototypeChain + if len(protoChain) == 1: + getParentProto = "JS_GetObjectPrototype(aCx, aGlobal)" + else: + parentProtoName = self.descriptor.prototypeChain[-2] + getParentProto = ("%s::GetProtoObject(aCx, aGlobal, aReceiver)" % + toBindingNamespace(parentProtoName)) + + needInterfaceObject = self.descriptor.interface.hasInterfaceObject() + needInterfacePrototypeObject = self.descriptor.interface.hasInterfacePrototypeObject() + + # if we don't need to create anything, why are we generating this? + assert needInterfaceObject or needInterfacePrototypeObject + + idsToInit = [] + # There is no need to init any IDs in workers, because worker bindings + # don't have Xrays. + if not self.descriptor.workers: + for var in self.properties.xrayRelevantArrayNames(): + props = getattr(self.properties, var) + # We only have non-chrome ids to init if we have no chrome ids. + if props.hasChromeOnly(): + idsToInit.append(props.variableName(True)) + elif props.hasNonChromeOnly(): + idsToInit.append(props.variableName(False)) + if len(idsToInit) > 0: + initIds = CGList( + [CGGeneric("!InitIds(aCx, %s, %s_ids)" % (varname, varname)) for + varname in idsToInit], ' ||\n') + if len(idsToInit) > 1: + initIds = CGWrapper(initIds, pre="(", post=")", reindent=True) + initIds = CGList( + [CGGeneric("%s_ids[0] == JSID_VOID &&" % idsToInit[0]), initIds], + "\n") + initIds = CGWrapper(initIds, pre="if (", post=") {", reindent=True) + initIds = CGList( + [initIds, + CGGeneric((" %s_ids[0] = JSID_VOID;\n" + " return NULL;") % idsToInit[0]), + CGGeneric("}")], + "\n") + else: + initIds = None + + prefCacheData = [] + for var in self.properties.arrayNames(): + props = getattr(self.properties, var) + prefCacheData.extend(props.prefCacheData) + if len(prefCacheData) is not 0: + prefCacheData = [ + CGGeneric('Preferences::AddBoolVarCache(%s, "%s");' % (ptr, pref)) for + (pref, ptr) in prefCacheData] + prefCache = CGWrapper(CGIndenter(CGList(prefCacheData, "\n")), + pre=("static bool sPrefCachesInited = false;\n" + "if (!sPrefCachesInited) {\n" + " sPrefCachesInited = true;\n"), + post="\n}") + else: + prefCache = None + + getParentProto = ("JSObject* parentProto = %s;\n" + + "if (!parentProto) {\n" + + " return NULL;\n" + + "}\n") % getParentProto + + needInterfaceObjectClass = (needInterfaceObject and + self.descriptor.hasInstanceInterface) + needConstructor = (needInterfaceObject and + not self.descriptor.hasInstanceInterface) + if self.descriptor.interface.ctor(): + constructHook = CONSTRUCT_HOOK_NAME + constructArgs = methodLength(self.descriptor.interface.ctor()) + else: + constructHook = "ThrowingConstructor" + constructArgs = 0 + + if self.descriptor.concrete: + if self.descriptor.proxy: + domClass = "&Class" + else: + domClass = "&Class.mClass" + else: + domClass = "nullptr" + + call = """return dom::CreateInterfaceObjects(aCx, aGlobal, aReceiver, parentProto, + %s, %s, %s, %d, + %s, + %%(methods)s, %%(attrs)s, + %%(consts)s, %%(staticMethods)s, + %s);""" % ( + "&PrototypeClass" if needInterfacePrototypeObject else "NULL", + "&InterfaceObjectClass" if needInterfaceObjectClass else "NULL", + constructHook if needConstructor else "NULL", + constructArgs, + domClass, + '"' + self.descriptor.interface.identifier.name + '"' if needInterfaceObject else "NULL") + if self.properties.hasChromeOnly(): + if self.descriptor.workers: + accessCheck = "mozilla::dom::workers::GetWorkerPrivateFromContext(aCx)->IsChromeWorker()" + else: + accessCheck = "xpc::AccessCheck::isChrome(js::GetObjectCompartment(aGlobal))" + chrome = CGIfWrapper(CGGeneric(call % self.properties.variableNames(True)), + accessCheck) + chrome = CGWrapper(chrome, pre="\n\n") + else: + chrome = None + + functionBody = CGList( + [CGGeneric(getParentProto), initIds, prefCache, chrome, + CGGeneric(call % self.properties.variableNames(False))], + "\n\n") + return CGIndenter(functionBody).define() + +class CGGetPerInterfaceObject(CGAbstractMethod): + """ + A method for getting a per-interface object (a prototype object or interface + constructor object). + """ + def __init__(self, descriptor, name, idPrefix=""): + args = [Argument('JSContext*', 'aCx'), Argument('JSObject*', 'aGlobal'), + Argument('JSObject*', 'aReceiver')] + CGAbstractMethod.__init__(self, descriptor, name, + 'JSObject*', args, inline=True) + self.id = idPrefix + "id::" + self.descriptor.name + def definition_body(self): + return """ + + /* aGlobal and aReceiver are usually the same, but they can be different + too. For example a sandbox often has an xray wrapper for a window as the + prototype of the sandbox's global. In that case aReceiver is the xray + wrapper and aGlobal is the sandbox's global. + */ + + /* Make sure our global is sane. Hopefully we can remove this sometime */ + if (!(js::GetObjectClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL)) { + return NULL; + } + /* Check to see whether the interface objects are already installed */ + JSObject** protoOrIfaceArray = GetProtoOrIfaceArray(aGlobal); + JSObject* cachedObject = protoOrIfaceArray[%s]; + if (!cachedObject) { + protoOrIfaceArray[%s] = cachedObject = CreateInterfaceObjects(aCx, aGlobal, aReceiver); + } + + /* cachedObject might _still_ be null, but that's OK */ + return cachedObject;""" % (self.id, self.id) + +class CGGetProtoObjectMethod(CGGetPerInterfaceObject): + """ + A method for getting the interface prototype object. + """ + def __init__(self, descriptor): + CGGetPerInterfaceObject.__init__(self, descriptor, "GetProtoObject", + "prototypes::") + def definition_body(self): + return """ + /* Get the interface prototype object for this class. This will create the + object as needed. */""" + CGGetPerInterfaceObject.definition_body(self) + +class CGGetConstructorObjectMethod(CGGetPerInterfaceObject): + """ + A method for getting the interface constructor object. + """ + def __init__(self, descriptor): + CGGetPerInterfaceObject.__init__(self, descriptor, "GetConstructorObject", + "constructors::") + def definition_body(self): + return """ + /* Get the interface object for this class. This will create the object as + needed. */""" + CGGetPerInterfaceObject.definition_body(self) + +def CheckPref(descriptor, globalName, varName, retval, wrapperCache = None): + """ + Check whether bindings should be enabled for this descriptor. If not, set + varName to false and return retval. + """ + if not descriptor.prefable: + return "" + + if wrapperCache: + wrapperCache = " %s->ClearIsDOMBinding();\n" % (wrapperCache) + else: + wrapperCache = "" + + failureCode = (" %s = false;\n" + + " return %s;") % (varName, retval) + return """ + { + XPCWrappedNativeScope* scope = + XPCWrappedNativeScope::FindInJSObjectScope(aCx, %s); + if (!scope) { +%s + } + + if (!scope->ExperimentalBindingsEnabled()) { +%s%s + } + } +""" % (globalName, failureCode, wrapperCache, failureCode) + +class CGDefineDOMInterfaceMethod(CGAbstractMethod): + """ + A method for resolve hooks to try to lazily define the interface object for + a given interface. + """ + def __init__(self, descriptor): + args = [Argument('JSContext*', 'aCx'), Argument('JSObject*', 'aReceiver'), + Argument('bool*', 'aEnabled')] + CGAbstractMethod.__init__(self, descriptor, 'DefineDOMInterface', 'bool', args) + + def declare(self): + if self.descriptor.workers: + return '' + return CGAbstractMethod.declare(self) + + def define(self): + if self.descriptor.workers: + return '' + return CGAbstractMethod.define(self) + + def definition_body(self): + if self.descriptor.interface.hasInterfacePrototypeObject(): + # We depend on GetProtoObject defining an interface constructor + # object as needed. + getter = "GetProtoObject" + else: + getter = "GetConstructorObject" + + return (" JSObject* global = JS_GetGlobalForObject(aCx, aReceiver);\n" + + CheckPref(self.descriptor, "global", "*aEnabled", "false") + + """ + *aEnabled = true; + return !!%s(aCx, global, aReceiver);""" % (getter)) + +class CGPrefEnabled(CGAbstractMethod): + """ + A method for testing whether the preference controlling this + interface is enabled. When it's not, the interface should not be + visible on the global. + """ + def __init__(self, descriptor): + CGAbstractMethod.__init__(self, descriptor, 'PrefEnabled', 'bool', []) + + def declare(self): + return CGAbstractMethod.declare(self) + + def define(self): + return CGAbstractMethod.define(self) + + def definition_body(self): + return " return %s::PrefEnabled();" % self.descriptor.nativeType + +class CGIsMethod(CGAbstractMethod): + def __init__(self, descriptor): + args = [Argument('JSObject*', 'obj')] + CGAbstractMethod.__init__(self, descriptor, 'Is', 'bool', args) + + def definition_body(self): + # Non-proxy implementation would check + # js::GetObjectJSClass(obj) == &Class.mBase + return """ return IsProxy(obj);""" + +def CreateBindingJSObject(descriptor, parent): + if descriptor.proxy: + create = """ JSObject *obj = NewProxyObject(aCx, DOMProxyHandler::getInstance(), + JS::PrivateValue(aObject), proto, %s); + if (!obj) { + return NULL; + } + +""" + else: + create = """ JSObject* obj = JS_NewObject(aCx, &Class.mBase, proto, %s); + if (!obj) { + return NULL; + } + + js::SetReservedSlot(obj, DOM_OBJECT_SLOT, PRIVATE_TO_JSVAL(aObject)); +""" + return create % parent + +class CGWrapWithCacheMethod(CGAbstractMethod): + def __init__(self, descriptor): + assert descriptor.interface.hasInterfacePrototypeObject() + args = [Argument('JSContext*', 'aCx'), Argument('JSObject*', 'aScope'), + Argument(descriptor.nativeType + '*', 'aObject'), + Argument('nsWrapperCache*', 'aCache'), + Argument('bool*', 'aTriedToWrap')] + CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'JSObject*', args) + + def definition_body(self): + if self.descriptor.workers: + return """ *aTriedToWrap = true; + return aObject->GetJSObject();""" + + return """ *aTriedToWrap = true; + + JSObject* parent = WrapNativeParent(aCx, aScope, aObject->GetParentObject()); + if (!parent) { + return NULL; + } + + JSAutoCompartment ac(aCx, parent); + JSObject* global = JS_GetGlobalForObject(aCx, parent); +%s + JSObject* proto = GetProtoObject(aCx, global, global); + if (!proto) { + return NULL; + } + +%s + NS_ADDREF(aObject); + + aCache->SetWrapper(obj); + + return obj;""" % (CheckPref(self.descriptor, "global", "*aTriedToWrap", "NULL", "aCache"), + CreateBindingJSObject(self.descriptor, "parent")) + +class CGWrapMethod(CGAbstractMethod): + def __init__(self, descriptor): + # XXX can we wrap if we don't have an interface prototype object? + assert descriptor.interface.hasInterfacePrototypeObject() + args = [Argument('JSContext*', 'aCx'), Argument('JSObject*', 'aScope'), + Argument('T*', 'aObject'), Argument('bool*', 'aTriedToWrap')] + CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'JSObject*', args, inline=True, templateArgs=["class T"]) + + def definition_body(self): + return " return Wrap(aCx, aScope, aObject, aObject, aTriedToWrap);" + +class CGWrapNonWrapperCacheMethod(CGAbstractMethod): + def __init__(self, descriptor): + # XXX can we wrap if we don't have an interface prototype object? + assert descriptor.interface.hasInterfacePrototypeObject() + args = [Argument('JSContext*', 'aCx'), Argument('JSObject*', 'aScope'), + Argument(descriptor.nativeType + '*', 'aObject')] + CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'JSObject*', args) + + def definition_body(self): + return """ + JSObject* global = JS_GetGlobalForObject(aCx, aScope); + JSObject* proto = GetProtoObject(aCx, global, global); + if (!proto) { + return NULL; + } + +%s + NS_ADDREF(aObject); + + return obj;""" % CreateBindingJSObject(self.descriptor, "global") + +builtinNames = { + IDLType.Tags.bool: 'bool', + IDLType.Tags.int8: 'int8_t', + IDLType.Tags.int16: 'int16_t', + IDLType.Tags.int32: 'int32_t', + IDLType.Tags.int64: 'int64_t', + IDLType.Tags.uint8: 'uint8_t', + IDLType.Tags.uint16: 'uint16_t', + IDLType.Tags.uint32: 'uint32_t', + IDLType.Tags.uint64: 'uint64_t', + IDLType.Tags.float: 'float', + IDLType.Tags.double: 'double' +} + +numericTags = [ + IDLType.Tags.int8, IDLType.Tags.uint8, + IDLType.Tags.int16, IDLType.Tags.uint16, + IDLType.Tags.int32, IDLType.Tags.uint32, + IDLType.Tags.int64, IDLType.Tags.uint64, + IDLType.Tags.float, IDLType.Tags.double + ] + +class CastableObjectUnwrapper(): + """ + A class for unwrapping an object named by the "source" argument + based on the passed-in descriptor and storing it in a variable + called by the name in the "target" argument. + + codeOnFailure is the code to run if unwrapping fails. + """ + def __init__(self, descriptor, source, target, codeOnFailure): + assert descriptor.castable + + self.substitution = { "type" : descriptor.nativeType, + "protoID" : "prototypes::id::" + descriptor.name, + "source" : source, + "target" : target, + "codeOnFailure" : CGIndenter(CGGeneric(codeOnFailure), 4).define() } + if descriptor.hasXPConnectImpls: + # We don't use xpc_qsUnwrapThis because it will always throw on + # unwrap failure, whereas we want to control whether we throw or + # not. + self.substitution["codeOnFailure"] = CGIndenter(CGGeneric(string.Template( + "${type} *objPtr;\n" + "xpc_qsSelfRef objRef;\n" + "JS::Value val = JS::ObjectValue(*${source});\n" + "nsresult rv = xpc_qsUnwrapArg<${type}>(cx, val, &objPtr, &objRef.ptr, &val);\n" + "if (NS_FAILED(rv)) {\n" + "${codeOnFailure}\n" + "}\n" + "// We should be castable!\n" + "MOZ_ASSERT(!objRef.ptr);\n" + "// We should have an object, too!\n" + "MOZ_ASSERT(objPtr);\n" + "${target} = objPtr;").substitute(self.substitution)), 4).define() + + def __str__(self): + return string.Template( +"""{ + nsresult rv = UnwrapObject<${protoID}, ${type}>(cx, ${source}, ${target}); + if (NS_FAILED(rv)) { +${codeOnFailure} + } +}""").substitute(self.substitution) + +class FailureFatalCastableObjectUnwrapper(CastableObjectUnwrapper): + """ + As CastableObjectUnwrapper, but defaulting to throwing if unwrapping fails + """ + def __init__(self, descriptor, source, target): + CastableObjectUnwrapper.__init__(self, descriptor, source, target, + "return Throw<%s>(cx, rv);" % + toStringBool(not descriptor.workers)) + +class CallbackObjectUnwrapper: + """ + A class for unwrapping objects implemented in JS. + + |source| is the JSObject we want to use in native code. + |target| is an nsCOMPtr of the appropriate type in which we store the result. + """ + def __init__(self, descriptor, source, target, codeOnFailure=None): + if codeOnFailure is None: + codeOnFailure = ("return Throw<%s>(cx, rv);" % + toStringBool(not descriptor.workers)) + self.descriptor = descriptor + self.substitution = { "nativeType" : descriptor.nativeType, + "source" : source, + "target" : target, + "codeOnFailure" : CGIndenter(CGGeneric(codeOnFailure)).define() } + + def __str__(self): + if self.descriptor.workers: + return string.Template( + "${target} = ${source};" + ).substitute(self.substitution) + + return string.Template( + """nsresult rv; +XPCCallContext ccx(JS_CALLER, cx); +if (!ccx.IsValid()) { + rv = NS_ERROR_XPC_BAD_CONVERT_JS; +${codeOnFailure} +} + +const nsIID& iid = NS_GET_IID(${nativeType}); +nsRefPtr<nsXPCWrappedJS> wrappedJS; +rv = nsXPCWrappedJS::GetNewOrUsed(ccx, ${source}, iid, + NULL, getter_AddRefs(wrappedJS)); +if (NS_FAILED(rv) || !wrappedJS) { +${codeOnFailure} +} + +// Use a temp nsCOMPtr for the null-check, because ${target} might be +// OwningNonNull, not an nsCOMPtr. +nsCOMPtr<${nativeType}> tmp = do_QueryObject(wrappedJS.get()); +if (!tmp) { +${codeOnFailure} +} +${target} = tmp.forget();""").substitute(self.substitution) + +def dictionaryHasSequenceMember(dictionary): + return (any(typeIsSequenceOrHasSequenceMember(m.type) for m in + dictionary.members) or + (dictionary.parent and + dictionaryHasSequenceMember(dictionary.parent))) + +def typeIsSequenceOrHasSequenceMember(type): + if type.nullable(): + type = type.inner + if type.isSequence(): + return True + if type.isArray(): + elementType = type.inner + return typeIsSequenceOrHasSequenceMember(elementType) + if type.isDictionary(): + return dictionaryHasSequenceMember(type.inner) + if type.isUnion(): + return any(typeIsSequenceOrHasSequenceMember(m.type) for m in + type.flatMemberTypes) + return False + +def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, + isDefinitelyObject=False, + isMember=False, + isOptional=False, + invalidEnumValueFatal=True, + defaultValue=None, + treatNullAs="Default", + treatUndefinedAs="Default", + isEnforceRange=False, + isClamp=False): + """ + Get a template for converting a JS value to a native object based on the + given type and descriptor. If failureCode is given, then we're actually + testing whether we can convert the argument to the desired type. That + means that failures to convert due to the JS value being the wrong type of + value need to use failureCode instead of throwing exceptions. Failures to + convert that are due to JS exceptions (from toString or valueOf methods) or + out of memory conditions need to throw exceptions no matter what + failureCode is. + + If isDefinitelyObject is True, that means we know the value + isObject() and we have no need to recheck that. + + if isMember is True, we're being converted from a property of some + JS object, not from an actual method argument, so we can't rely on + our jsval being rooted or outliving us in any way. Any caller + passing true needs to ensure that it is handled correctly in + typeIsSequenceOrHasSequenceMember. + + If isOptional is true, then we are doing conversion of an optional + argument with no default value. + + invalidEnumValueFatal controls whether an invalid enum value conversion + attempt will throw (if true) or simply return without doing anything (if + false). + + If defaultValue is not None, it's the IDL default value for this conversion + + If isEnforceRange is true, we're converting an integer and throwing if the + value is out of range. + + If isClamp is true, we're converting an integer and clamping if the + value is out of range. + + The return value from this function is a tuple consisting of four things: + + 1) A string representing the conversion code. This will have template + substitution performed on it as follows: + + ${val} replaced by an expression for the JS::Value in question + ${valPtr} is a pointer to the JS::Value in question + ${holderName} replaced by the holder's name, if any + ${declName} replaced by the declaration's name + ${haveValue} replaced by an expression that evaluates to a boolean + for whether we have a JS::Value. Only used when + defaultValue is not None. + + 2) A CGThing representing the native C++ type we're converting to + (declType). This is allowed to be None if the conversion code is + supposed to be used as-is. + 3) A CGThing representing the type of a "holder" (holderType) which will + hold a possible reference to the C++ thing whose type we returned in #1, + or None if no such holder is needed. + 4) A boolean indicating whether the caller has to do optional-argument handling. + This will only be true if isOptional is true and if the returned template + expects both declType and holderType to be wrapped in Optional<>, with + ${declName} and ${holderName} adjusted to point to the Value() of the + Optional, and Construct() calls to be made on the Optional<>s as needed. + + ${declName} must be in scope before the generated code is entered. + + If holderType is not None then ${holderName} must be in scope + before the generated code is entered. + """ + # If we have a defaultValue then we're not actually optional for + # purposes of what we need to be declared as. + assert(defaultValue is None or not isOptional) + + # Also, we should not have a defaultValue if we know we're an object + assert(not isDefinitelyObject or defaultValue is None) + + # Helper functions for dealing with failures due to the JS value being the + # wrong type of value + def onFailureNotAnObject(failureCode): + return CGWrapper(CGGeneric( + failureCode or + 'return ThrowErrorMessage(cx, MSG_NOT_OBJECT);'), post="\n") + def onFailureBadType(failureCode, typeName): + return CGWrapper(CGGeneric( + failureCode or + 'return ThrowErrorMessage(cx, MSG_DOES_NOT_IMPLEMENT_INTERFACE, "%s");' % typeName), post="\n") + + # A helper function for handling default values. Takes a template + # body and the C++ code to set the default value and wraps the + # given template body in handling for the default value. + def handleDefault(template, setDefault): + if defaultValue is None: + return template + return CGWrapper( + CGIndenter(CGGeneric(template)), + pre="if (${haveValue}) {\n", + post=("\n" + "} else {\n" + "%s;\n" + "}" % + CGIndenter(CGGeneric(setDefault)).define())).define() + + # A helper function for handling null default values. Much like + # handleDefault, but checks that the default value, if it exists, is null. + def handleDefaultNull(template, codeToSetNull): + if (defaultValue is not None and + not isinstance(defaultValue, IDLNullValue)): + raise TypeError("Can't handle non-null default value here") + return handleDefault(template, codeToSetNull) + + # A helper function for wrapping up the template body for + # possibly-nullable objecty stuff + def wrapObjectTemplate(templateBody, isDefinitelyObject, type, + codeToSetNull, failureCode=None): + if not isDefinitelyObject: + # Handle the non-object cases by wrapping up the whole + # thing in an if cascade. + templateBody = ( + "if (${val}.isObject()) {\n" + + CGIndenter(CGGeneric(templateBody)).define() + "\n") + if type.nullable(): + templateBody += ( + "} else if (${val}.isNullOrUndefined()) {\n" + " %s;\n" % codeToSetNull) + templateBody += ( + "} else {\n" + + CGIndenter(onFailureNotAnObject(failureCode)).define() + + "}") + if type.nullable(): + templateBody = handleDefaultNull(templateBody, codeToSetNull) + else: + assert(defaultValue is None) + + return templateBody + + assert not (isEnforceRange and isClamp) # These are mutually exclusive + + if type.isArray(): + raise TypeError("Can't handle array arguments yet") + + if type.isSequence(): + assert not isEnforceRange and not isClamp + + if failureCode is not None: + raise TypeError("Can't handle sequences when failureCode is not None") + nullable = type.nullable(); + # Be very careful not to change "type": we need it later + if nullable: + elementType = type.inner.inner + else: + elementType = type.inner + + # We have to be careful with reallocation behavior for arrays. In + # particular, if we have a sequence of elements which are themselves + # sequences (so nsAutoTArrays) or have sequences as members, we have a + # problem. In that case, resizing the outermost nsAutoTarray to the + # right size will memmove its elements, but nsAutoTArrays are not + # memmovable and hence will end up with pointers to bogus memory, which + # is bad. To deal with this, we disallow sequences, arrays, + # dictionaries, and unions which contain sequences as sequence item + # types. If WebIDL ever adds another container type, we'd have to + # disallow it as well. + if typeIsSequenceOrHasSequenceMember(elementType): + raise TypeError("Can't handle a sequence containing another " + "sequence as an element or member of an element. " + "See the big comment explaining why.\n%s" % + str(type.location)) + + (elementTemplate, elementDeclType, + elementHolderType, dealWithOptional) = getJSToNativeConversionTemplate( + elementType, descriptorProvider, isMember=True) + if dealWithOptional: + raise TypeError("Shouldn't have optional things in sequences") + if elementHolderType is not None: + raise TypeError("Shouldn't need holders for sequences") + + typeName = CGWrapper(elementDeclType, pre="Sequence< ", post=" >") + if nullable: + typeName = CGWrapper(typeName, pre="Nullable< ", post=" >") + arrayRef = "${declName}.Value()" + else: + arrayRef = "${declName}" + # If we're optional, the const will come from the Optional + mutableTypeName = typeName + if not isOptional: + typeName = CGWrapper(typeName, pre="const ") + + templateBody = ("""JSObject* seq = &${val}.toObject();\n +if (!IsArrayLike(cx, seq)) { + return Throw<%s>(cx, NS_ERROR_XPC_BAD_CONVERT_JS); +} +uint32_t length; +// JS_GetArrayLength actually works on all objects +if (!JS_GetArrayLength(cx, seq, &length)) { + return false; +} +Sequence< %s > &arr = const_cast< Sequence< %s >& >(%s); +if (!arr.SetCapacity(length)) { + return Throw<%s>(cx, NS_ERROR_OUT_OF_MEMORY); +} +for (uint32_t i = 0; i < length; ++i) { + jsval temp; + if (!JS_GetElement(cx, seq, i, &temp)) { + return false; + } +""" % (toStringBool(descriptorProvider.workers), + elementDeclType.define(), + elementDeclType.define(), + arrayRef, + toStringBool(descriptorProvider.workers))) + + templateBody += CGIndenter(CGGeneric( + string.Template(elementTemplate).substitute( + { + "val" : "temp", + "valPtr": "&temp", + "declName" : "(*arr.AppendElement())" + } + ))).define() + + templateBody += "\n}" + templateBody = wrapObjectTemplate(templateBody, isDefinitelyObject, + type, + "const_cast< %s & >(${declName}).SetNull()" % mutableTypeName.define()) + return (templateBody, typeName, None, isOptional) + + if type.isUnion(): + if isMember: + raise TypeError("Can't handle unions as members, we have a " + "holderType") + nullable = type.nullable(); + if nullable: + type = type.inner + + assert(defaultValue is None or + (isinstance(defaultValue, IDLNullValue) and nullable)) + + unionArgumentObj = "${holderName}" + if isOptional or nullable: + unionArgumentObj += ".ref()" + + memberTypes = type.flatMemberTypes + names = [] + + interfaceMemberTypes = filter(lambda t: t.isNonCallbackInterface(), memberTypes) + if len(interfaceMemberTypes) > 0: + interfaceObject = [] + for memberType in interfaceMemberTypes: + if type.isGeckoInterface(): + name = memberType.inner.identifier.name + else: + name = memberType.name + interfaceObject.append(CGGeneric("(failed = !%s.TrySetTo%s(cx, ${val}, ${valPtr}, tryNext)) || !tryNext" % (unionArgumentObj, name))) + names.append(name) + interfaceObject = CGWrapper(CGList(interfaceObject, " ||\n"), pre="done = ", post=";\n", reindent=True) + else: + interfaceObject = None + + arrayObjectMemberTypes = filter(lambda t: t.isArray() or t.isSequence(), memberTypes) + if len(arrayObjectMemberTypes) > 0: + assert len(arrayObjectMemberTypes) == 1 + memberType = arrayObjectMemberTypes[0] + name = memberType.name + arrayObject = CGGeneric("done = (failed = !%s.TrySetTo%s(cx, ${val}, ${valPtr}, tryNext)) || !tryNext;" % (unionArgumentObj, name)) + # XXX Now we're supposed to check for an array or a platform object + # that supports indexed properties... skip that last for now. It's a + # bit of a pain. + arrayObject = CGWrapper(CGIndenter(arrayObject), + pre="if (IsArrayLike(cx, &argObj)) {\n", + post="}") + names.append(name) + else: + arrayObject = None + + dateObjectMemberTypes = filter(lambda t: t.isDate(), memberTypes) + if len(dateObjectMemberTypes) > 0: + assert len(dateObjectMemberTypes) == 1 + memberType = dateObjectMemberTypes[0] + name = memberType.name + dateObject = CGGeneric("%s.SetTo%s(cx, ${val}, ${valPtr});\n" + "done = true;" % (unionArgumentObj, name)) + dateObject = CGWrapper(CGIndenter(dateObject), + pre="if (JS_ObjectIsDate(cx, &argObj)) {\n", + post="\n}") + names.append(name) + else: + dateObject = None + + callbackMemberTypes = filter(lambda t: t.isCallback() or t.isCallbackInterface(), memberTypes) + if len(callbackMemberTypes) > 0: + assert len(callbackMemberTypes) == 1 + memberType = callbackMemberTypes[0] + name = memberType.name + callbackObject = CGGeneric("done = (failed = !%s.TrySetTo%s(cx, ${val}, ${valPtr}, tryNext)) || !tryNext;" % (unionArgumentObj, name)) + names.append(name) + else: + callbackObject = None + + dictionaryMemberTypes = filter(lambda t: t.isDictionary(), memberTypes) + if len(dictionaryMemberTypes) > 0: + raise TypeError("No support for unwrapping dictionaries as member " + "of a union") + else: + dictionaryObject = None + + if callbackObject or dictionaryObject: + nonPlatformObject = CGList([callbackObject, dictionaryObject], "\n") + nonPlatformObject = CGWrapper(CGIndenter(nonPlatformObject), + pre="if (!IsPlatformObject(cx, &argObj)) {\n", + post="\n}") + else: + nonPlatformObject = None + + objectMemberTypes = filter(lambda t: t.isObject(), memberTypes) + if len(objectMemberTypes) > 0: + object = CGGeneric("%s.SetToObject(&argObj);\n" + "done = true;" % unionArgumentObj) + else: + object = None + + hasObjectTypes = interfaceObject or arrayObject or dateObject or nonPlatformObject or object + if hasObjectTypes: + # If we try more specific object types first then we need to check + # whether that succeeded before converting to object. + if object and (interfaceObject or arrayObject or dateObject or nonPlatformObject): + object = CGWrapper(CGIndenter(object), pre="if (!done) {\n", + post=("\n}")) + + if arrayObject or dateObject or nonPlatformObject: + # An object can be both an array object and not a platform + # object, but we shouldn't have both in the union's members + # because they are not distinguishable. + assert not (arrayObject and nonPlatformObject) + templateBody = CGList([arrayObject, dateObject, nonPlatformObject], " else ") + else: + templateBody = None + if interfaceObject: + if templateBody: + templateBody = CGList([templateBody, object], "\n") + templateBody = CGWrapper(CGIndenter(templateBody), + pre="if (!done) {\n", post=("\n}")) + templateBody = CGList([interfaceObject, templateBody], "\n") + else: + templateBody = CGList([templateBody, object], "\n") + + if any([arrayObject, dateObject, nonPlatformObject, object]): + templateBody.prepend(CGGeneric("JSObject& argObj = ${val}.toObject();")) + templateBody = CGWrapper(CGIndenter(templateBody), + pre="if (${val}.isObject()) {\n", + post="\n}") + else: + templateBody = CGGeneric() + + otherMemberTypes = filter(lambda t: t.isString() or t.isEnum(), + memberTypes) + otherMemberTypes.extend(t for t in memberTypes if t.isPrimitive()) + if len(otherMemberTypes) > 0: + assert len(otherMemberTypes) == 1 + memberType = otherMemberTypes[0] + if memberType.isEnum(): + name = memberType.inner.identifier.name + else: + name = memberType.name + other = CGGeneric("done = (failed = !%s.TrySetTo%s(cx, ${val}, ${valPtr}, tryNext)) || !tryNext;" % (unionArgumentObj, name)) + names.append(name) + if hasObjectTypes: + other = CGWrapper(CGIndenter(other), "{\n", post="\n}") + if object: + join = " else " + else: + other = CGWrapper(other, pre="if (!done) ") + join = "\n" + templateBody = CGList([templateBody, other], join) + else: + other = None + + templateBody = CGWrapper(templateBody, pre="bool done = false, failed = false, tryNext;\n") + throw = CGGeneric("if (failed) {\n" + " return false;\n" + "}\n" + "if (!done) {\n" + " return ThrowErrorMessage(cx, MSG_NOT_IN_UNION, \"%s\");\n" + "}" % ", ".join(names)) + templateBody = CGWrapper(CGIndenter(CGList([templateBody, throw], "\n")), pre="{\n", post="\n}") + + typeName = type.name + argumentTypeName = typeName + "Argument" + if nullable: + typeName = "Nullable<" + typeName + " >" + if isOptional: + nonConstDecl = "const_cast<Optional<" + typeName + " >& >(${declName})" + else: + nonConstDecl = "const_cast<" + typeName + "& >(${declName})" + typeName = "const " + typeName + + def handleNull(templateBody, setToNullVar, extraConditionForNull=""): + null = CGGeneric("if (%s${val}.isNullOrUndefined()) {\n" + " %s.SetNull();\n" + "}" % (extraConditionForNull, setToNullVar)) + templateBody = CGWrapper(CGIndenter(templateBody), pre="{\n", post="\n}") + return CGList([null, templateBody], " else ") + + if type.hasNullableType: + templateBody = handleNull(templateBody, unionArgumentObj) + + declType = CGGeneric(typeName) + holderType = CGGeneric(argumentTypeName) + if isOptional: + mutableDecl = nonConstDecl + ".Value()" + declType = CGWrapper(declType, pre="const Optional<", post=" >") + holderType = CGWrapper(holderType, pre="Maybe<", post=" >") + constructDecl = CGGeneric(nonConstDecl + ".Construct();") + if nullable: + constructHolder = CGGeneric("${holderName}.construct(%s.SetValue());" % mutableDecl) + else: + constructHolder = CGGeneric("${holderName}.construct(${declName}.Value());") + else: + mutableDecl = nonConstDecl + constructDecl = None + if nullable: + holderType = CGWrapper(holderType, pre="Maybe<", post=" >") + constructHolder = CGGeneric("${holderName}.construct(%s.SetValue());" % mutableDecl) + else: + constructHolder = CGWrapper(holderType, post=" ${holderName}(${declName});") + holderType = None + + templateBody = CGList([constructHolder, templateBody], "\n") + if nullable: + if defaultValue: + assert(isinstance(defaultValue, IDLNullValue)) + valueMissing = "!(${haveValue}) || " + else: + valueMissing = "" + templateBody = handleNull(templateBody, mutableDecl, + extraConditionForNull=valueMissing) + templateBody = CGList([constructDecl, templateBody], "\n") + + return templateBody.define(), declType, holderType, False + + if type.isGeckoInterface(): + assert not isEnforceRange and not isClamp + + descriptor = descriptorProvider.getDescriptor( + type.unroll().inner.identifier.name) + # This is an interface that we implement as a concrete class + # or an XPCOM interface. + + # Allow null pointers for nullable types and old-binding classes + argIsPointer = type.nullable() or type.unroll().inner.isExternal() + + # Sequences and non-worker callbacks have to hold a strong ref to the + # thing being passed down. + forceOwningType = (descriptor.interface.isCallback() and + not descriptor.workers) or isMember + + typeName = descriptor.nativeType + typePtr = typeName + "*" + + # Compute a few things: + # - declType is the type we want to return as the first element of our + # tuple. + # - holderType is the type we want to return as the third element + # of our tuple. + + # Set up some sensible defaults for these things insofar as we can. + holderType = None + if argIsPointer: + if forceOwningType: + declType = "nsRefPtr<" + typeName + ">" + else: + declType = typePtr + else: + if forceOwningType: + declType = "OwningNonNull<" + typeName + ">" + else: + declType = "NonNull<" + typeName + ">" + + templateBody = "" + if descriptor.castable: + if descriptor.prefable: + raise TypeError("We don't support prefable castable object " + "arguments (like %s), because we don't know " + "how to handle them being preffed off" % + descriptor.interface.identifier.name) + if descriptor.interface.isConsequential(): + raise TypeError("Consequential interface %s being used as an " + "argument but flagged as castable" % + descriptor.interface.identifier.name) + if failureCode is not None: + templateBody += str(CastableObjectUnwrapper( + descriptor, + "&${val}.toObject()", + "${declName}", + failureCode)) + else: + templateBody += str(FailureFatalCastableObjectUnwrapper( + descriptor, + "&${val}.toObject()", + "${declName}")) + elif descriptor.interface.isCallback(): + templateBody += str(CallbackObjectUnwrapper( + descriptor, + "&${val}.toObject()", + "${declName}", + codeOnFailure=failureCode)) + elif descriptor.workers: + templateBody += "${declName} = &${val}.toObject();" + else: + # Either external, or new-binding non-castable. We always have a + # holder for these, because we don't actually know whether we have + # to addref when unwrapping or not. So we just pass an + # getter_AddRefs(nsRefPtr) to XPConnect and if we'll need a release + # it'll put a non-null pointer in there. + if forceOwningType: + # Don't return a holderType in this case; our declName + # will just own stuff. + templateBody += "nsRefPtr<" + typeName + "> ${holderName};\n" + else: + holderType = "nsRefPtr<" + typeName + ">" + templateBody += ( + "jsval tmpVal = ${val};\n" + + typePtr + " tmp;\n" + "if (NS_FAILED(xpc_qsUnwrapArg<" + typeName + ">(cx, ${val}, &tmp, static_cast<" + typeName + "**>(getter_AddRefs(${holderName})), &tmpVal))) {\n") + templateBody += CGIndenter(onFailureBadType(failureCode, + descriptor.interface.identifier.name)).define() + templateBody += ("}\n" + "MOZ_ASSERT(tmp);\n") + + if not isDefinitelyObject: + # Our tmpVal will go out of scope, so we can't rely on it + # for rooting + templateBody += ( + "if (tmpVal != ${val} && !${holderName}) {\n" + " // We have to have a strong ref, because we got this off\n" + " // some random object that might get GCed\n" + " ${holderName} = tmp;\n" + "}\n") + + # And store our tmp, before it goes out of scope. + templateBody += "${declName} = tmp;" + + templateBody = wrapObjectTemplate(templateBody, isDefinitelyObject, + type, "${declName} = NULL", + failureCode) + + declType = CGGeneric(declType) + if holderType is not None: + holderType = CGGeneric(holderType) + return (templateBody, declType, holderType, isOptional) + + if type.isSpiderMonkeyInterface(): + assert not isEnforceRange and not isClamp + if isMember: + raise TypeError("Can't handle member arraybuffers or " + "arraybuffer views because making sure all the " + "objects are properly rooted is hard") + name = type.name + # By default, we use a Maybe<> to hold our typed array. And in the optional + # non-nullable case we want to pass Optional<TypedArray> to consumers, not + # Optional<NonNull<TypedArray> >, so jump though some hoops to do that. + holderType = "Maybe<%s>" % name + constructLoc = "${holderName}" + constructMethod = "construct" + constructInternal = "ref" + if type.nullable(): + if isOptional: + declType = "const Optional<" + name + "*>" + else: + declType = name + "*" + else: + if isOptional: + declType = "const Optional<" + name + ">" + # We don't need a holder in this case + holderType = None + constructLoc = "(const_cast<Optional<" + name + ">& >(${declName}))" + constructMethod = "Construct" + constructInternal = "Value" + else: + declType = "NonNull<" + name + ">" + template = ( + "%s.%s(cx, &${val}.toObject());\n" + "if (!%s.%s().inited()) {\n" + "%s" # No newline here because onFailureBadType() handles that + "}\n" % + (constructLoc, constructMethod, constructLoc, constructInternal, + CGIndenter(onFailureBadType(failureCode, type.name)).define())) + nullableTarget = "" + if type.nullable(): + if isOptional: + mutableDecl = "(const_cast<Optional<" + name + "*>& >(${declName}))" + template += "%s.Construct();\n" % mutableDecl + nullableTarget = "%s.Value()" % mutableDecl + else: + nullableTarget = "${declName}" + template += "%s = ${holderName}.addr();" % nullableTarget + elif not isOptional: + template += "${declName} = ${holderName}.addr();" + template = wrapObjectTemplate(template, isDefinitelyObject, type, + "%s = NULL" % nullableTarget, + failureCode) + + if holderType is not None: + holderType = CGGeneric(holderType) + # We handle all the optional stuff ourselves; no need for caller to do it. + return (template, CGGeneric(declType), holderType, False) + + if type.isString(): + assert not isEnforceRange and not isClamp + + treatAs = { + "Default": "eStringify", + "EmptyString": "eEmpty", + "Null": "eNull" + } + if type.nullable(): + # For nullable strings null becomes a null string. + treatNullAs = "Null" + # For nullable strings undefined becomes a null string unless + # specified otherwise. + if treatUndefinedAs == "Default": + treatUndefinedAs = "Null" + nullBehavior = treatAs[treatNullAs] + if treatUndefinedAs == "Missing": + raise TypeError("We don't support [TreatUndefinedAs=Missing]") + undefinedBehavior = treatAs[treatUndefinedAs] + + def getConversionCode(varName): + conversionCode = ( + "if (!ConvertJSValueToString(cx, ${val}, ${valPtr}, %s, %s, %s)) {\n" + " return false;\n" + "}" % (nullBehavior, undefinedBehavior, varName)) + if defaultValue is None: + return conversionCode + + if isinstance(defaultValue, IDLNullValue): + assert(type.nullable()) + return handleDefault(conversionCode, + "%s.SetNull()" % varName) + return handleDefault( + conversionCode, + ("static const PRUnichar data[] = { %s };\n" + "%s.SetData(data, ArrayLength(data) - 1)" % + (", ".join(["'" + char + "'" for char in defaultValue.value] + ["0"]), + varName))) + + if isMember: + # We have to make a copy, because our jsval may well not + # live as long as our string needs to. + declType = CGGeneric("nsString") + return ( + "{\n" + " FakeDependentString str;\n" + "%s\n" + " ${declName} = str;\n" + "}\n" % CGIndenter(CGGeneric(getConversionCode("str"))).define(), + declType, None, isOptional) + + if isOptional: + declType = "Optional<nsAString>" + else: + declType = "NonNull<nsAString>" + + return ( + "%s\n" + "const_cast<%s&>(${declName}) = &${holderName};" % + (getConversionCode("${holderName}"), declType), + CGGeneric("const " + declType), CGGeneric("FakeDependentString"), + # No need to deal with Optional here; we have handled it already + False) + + if type.isEnum(): + assert not isEnforceRange and not isClamp + + if type.nullable(): + raise TypeError("We don't support nullable enumerated arguments " + "yet") + enum = type.inner.identifier.name + if invalidEnumValueFatal: + handleInvalidEnumValueCode = " MOZ_ASSERT(index >= 0);\n" + else: + handleInvalidEnumValueCode = ( + " if (index < 0) {\n" + " return true;\n" + " }\n") + + template = ( + "{\n" + " bool ok;\n" + " int index = FindEnumStringIndex<%(invalidEnumValueFatal)s>(cx, ${val}, %(values)s, \"%(enumtype)s\", &ok);\n" + " if (!ok) {\n" + " return false;\n" + " }\n" + "%(handleInvalidEnumValueCode)s" + " ${declName} = static_cast<%(enumtype)s>(index);\n" + "}" % { "enumtype" : enum, + "values" : enum + "Values::strings", + "invalidEnumValueFatal" : toStringBool(invalidEnumValueFatal), + "handleInvalidEnumValueCode" : handleInvalidEnumValueCode }) + + if defaultValue is not None: + assert(defaultValue.type.tag() == IDLType.Tags.domstring) + template = handleDefault(template, + ("${declName} = %sValues::%s" % + (enum, + getEnumValueName(defaultValue.value)))) + return (template, CGGeneric(enum), None, isOptional) + + if type.isCallback(): + assert not isEnforceRange and not isClamp + + if isMember: + raise TypeError("Can't handle member callbacks; need to sort out " + "rooting issues") + # XXXbz we're going to assume that callback types are always + # nullable and always have [TreatNonCallableAsNull] for now. + haveCallable = "${val}.isObject() && JS_ObjectIsCallable(cx, &${val}.toObject())" + if defaultValue is not None: + assert(isinstance(defaultValue, IDLNullValue)) + haveCallable = "${haveValue} && " + haveCallable + return ( + "if (%s) {\n" + " ${declName} = &${val}.toObject();\n" + "} else {\n" + " ${declName} = NULL;\n" + "}" % haveCallable, + CGGeneric("JSObject*"), None, isOptional) + + if type.isAny(): + assert not isEnforceRange and not isClamp + + if isMember: + raise TypeError("Can't handle member 'any'; need to sort out " + "rooting issues") + templateBody = "${declName} = ${val};" + templateBody = handleDefaultNull(templateBody, + "${declName} = JS::NullValue()") + return (templateBody, CGGeneric("JS::Value"), None, isOptional) + + if type.isObject(): + assert not isEnforceRange and not isClamp + + if isMember: + raise TypeError("Can't handle member 'object'; need to sort out " + "rooting issues") + template = wrapObjectTemplate("${declName} = &${val}.toObject();", + isDefinitelyObject, type, + "${declName} = NULL", + failureCode) + if type.nullable(): + declType = CGGeneric("JSObject*") + else: + declType = CGGeneric("NonNull<JSObject>") + return (template, declType, None, isOptional) + + if type.isDictionary(): + if failureCode is not None: + raise TypeError("Can't handle dictionaries when failureCode is not None") + # There are no nullable dictionaries + assert not type.nullable() + # All optional dictionaries always have default values, so we + # should be able to assume not isOptional here. + assert not isOptional + + typeName = CGDictionary.makeDictionaryName(type.inner, + descriptorProvider.workers) + actualTypeName = typeName + selfRef = "${declName}" + + declType = CGGeneric(actualTypeName) + + # If we're a member of something else, the const + # will come from the Optional or our container. + if not isMember: + declType = CGWrapper(declType, pre="const ") + selfRef = "const_cast<%s&>(%s)" % (typeName, selfRef) + + # We do manual default value handling here, because we + # actually do want a jsval, and we only handle null anyway + if defaultValue is not None: + assert(isinstance(defaultValue, IDLNullValue)) + val = "(${haveValue}) ? ${val} : JSVAL_NULL" + else: + val = "${val}" + + template = ("if (!%s.Init(cx, %s)) {\n" + " return false;\n" + "}" % (selfRef, val)) + + return (template, declType, None, False) + + if not type.isPrimitive(): + raise TypeError("Need conversion for argument type '%s'" % str(type)) + + typeName = builtinNames[type.tag()] + + conversionBehavior = "eDefault" + if isEnforceRange: + conversionBehavior = "eEnforceRange" + elif isClamp: + conversionBehavior = "eClamp" + + if type.nullable(): + dataLoc = "${declName}.SetValue()" + nullCondition = "${val}.isNullOrUndefined()" + if defaultValue is not None and isinstance(defaultValue, IDLNullValue): + nullCondition = "!(${haveValue}) || " + nullCondition + template = ( + "if (%s) {\n" + " ${declName}.SetNull();\n" + "} else if (!ValueToPrimitive<%s, %s>(cx, ${val}, &%s)) {\n" + " return false;\n" + "}" % (nullCondition, typeName, conversionBehavior, dataLoc)) + declType = CGGeneric("Nullable<" + typeName + ">") + else: + assert(defaultValue is None or + not isinstance(defaultValue, IDLNullValue)) + dataLoc = "${declName}" + template = ( + "if (!ValueToPrimitive<%s, %s>(cx, ${val}, &%s)) {\n" + " return false;\n" + "}" % (typeName, conversionBehavior, dataLoc)) + declType = CGGeneric(typeName) + if (defaultValue is not None and + # We already handled IDLNullValue, so just deal with the other ones + not isinstance(defaultValue, IDLNullValue)): + tag = defaultValue.type.tag() + if tag in numericTags: + defaultStr = defaultValue.value + else: + assert(tag == IDLType.Tags.bool) + defaultStr = toStringBool(defaultValue.value) + template = CGWrapper(CGIndenter(CGGeneric(template)), + pre="if (${haveValue}) {\n", + post=("\n" + "} else {\n" + " %s = %s;\n" + "}" % (dataLoc, defaultStr))).define() + + return (template, declType, None, isOptional) + +def instantiateJSToNativeConversionTemplate(templateTuple, replacements, + argcAndIndex=None): + """ + Take a tuple as returned by getJSToNativeConversionTemplate and a set of + replacements as required by the strings in such a tuple, and generate code + to convert into stack C++ types. + + If argcAndIndex is not None it must be a dict that can be used to + replace ${argc} and ${index}, where ${index} is the index of this + argument (0-based) and ${argc} is the total number of arguments. + """ + (templateBody, declType, holderType, dealWithOptional) = templateTuple + + if dealWithOptional and argcAndIndex is None: + raise TypeError("Have to deal with optional things, but don't know how") + if argcAndIndex is not None and declType is None: + raise TypeError("Need to predeclare optional things, so they will be " + "outside the check for big enough arg count!"); + + result = CGList([], "\n") + # Make a copy of "replacements" since we may be about to start modifying it + replacements = dict(replacements) + originalHolderName = replacements["holderName"] + if holderType is not None: + if dealWithOptional: + replacements["holderName"] = ( + "const_cast< %s & >(%s.Value())" % + (holderType.define(), originalHolderName)) + mutableHolderType = CGWrapper(holderType, pre="Optional< ", post=" >") + holderType = CGWrapper(mutableHolderType, pre="const ") + result.append( + CGList([holderType, CGGeneric(" "), + CGGeneric(originalHolderName), + CGGeneric(";")])) + + originalDeclName = replacements["declName"] + if declType is not None: + if dealWithOptional: + replacements["declName"] = ( + "const_cast< %s & >(%s.Value())" % + (declType.define(), originalDeclName)) + mutableDeclType = CGWrapper(declType, pre="Optional< ", post=" >") + declType = CGWrapper(mutableDeclType, pre="const ") + result.append( + CGList([declType, CGGeneric(" "), + CGGeneric(originalDeclName), + CGGeneric(";")])) + + conversion = CGGeneric( + string.Template(templateBody).substitute(replacements) + ) + + if argcAndIndex is not None: + if dealWithOptional: + declConstruct = CGIndenter( + CGGeneric("const_cast< %s &>(%s).Construct();" % + (mutableDeclType.define(), originalDeclName))) + if holderType is not None: + holderConstruct = CGIndenter( + CGGeneric("const_cast< %s &>(%s).Construct();" % + (mutableHolderType.define(), originalHolderName))) + else: + holderConstruct = None + else: + declConstruct = None + holderConstruct = None + + conversion = CGList( + [CGGeneric( + string.Template("if (${index} < ${argc}) {").substitute( + argcAndIndex + )), + declConstruct, + holderConstruct, + CGIndenter(conversion), + CGGeneric("}")], + "\n") + + result.append(conversion) + # Add an empty CGGeneric to get an extra newline after the argument + # conversion. + result.append(CGGeneric("")) + return result; + +def convertConstIDLValueToJSVal(value): + if isinstance(value, IDLNullValue): + return "JSVAL_NULL" + tag = value.type.tag() + if tag in [IDLType.Tags.int8, IDLType.Tags.uint8, IDLType.Tags.int16, + IDLType.Tags.uint16, IDLType.Tags.int32]: + return "INT_TO_JSVAL(%s)" % (value.value) + if tag == IDLType.Tags.uint32: + return "UINT_TO_JSVAL(%s)" % (value.value) + if tag in [IDLType.Tags.int64, IDLType.Tags.uint64]: + return "DOUBLE_TO_JSVAL(%s)" % (value.value) + if tag == IDLType.Tags.bool: + return "JSVAL_TRUE" if value.value else "JSVAL_FALSE" + if tag in [IDLType.Tags.float, IDLType.Tags.double]: + return "DOUBLE_TO_JSVAL(%s)" % (value.value) + raise TypeError("Const value of unhandled type: " + value.type) + +class CGArgumentConverter(CGThing): + """ + A class that takes an IDL argument object, its index in the + argument list, and the argv and argc strings and generates code to + unwrap the argument to the right native type. + """ + def __init__(self, argument, index, argv, argc, descriptorProvider, + invalidEnumValueFatal=True): + CGThing.__init__(self) + self.argument = argument + if argument.variadic: + raise TypeError("We don't support variadic arguments yet " + + str(argument.location)) + assert(not argument.defaultValue or argument.optional) + + replacer = { + "index" : index, + "argc" : argc, + "argv" : argv + } + self.replacementVariables = { + "declName" : "arg%d" % index, + "holderName" : ("arg%d" % index) + "_holder" + } + self.replacementVariables["val"] = string.Template( + "${argv}[${index}]" + ).substitute(replacer) + self.replacementVariables["valPtr"] = ( + "&" + self.replacementVariables["val"]) + if argument.defaultValue: + self.replacementVariables["haveValue"] = string.Template( + "${index} < ${argc}").substitute(replacer) + self.descriptorProvider = descriptorProvider + if self.argument.optional and not self.argument.defaultValue: + self.argcAndIndex = replacer + else: + self.argcAndIndex = None + self.invalidEnumValueFatal = invalidEnumValueFatal + + def define(self): + return instantiateJSToNativeConversionTemplate( + getJSToNativeConversionTemplate(self.argument.type, + self.descriptorProvider, + isOptional=(self.argcAndIndex is not None), + invalidEnumValueFatal=self.invalidEnumValueFatal, + defaultValue=self.argument.defaultValue, + treatNullAs=self.argument.treatNullAs, + treatUndefinedAs=self.argument.treatUndefinedAs, + isEnforceRange=self.argument.enforceRange, + isClamp=self.argument.clamp), + self.replacementVariables, + self.argcAndIndex).define() + +def getWrapTemplateForType(type, descriptorProvider, result, successCode, + isCreator): + """ + Reflect a C++ value stored in "result", of IDL type "type" into JS. The + "successCode" is the code to run once we have successfully done the + conversion. The resulting string should be used with string.Template, it + needs the following keys when substituting: jsvalPtr/jsvalRef/obj. + + Returns (templateString, infallibility of conversion template) + """ + haveSuccessCode = successCode is not None + if not haveSuccessCode: + successCode = "return true;" + + def setValue(value, callWrapValue=False): + """ + Returns the code to set the jsval to value. If "callWrapValue" is true + JS_WrapValue will be called on the jsval. + """ + if not callWrapValue: + tail = successCode + elif haveSuccessCode: + tail = ("if (!JS_WrapValue(cx, ${jsvalPtr})) {\n" + + " return false;\n" + + "}\n" + + successCode) + else: + tail = "return JS_WrapValue(cx, ${jsvalPtr});" + return ("${jsvalRef} = %s;\n" + + tail) % (value) + + def wrapAndSetPtr(wrapCall, failureCode=None): + """ + Returns the code to set the jsval by calling "wrapCall". "failureCode" + is the code to run if calling "wrapCall" fails + """ + if failureCode is None: + if not haveSuccessCode: + return "return " + wrapCall + ";" + failureCode = "return false;" + str = ("if (!%s) {\n" + + CGIndenter(CGGeneric(failureCode)).define() + "\n" + + "}\n" + + successCode) % (wrapCall) + return str + + if type is None or type.isVoid(): + return (setValue("JSVAL_VOID"), True) + + if type.isArray(): + raise TypeError("Can't handle array return values yet") + + if type.isSequence(): + if type.nullable(): + # Nullable sequences are Nullable< nsTArray<T> > + (recTemplate, recInfall) = getWrapTemplateForType(type.inner, descriptorProvider, + "%s.Value()" % result, successCode, + isCreator) + return (""" +if (%s.IsNull()) { +%s +} +%s""" % (result, CGIndenter(CGGeneric(setValue("JSVAL_NULL"))).define(), recTemplate), recInfall) + + # Now do non-nullable sequences. We use setting the element + # in the array as our succcess code because when we succeed in + # wrapping that's what we should do. + innerTemplate = wrapForType( + type.inner, descriptorProvider, + { + 'result' : "%s[i]" % result, + 'successCode': ("if (!JS_DefineElement(cx, returnArray, i, tmp,\n" + " NULL, NULL, JSPROP_ENUMERATE)) {\n" + " return false;\n" + "}"), + 'jsvalRef': "tmp", + 'jsvalPtr': "&tmp", + 'isCreator': isCreator + } + ) + innerTemplate = CGIndenter(CGGeneric(innerTemplate)).define() + return ((""" +uint32_t length = %s.Length(); +JSObject *returnArray = JS_NewArrayObject(cx, length, NULL); +if (!returnArray) { + return false; +} +jsval tmp; +for (uint32_t i = 0; i < length; ++i) { +%s +}\n""" % (result, innerTemplate)) + setValue("JS::ObjectValue(*returnArray)"), False) + + if type.isGeckoInterface(): + descriptor = descriptorProvider.getDescriptor(type.unroll().inner.identifier.name) + if type.nullable(): + wrappingCode = ("if (!%s) {\n" % (result) + + CGIndenter(CGGeneric(setValue("JSVAL_NULL"))).define() + "\n" + + "}\n") + else: + wrappingCode = "" + if (not descriptor.interface.isExternal() and + not descriptor.interface.isCallback()): + if descriptor.wrapperCache: + wrapMethod = "WrapNewBindingObject" + else: + if not isCreator: + raise MethodNotCreatorError(descriptor.interface.identifier.name) + wrapMethod = "WrapNewBindingNonWrapperCachedObject" + wrap = "%s(cx, ${obj}, %s, ${jsvalPtr})" % (wrapMethod, result) + # We don't support prefable stuff in workers. + assert(not descriptor.prefable or not descriptor.workers) + if not descriptor.prefable: + # Non-prefable bindings can only fail to wrap as a new-binding object + # if they already threw an exception. Same thing for + # non-prefable bindings. + failed = ("MOZ_ASSERT(JS_IsExceptionPending(cx));\n" + + "return false;") + else: + if descriptor.notflattened: + raise TypeError("%s is prefable but not flattened; " + "fallback won't work correctly" % + descriptor.interface.identifier.name) + # Try old-style wrapping for bindings which might be preffed off. + failed = wrapAndSetPtr("HandleNewBindingWrappingFailure(cx, ${obj}, %s, ${jsvalPtr})" % result) + wrappingCode += wrapAndSetPtr(wrap, failed) + else: + if descriptor.notflattened: + getIID = "&NS_GET_IID(%s), " % descriptor.nativeType + else: + getIID = "" + wrap = "WrapObject(cx, ${obj}, %s, %s${jsvalPtr})" % (result, getIID) + wrappingCode += wrapAndSetPtr(wrap) + return (wrappingCode, False) + + if type.isString(): + if type.nullable(): + return (wrapAndSetPtr("xpc::StringToJsval(cx, %s, ${jsvalPtr})" % result), False) + else: + return (wrapAndSetPtr("xpc::NonVoidStringToJsval(cx, %s, ${jsvalPtr})" % result), False) + + if type.isEnum(): + if type.nullable(): + raise TypeError("We don't support nullable enumerated return types " + "yet") + return ("""MOZ_ASSERT(uint32_t(%(result)s) < ArrayLength(%(strings)s)); +JSString* %(resultStr)s = JS_NewStringCopyN(cx, %(strings)s[uint32_t(%(result)s)].value, %(strings)s[uint32_t(%(result)s)].length); +if (!%(resultStr)s) { + return false; +} +""" % { "result" : result, + "resultStr" : result + "_str", + "strings" : type.inner.identifier.name + "Values::strings" } + + setValue("JS::StringValue(%s_str)" % result), False) + + if type.isCallback(): + assert not type.isInterface() + # XXXbz we're going to assume that callback types are always + # nullable and always have [TreatNonCallableAsNull] for now. + # See comments in WrapNewBindingObject explaining why we need + # to wrap here. + # NB: setValue(..., True) calls JS_WrapValue(), so is fallible + return (setValue("JS::ObjectOrNullValue(%s)" % result, True), False) + + if type.tag() == IDLType.Tags.any: + # See comments in WrapNewBindingObject explaining why we need + # to wrap here. + # NB: setValue(..., True) calls JS_WrapValue(), so is fallible + return (setValue(result, True), False) + + if type.isObject() or type.isSpiderMonkeyInterface(): + # See comments in WrapNewBindingObject explaining why we need + # to wrap here. + if type.nullable(): + toValue = "JS::ObjectOrNullValue(%s)" + else: + toValue = "JS::ObjectValue(*%s)" + # NB: setValue(..., True) calls JS_WrapValue(), so is fallible + return (setValue(toValue % result, True), False) + + if not type.isPrimitive(): + raise TypeError("Need to learn to wrap %s" % type) + + if type.nullable(): + (recTemplate, recInfal) = getWrapTemplateForType(type.inner, descriptorProvider, + "%s.Value()" % result, successCode, + isCreator) + return ("if (%s.IsNull()) {\n" % result + + CGIndenter(CGGeneric(setValue("JSVAL_NULL"))).define() + "\n" + + "}\n" + recTemplate, recInfal) + + tag = type.tag() + + if tag in [IDLType.Tags.int8, IDLType.Tags.uint8, IDLType.Tags.int16, + IDLType.Tags.uint16, IDLType.Tags.int32]: + return (setValue("INT_TO_JSVAL(int32_t(%s))" % result), True) + + elif tag in [IDLType.Tags.int64, IDLType.Tags.uint64, IDLType.Tags.float, + IDLType.Tags.double]: + # XXXbz will cast to double do the "even significand" thing that webidl + # calls for for 64-bit ints? Do we care? + return (setValue("JS_NumberValue(double(%s))" % result), True) + + elif tag == IDLType.Tags.uint32: + return (setValue("UINT_TO_JSVAL(%s)" % result), True) + + elif tag == IDLType.Tags.bool: + return (setValue("BOOLEAN_TO_JSVAL(%s)" % result), True) + + else: + raise TypeError("Need to learn to wrap primitive: %s" % type) + +def wrapForType(type, descriptorProvider, templateValues): + """ + Reflect a C++ value of IDL type "type" into JS. TemplateValues is a dict + that should contain: + + * 'jsvalRef': a C++ reference to the jsval in which to store the result of + the conversion + * 'jsvalPtr': a C++ pointer to the jsval in which to store the result of + the conversion + * 'obj' (optional): the name of the variable that contains the JSObject to + use as a scope when wrapping, if not supplied 'obj' + will be used as the name + * 'result' (optional): the name of the variable in which the C++ value is + stored, if not supplied 'result' will be used as + the name + * 'successCode' (optional): the code to run once we have successfully done + the conversion, if not supplied 'return true;' + will be used as the code + * 'isCreator' (optional): If true, we're wrapping for the return value of + a [Creator] method. Assumed false if not set. + """ + wrap = getWrapTemplateForType(type, descriptorProvider, + templateValues.get('result', 'result'), + templateValues.get('successCode', None), + templateValues.get('isCreator', False))[0] + + defaultValues = {'obj': 'obj'} + return string.Template(wrap).substitute(defaultValues, **templateValues) + +def infallibleForMember(member, type, descriptorProvider): + """ + Determine the fallibility of changing a C++ value of IDL type "type" into + JS for the given attribute. Apart from isCreator, all the defaults are used, + since the fallbility does not change based on the boolean values, + and the template will be discarded. + + CURRENT ASSUMPTIONS: + We assume that successCode for wrapping up return values cannot contain + failure conditions. + """ + return getWrapTemplateForType(type, descriptorProvider, 'result', None,\ + memberIsCreator(member))[1] + +def typeNeedsCx(type, retVal=False): + if type is None: + return False + if type.nullable(): + type = type.inner + if type.isSequence() or type.isArray(): + type = type.inner + if type.isUnion(): + return any(typeNeedsCx(t) for t in type.unroll().flatMemberTypes) + if retVal and type.isSpiderMonkeyInterface(): + return True + return type.isCallback() or type.isAny() or type.isObject() + +# Returns a tuple consisting of a CGThing containing the type of the return +# value, or None if there is no need for a return value, and a boolean signaling +# whether the return value is passed in an out parameter. +def getRetvalDeclarationForType(returnType, descriptorProvider, + resultAlreadyAddRefed): + if returnType is None or returnType.isVoid(): + # Nothing to declare + return None, False + if returnType.isPrimitive() and returnType.tag() in builtinNames: + result = CGGeneric(builtinNames[returnType.tag()]) + if returnType.nullable(): + result = CGWrapper(result, pre="Nullable<", post=">") + return result, False + if returnType.isString(): + return CGGeneric("nsString"), True + if returnType.isEnum(): + if returnType.nullable(): + raise TypeError("We don't support nullable enum return values") + return CGGeneric(returnType.inner.identifier.name), False + if returnType.isGeckoInterface(): + result = CGGeneric(descriptorProvider.getDescriptor( + returnType.unroll().inner.identifier.name).nativeType) + if resultAlreadyAddRefed: + result = CGWrapper(result, pre="nsRefPtr<", post=">") + else: + result = CGWrapper(result, post="*") + return result, False + if returnType.isCallback(): + # XXXbz we're going to assume that callback types are always + # nullable for now. + return CGGeneric("JSObject*"), False + if returnType.isAny(): + return CGGeneric("JS::Value"), False + if returnType.isObject() or returnType.isSpiderMonkeyInterface(): + return CGGeneric("JSObject*"), False + if returnType.isSequence(): + nullable = returnType.nullable() + if nullable: + returnType = returnType.inner + # If our result is already addrefed, use the right type in the + # sequence argument here. + (result, _) = getRetvalDeclarationForType(returnType.inner, + descriptorProvider, + resultAlreadyAddRefed) + result = CGWrapper(result, pre="nsTArray< ", post=" >") + if nullable: + result = CGWrapper(result, pre="Nullable< ", post=" >") + return result, True + raise TypeError("Don't know how to declare return value for %s" % + returnType) + +def isResultAlreadyAddRefed(descriptor, extendedAttributes): + # Default to already_AddRefed on the main thread, raw pointer in workers + return not descriptor.workers and not 'resultNotAddRefed' in extendedAttributes + +class CGCallGenerator(CGThing): + """ + A class to generate an actual call to a C++ object. Assumes that the C++ + object is stored in a variable whose name is given by the |object| argument. + + errorReport should be a CGThing for an error report or None if no + error reporting is needed. + """ + def __init__(self, errorReport, arguments, argsPre, returnType, + extendedAttributes, descriptorProvider, nativeMethodName, + static, object="self", declareResult=True): + CGThing.__init__(self) + + assert errorReport is None or isinstance(errorReport, CGThing) + + isFallible = errorReport is not None + + resultAlreadyAddRefed = isResultAlreadyAddRefed(descriptorProvider, + extendedAttributes) + (result, resultOutParam) = getRetvalDeclarationForType(returnType, + descriptorProvider, + resultAlreadyAddRefed) + + args = CGList([CGGeneric(arg) for arg in argsPre], ", ") + for (a, name) in arguments: + # This is a workaround for a bug in Apple's clang. + if a.type.isObject() and not a.type.nullable() and not a.optional: + name = "(JSObject&)" + name + args.append(CGGeneric(name)) + + # Return values that go in outparams go here + if resultOutParam: + args.append(CGGeneric("result")) + if isFallible: + args.append(CGGeneric("rv")) + + needsCx = (typeNeedsCx(returnType, True) or + any(typeNeedsCx(a.type) for (a, _) in arguments) or + 'implicitJSContext' in extendedAttributes) + + if not "cx" in argsPre and needsCx: + args.prepend(CGGeneric("cx")) + + # Build up our actual call + self.cgRoot = CGList([], "\n") + + call = CGGeneric(nativeMethodName) + if static: + call = CGWrapper(call, pre="%s::" % descriptorProvider.nativeType) + else: + call = CGWrapper(call, pre="%s->" % object) + call = CGList([call, CGWrapper(args, pre="(", post=");")]) + if result is not None: + if declareResult: + result = CGWrapper(result, post=" result;") + self.cgRoot.prepend(result) + if not resultOutParam: + call = CGWrapper(call, pre="result = ") + + call = CGWrapper(call) + self.cgRoot.append(call) + + if isFallible: + self.cgRoot.prepend(CGGeneric("ErrorResult rv;")) + self.cgRoot.append(CGGeneric("if (rv.Failed()) {")) + self.cgRoot.append(CGIndenter(errorReport)) + self.cgRoot.append(CGGeneric("}")) + + def define(self): + return self.cgRoot.define() + +class MethodNotCreatorError(Exception): + def __init__(self, typename): + self.typename = typename + +class CGPerSignatureCall(CGThing): + """ + This class handles the guts of generating code for a particular + call signature. A call signature consists of four things: + + 1) A return type, which can be None to indicate that there is no + actual return value (e.g. this is an attribute setter) or an + IDLType if there's an IDL type involved (including |void|). + 2) An argument list, which is allowed to be empty. + 3) A name of a native method to call. + 4) Whether or not this method is static. + + We also need to know whether this is a method or a getter/setter + to do error reporting correctly. + + The idlNode parameter can be either a method or an attr. We can query + |idlNode.identifier| in both cases, so we can be agnostic between the two. + """ + # XXXbz For now each entry in the argument list is either an + # IDLArgument or a FakeArgument, but longer-term we may want to + # have ways of flagging things like JSContext* or optional_argc in + # there. + + def __init__(self, returnType, argsPre, arguments, nativeMethodName, static, + descriptor, idlNode, argConversionStartsAt=0, + getter=False, setter=False): + CGThing.__init__(self) + self.returnType = returnType + self.descriptor = descriptor + self.idlNode = idlNode + self.extendedAttributes = descriptor.getExtendedAttributes(idlNode, + getter=getter, + setter=setter) + self.argsPre = argsPre + self.arguments = arguments + self.argCount = len(arguments) + if self.argCount > argConversionStartsAt: + # Insert our argv in there + cgThings = [CGGeneric(self.getArgvDecl())] + else: + cgThings = [] + cgThings.extend([CGArgumentConverter(arguments[i], i, self.getArgv(), + self.getArgc(), self.descriptor, + invalidEnumValueFatal=not setter) for + i in range(argConversionStartsAt, self.argCount)]) + + cgThings.append(CGCallGenerator( + self.getErrorReport() if self.isFallible() else None, + self.getArguments(), self.argsPre, returnType, + self.extendedAttributes, descriptor, nativeMethodName, + static)) + self.cgRoot = CGList(cgThings, "\n") + + def getArgv(self): + return "argv" if self.argCount > 0 else "" + def getArgvDecl(self): + return "\nJS::Value* argv = JS_ARGV(cx, vp);\n" + def getArgc(self): + return "argc" + def getArguments(self): + return [(a, "arg" + str(i)) for (i, a) in enumerate(self.arguments)] + + def isFallible(self): + return not 'infallible' in self.extendedAttributes + + def wrap_return_value(self): + isCreator = memberIsCreator(self.idlNode) + if isCreator: + # We better be returning addrefed things! + assert(isResultAlreadyAddRefed(self.descriptor, + self.extendedAttributes) or + # Workers use raw pointers for new-object return + # values or something + self.descriptor.workers) + + resultTemplateValues = { 'jsvalRef': '*vp', 'jsvalPtr': 'vp', + 'isCreator': isCreator} + try: + return wrapForType(self.returnType, self.descriptor, + resultTemplateValues) + except MethodNotCreatorError, err: + assert not isCreator + raise TypeError("%s being returned from non-creator method or property %s.%s" % + (err.typename, + self.descriptor.interface.identifier.name, + self.idlNode.identifier.name)) + + def getErrorReport(self): + return CGGeneric('return ThrowMethodFailedWithDetails<%s>(cx, rv, "%s", "%s");' + % (toStringBool(not self.descriptor.workers), + self.descriptor.interface.identifier.name, + self.idlNode.identifier.name)) + + def define(self): + return (self.cgRoot.define() + "\n" + self.wrap_return_value()) + +class CGSwitch(CGList): + """ + A class to generate code for a switch statement. + + Takes three constructor arguments: an expression, a list of cases, + and an optional default. + + Each case is a CGCase. The default is a CGThing for the body of + the default case, if any. + """ + def __init__(self, expression, cases, default=None): + CGList.__init__(self, [CGIndenter(c) for c in cases], "\n") + self.prepend(CGWrapper(CGGeneric(expression), + pre="switch (", post=") {")); + if default is not None: + self.append( + CGIndenter( + CGWrapper( + CGIndenter(default), + pre="default: {\n", + post="\n break;\n}" + ) + ) + ) + + self.append(CGGeneric("}")) + +class CGCase(CGList): + """ + A class to generate code for a case statement. + + Takes three constructor arguments: an expression, a CGThing for + the body (allowed to be None if there is no body), and an optional + argument (defaulting to False) for whether to fall through. + """ + def __init__(self, expression, body, fallThrough=False): + CGList.__init__(self, [], "\n") + self.append(CGWrapper(CGGeneric(expression), pre="case ", post=": {")) + bodyList = CGList([body], "\n") + if fallThrough: + bodyList.append(CGGeneric("/* Fall through */")) + else: + bodyList.append(CGGeneric("break;")) + self.append(CGIndenter(bodyList)); + self.append(CGGeneric("}")) + +class CGMethodCall(CGThing): + """ + A class to generate selection of a method signature from a set of + signatures and generation of a call to that signature. + """ + def __init__(self, argsPre, nativeMethodName, static, descriptor, method): + CGThing.__init__(self) + + methodName = '"%s.%s"' % (descriptor.interface.identifier.name, method.identifier.name) + + def requiredArgCount(signature): + arguments = signature[1] + if len(arguments) == 0: + return 0 + requiredArgs = len(arguments) + while requiredArgs and arguments[requiredArgs-1].optional: + requiredArgs -= 1 + return requiredArgs + + def getPerSignatureCall(signature, argConversionStartsAt=0): + return CGPerSignatureCall(signature[0], argsPre, signature[1], + nativeMethodName, static, descriptor, + method, argConversionStartsAt) + + + signatures = method.signatures() + if len(signatures) == 1: + # Special case: we can just do a per-signature method call + # here for our one signature and not worry about switching + # on anything. + signature = signatures[0] + self.cgRoot = CGList([ CGIndenter(getPerSignatureCall(signature)) ]) + requiredArgs = requiredArgCount(signature) + + + if requiredArgs > 0: + code = ( + "if (argc < %d) {\n" + " return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, %s);\n" + "}" % (requiredArgs, methodName)) + self.cgRoot.prepend( + CGWrapper(CGIndenter(CGGeneric(code)), pre="\n", post="\n")) + return + + # Need to find the right overload + maxArgCount = method.maxArgCount + allowedArgCounts = method.allowedArgCounts + + argCountCases = [] + for argCount in allowedArgCounts: + possibleSignatures = method.signaturesForArgCount(argCount) + if len(possibleSignatures) == 1: + # easy case! + signature = possibleSignatures[0] + + # (possibly) important optimization: if signature[1] has > + # argCount arguments and signature[1][argCount] is optional and + # there is only one signature for argCount+1, then the + # signature for argCount+1 is just ourselves and we can fall + # through. + if (len(signature[1]) > argCount and + signature[1][argCount].optional and + (argCount+1) in allowedArgCounts and + len(method.signaturesForArgCount(argCount+1)) == 1): + argCountCases.append( + CGCase(str(argCount), None, True)) + else: + argCountCases.append( + CGCase(str(argCount), getPerSignatureCall(signature))) + continue + + distinguishingIndex = method.distinguishingIndexForArgCount(argCount) + + # We can't handle unions at the distinguishing index. + for (returnType, args) in possibleSignatures: + if args[distinguishingIndex].type.isUnion(): + raise TypeError("No support for unions as distinguishing " + "arguments yet: %s", + args[distinguishingIndex].location) + + # Convert all our arguments up to the distinguishing index. + # Doesn't matter which of the possible signatures we use, since + # they all have the same types up to that point; just use + # possibleSignatures[0] + caseBody = [CGGeneric("JS::Value* argv_start = JS_ARGV(cx, vp);")] + caseBody.extend([ CGArgumentConverter(possibleSignatures[0][1][i], + i, "argv_start", "argc", + descriptor) for i in + range(0, distinguishingIndex) ]) + + # Select the right overload from our set. + distinguishingArg = "argv_start[%d]" % distinguishingIndex + + def pickFirstSignature(condition, filterLambda): + sigs = filter(filterLambda, possibleSignatures) + assert len(sigs) < 2 + if len(sigs) > 0: + if condition is None: + caseBody.append( + getPerSignatureCall(sigs[0], distinguishingIndex)) + else: + caseBody.append(CGGeneric("if (" + condition + ") {")) + caseBody.append(CGIndenter( + getPerSignatureCall(sigs[0], distinguishingIndex))) + caseBody.append(CGGeneric("}")) + return True + return False + + # First check for null or undefined + pickFirstSignature("%s.isNullOrUndefined()" % distinguishingArg, + lambda s: (s[1][distinguishingIndex].type.nullable() or + s[1][distinguishingIndex].type.isDictionary())) + + # Now check for distinguishingArg being an object that implements a + # non-callback interface. That includes typed arrays and + # arraybuffers. + interfacesSigs = [ + s for s in possibleSignatures + if (s[1][distinguishingIndex].type.isObject() or + s[1][distinguishingIndex].type.isNonCallbackInterface()) ] + # There might be more than one of these; we need to check + # which ones we unwrap to. + + if len(interfacesSigs) > 0: + # The spec says that we should check for "platform objects + # implementing an interface", but it's enough to guard on these + # being an object. The code for unwrapping non-callback + # interfaces and typed arrays will just bail out and move on to + # the next overload if the object fails to unwrap correctly. We + # could even not do the isObject() check up front here, but in + # cases where we have multiple object overloads it makes sense + # to do it only once instead of for each overload. That will + # also allow the unwrapping test to skip having to do codegen + # for the null-or-undefined case, which we already handled + # above. + caseBody.append(CGGeneric("if (%s.isObject()) {" % + (distinguishingArg))) + for sig in interfacesSigs: + caseBody.append(CGIndenter(CGGeneric("do {"))); + type = sig[1][distinguishingIndex].type + + # The argument at index distinguishingIndex can't possibly + # be unset here, because we've already checked that argc is + # large enough that we can examine this argument. + testCode = instantiateJSToNativeConversionTemplate( + getJSToNativeConversionTemplate(type, descriptor, + failureCode="break;", + isDefinitelyObject=True), + { + "declName" : "arg%d" % distinguishingIndex, + "holderName" : ("arg%d" % distinguishingIndex) + "_holder", + "val" : distinguishingArg + }) + + # Indent by 4, since we need to indent further than our "do" statement + caseBody.append(CGIndenter(testCode, 4)); + # If we got this far, we know we unwrapped to the right + # interface, so just do the call. Start conversion with + # distinguishingIndex + 1, since we already converted + # distinguishingIndex. + caseBody.append(CGIndenter( + getPerSignatureCall(sig, distinguishingIndex + 1), 4)) + caseBody.append(CGIndenter(CGGeneric("} while (0);"))) + + caseBody.append(CGGeneric("}")) + + # XXXbz Now we're supposed to check for distinguishingArg being + # an array or a platform object that supports indexed + # properties... skip that last for now. It's a bit of a pain. + pickFirstSignature("%s.isObject() && IsArrayLike(cx, &%s.toObject())" % + (distinguishingArg, distinguishingArg), + lambda s: + (s[1][distinguishingIndex].type.isArray() or + s[1][distinguishingIndex].type.isSequence() or + s[1][distinguishingIndex].type.isObject())) + + # Check for Date objects + # XXXbz Do we need to worry about security wrappers around the Date? + pickFirstSignature("%s.isObject() && JS_ObjectIsDate(cx, &%s.toObject())" % + (distinguishingArg, distinguishingArg), + lambda s: (s[1][distinguishingIndex].type.isDate() or + s[1][distinguishingIndex].type.isObject())) + + # Check for vanilla JS objects + # XXXbz Do we need to worry about security wrappers? + pickFirstSignature("%s.isObject() && !IsPlatformObject(cx, &%s.toObject())" % + (distinguishingArg, distinguishingArg), + lambda s: (s[1][distinguishingIndex].type.isCallback() or + s[1][distinguishingIndex].type.isCallbackInterface() or + s[1][distinguishingIndex].type.isDictionary() or + s[1][distinguishingIndex].type.isObject())) + + # The remaining cases are mutually exclusive. The + # pickFirstSignature calls are what change caseBody + # Check for strings or enums + if pickFirstSignature(None, + lambda s: (s[1][distinguishingIndex].type.isString() or + s[1][distinguishingIndex].type.isEnum())): + pass + # Check for primitives + elif pickFirstSignature(None, + lambda s: s[1][distinguishingIndex].type.isPrimitive()): + pass + # Check for "any" + elif pickFirstSignature(None, + lambda s: s[1][distinguishingIndex].type.isAny()): + pass + else: + # Just throw; we have no idea what we're supposed to + # do with this. + caseBody.append(CGGeneric("return Throw<%s>(cx, NS_ERROR_XPC_BAD_CONVERT_JS);" % + toStringBool(not descriptor.workers))) + + argCountCases.append(CGCase(str(argCount), + CGList(caseBody, "\n"))) + + overloadCGThings = [] + overloadCGThings.append( + CGGeneric("unsigned argcount = NS_MIN(argc, %du);" % + maxArgCount)) + overloadCGThings.append( + CGSwitch("argcount", + argCountCases, + CGGeneric("return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, %s);\n" % methodName))) + overloadCGThings.append( + CGGeneric('MOZ_NOT_REACHED("We have an always-returning default case");\n' + 'return false;')) + self.cgRoot = CGWrapper(CGIndenter(CGList(overloadCGThings, "\n")), + pre="\n") + + def define(self): + return self.cgRoot.define() + +class CGGetterCall(CGPerSignatureCall): + """ + A class to generate a native object getter call for a particular IDL + getter. + """ + def __init__(self, returnType, nativeMethodName, descriptor, attr): + CGPerSignatureCall.__init__(self, returnType, [], [], + nativeMethodName, False, descriptor, + attr, getter=True) + +class FakeArgument(): + """ + A class that quacks like an IDLArgument. This is used to make + setters look like method calls or for special operations. + """ + def __init__(self, type, interfaceMember): + self.type = type + self.optional = False + self.variadic = False + self.defaultValue = None + self.treatNullAs = interfaceMember.treatNullAs + self.treatUndefinedAs = interfaceMember.treatUndefinedAs + self.enforceRange = False + self.clamp = False + +class CGSetterCall(CGPerSignatureCall): + """ + A class to generate a native object setter call for a particular IDL + setter. + """ + def __init__(self, argType, nativeMethodName, descriptor, attr): + CGPerSignatureCall.__init__(self, None, [], + [FakeArgument(argType, attr)], + nativeMethodName, False, descriptor, attr, + setter=True) + def wrap_return_value(self): + # We have no return value + return "\nreturn true;" + def getArgc(self): + return "1" + def getArgvDecl(self): + # We just get our stuff from our last arg no matter what + return "" + +class FakeCastableDescriptor(): + def __init__(self, descriptor): + self.castable = True + self.workers = descriptor.workers + self.nativeType = descriptor.nativeType + self.name = descriptor.name + self.hasXPConnectImpls = descriptor.hasXPConnectImpls + +class CGAbstractBindingMethod(CGAbstractStaticMethod): + """ + Common class to generate the JSNatives for all our methods, getters, and + setters. This will generate the function declaration and unwrap the + |this| object. Subclasses are expected to override the generate_code + function to do the rest of the work. This function should return a + CGThing which is already properly indented. + """ + def __init__(self, descriptor, name, args, unwrapFailureCode=None): + CGAbstractStaticMethod.__init__(self, descriptor, name, "JSBool", args) + + if unwrapFailureCode is None: + self.unwrapFailureCode = ("return Throw<%s>(cx, rv);" % + toStringBool(not descriptor.workers)) + else: + self.unwrapFailureCode = unwrapFailureCode + + def definition_body(self): + # Our descriptor might claim that we're not castable, simply because + # we're someone's consequential interface. But for this-unwrapping, we + # know that we're the real deal. So fake a descriptor here for + # consumption by FailureFatalCastableObjectUnwrapper. + unwrapThis = CGIndenter(CGGeneric( + str(CastableObjectUnwrapper( + FakeCastableDescriptor(self.descriptor), + "obj", "self", self.unwrapFailureCode)))) + return CGList([ self.getThis(), unwrapThis, + self.generate_code() ], "\n").define() + + def getThis(self): + return CGIndenter( + CGGeneric("js::RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));\n" + "if (!obj) {\n" + " return false;\n" + "}\n" + "\n" + "%s* self;" % self.descriptor.nativeType)) + + def generate_code(self): + assert(False) # Override me + +def MakeNativeName(name): + return name[0].upper() + name[1:] + +class CGGenericMethod(CGAbstractBindingMethod): + """ + A class for generating the C++ code for an IDL method.. + """ + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), Argument('unsigned', 'argc'), + Argument('JS::Value*', 'vp')] + CGAbstractBindingMethod.__init__(self, descriptor, 'genericMethod', args) + + def generate_code(self): + return CGIndenter(CGGeneric( + "const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n" + "JSJitMethodOp method = (JSJitMethodOp)info->op;\n" + "return method(cx, obj, self, argc, vp);")) + +class CGSpecializedMethod(CGAbstractStaticMethod): + """ + A class for generating the C++ code for a specialized method that the JIT + can call with lower overhead. + """ + def __init__(self, descriptor, method): + self.method = method + name = method.identifier.name + args = [Argument('JSContext*', 'cx'), Argument('JSHandleObject', 'obj'), + Argument('%s*' % descriptor.nativeType, 'self'), + Argument('unsigned', 'argc'), Argument('JS::Value*', 'vp')] + CGAbstractStaticMethod.__init__(self, descriptor, name, 'bool', args) + + def definition_body(self): + name = self.method.identifier.name + nativeName = MakeNativeName(self.descriptor.binaryNames.get(name, name)) + return CGMethodCall([], nativeName, self.method.isStatic(), + self.descriptor, self.method).define() + +class CGGenericGetter(CGAbstractBindingMethod): + """ + A class for generating the C++ code for an IDL attribute getter. + """ + def __init__(self, descriptor, lenientThis=False): + args = [Argument('JSContext*', 'cx'), Argument('unsigned', 'argc'), + Argument('JS::Value*', 'vp')] + if lenientThis: + name = "genericLenientGetter" + unwrapFailureCode = ( + "MOZ_ASSERT(!JS_IsExceptionPending(cx));\n" + "JS_SET_RVAL(cx, vp, JS::UndefinedValue());\n" + "return true;") + else: + name = "genericGetter" + unwrapFailureCode = None + CGAbstractBindingMethod.__init__(self, descriptor, name, args, + unwrapFailureCode) + + def generate_code(self): + return CGIndenter(CGGeneric( + "const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n" + "JSJitPropertyOp getter = info->op;\n" + "return getter(cx, obj, self, vp);")) + +class CGSpecializedGetter(CGAbstractStaticMethod): + """ + A class for generating the code for a specialized attribute getter + that the JIT can call with lower overhead. + """ + def __init__(self, descriptor, attr): + self.attr = attr + name = 'get_' + attr.identifier.name + args = [ Argument('JSContext*', 'cx'), + Argument('JSHandleObject', 'obj'), + Argument('%s*' % descriptor.nativeType, 'self'), + Argument('JS::Value*', 'vp') ] + CGAbstractStaticMethod.__init__(self, descriptor, name, "bool", args) + + def definition_body(self): + name = self.attr.identifier.name + nativeName = MakeNativeName(self.descriptor.binaryNames.get(name, name)) + # resultOutParam does not depend on whether resultAlreadyAddRefed is set + (_, resultOutParam) = getRetvalDeclarationForType(self.attr.type, + self.descriptor, + False) + infallible = ('infallible' in + self.descriptor.getExtendedAttributes(self.attr, + getter=True)) + if resultOutParam or self.attr.type.nullable() or not infallible: + nativeName = "Get" + nativeName + return CGIndenter(CGGetterCall(self.attr.type, nativeName, + self.descriptor, self.attr)).define() + +class CGGenericSetter(CGAbstractBindingMethod): + """ + A class for generating the C++ code for an IDL attribute setter. + """ + def __init__(self, descriptor, lenientThis=False): + args = [Argument('JSContext*', 'cx'), Argument('unsigned', 'argc'), + Argument('JS::Value*', 'vp')] + if lenientThis: + name = "genericLenientSetter" + unwrapFailureCode = ( + "MOZ_ASSERT(!JS_IsExceptionPending(cx));\n" + "return true;") + else: + name = "genericSetter" + unwrapFailureCode = None + CGAbstractBindingMethod.__init__(self, descriptor, name, args, + unwrapFailureCode) + + def generate_code(self): + return CGIndenter(CGGeneric( + "JS::Value* argv = JS_ARGV(cx, vp);\n" + "JS::Value undef = JS::UndefinedValue();\n" + "if (argc == 0) {\n" + " argv = &undef;\n" + "}\n" + "const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n" + "JSJitPropertyOp setter = info->op;\n" + "if (!setter(cx, obj, self, argv)) {\n" + " return false;\n" + "}\n" + "*vp = JSVAL_VOID;\n" + "return true;")) + +class CGSpecializedSetter(CGAbstractStaticMethod): + """ + A class for generating the code for a specialized attribute setter + that the JIT can call with lower overhead. + """ + def __init__(self, descriptor, attr): + self.attr = attr + name = 'set_' + attr.identifier.name + args = [ Argument('JSContext*', 'cx'), + Argument('JSHandleObject', 'obj'), + Argument('%s*' % descriptor.nativeType, 'self'), + Argument('JS::Value*', 'argv')] + CGAbstractStaticMethod.__init__(self, descriptor, name, "bool", args) + + def definition_body(self): + name = self.attr.identifier.name + nativeName = "Set" + MakeNativeName(self.descriptor.binaryNames.get(name, name)) + return CGIndenter(CGSetterCall(self.attr.type, nativeName, + self.descriptor, self.attr)).define() + +def memberIsCreator(member): + return member.getExtendedAttribute("Creator") is not None + +class CGMemberJITInfo(CGThing): + """ + A class for generating the JITInfo for a property that points to + our specialized getter and setter. + """ + def __init__(self, descriptor, member): + self.member = member + self.descriptor = descriptor + + def declare(self): + return "" + + def defineJitInfo(self, infoName, opName, infallible): + protoID = "prototypes::id::%s" % self.descriptor.name + depth = "PrototypeTraits<%s>::Depth" % protoID + failstr = "true" if infallible else "false" + return ("\n" + "const JSJitInfo %s = {\n" + " %s,\n" + " %s,\n" + " %s,\n" + " %s, /* isInfallible. False in setters. */\n" + " false /* isConstant. Only relevant for getters. */\n" + "};\n" % (infoName, opName, protoID, depth, failstr)) + + def define(self): + if self.member.isAttr(): + getterinfo = ("%s_getterinfo" % self.member.identifier.name) + getter = ("(JSJitPropertyOp)get_%s" % self.member.identifier.name) + getterinfal = "infallible" in self.descriptor.getExtendedAttributes(self.member, getter=True) + getterinfal = getterinfal and infallibleForMember(self.member, self.member.type, self.descriptor) + result = self.defineJitInfo(getterinfo, getter, getterinfal) + if not self.member.readonly: + setterinfo = ("%s_setterinfo" % self.member.identifier.name) + setter = ("(JSJitPropertyOp)set_%s" % self.member.identifier.name) + # Setters are always fallible, since they have to do a typed unwrap. + result += self.defineJitInfo(setterinfo, setter, False) + return result + if self.member.isMethod(): + methodinfo = ("%s_methodinfo" % self.member.identifier.name) + # Actually a JSJitMethodOp, but JSJitPropertyOp by struct definition. + method = ("(JSJitPropertyOp)%s" % self.member.identifier.name) + + # Methods are infallible if they are infallible, have no arguments + # to unwrap, and have a return type that's infallible to wrap up for + # return. + methodInfal = False + sigs = self.member.signatures() + if len(sigs) == 1: + # Don't handle overloading. If there's more than one signature, + # one of them must take arguments. + sig = sigs[0] + if len(sig[1]) == 0 and infallibleForMember(self.member, sig[0], self.descriptor): + # No arguments and infallible return boxing + methodInfal = True + + result = self.defineJitInfo(methodinfo, method, methodInfal) + return result + raise TypeError("Illegal member type to CGPropertyJITInfo") + +def getEnumValueName(value): + # Some enum values can be empty strings. Others might have weird + # characters in them. Deal with the former by returning "_empty", + # deal with possible name collisions from that by throwing if the + # enum value is actually "_empty", and throw on any value + # containing chars other than [a-z] or '-' for now. Replace '-' with '_'. + value = value.replace('-', '_') + if value == "_empty": + raise SyntaxError('"_empty" is not an IDL enum value we support yet') + if value == "": + return "_empty" + if not re.match("^[a-z_]+$", value): + raise SyntaxError('Enum value "' + value + '" contains characters ' + 'outside [a-z_]') + return MakeNativeName(value) + +class CGEnum(CGThing): + def __init__(self, enum): + CGThing.__init__(self) + self.enum = enum + + def declare(self): + return """ + enum valuelist { + %s + }; + + extern const EnumEntry strings[%d]; +""" % (",\n ".join(map(getEnumValueName, self.enum.values())), + len(self.enum.values()) + 1) + + def define(self): + return """ + const EnumEntry strings[%d] = { + %s, + { NULL, 0 } + }; +""" % (len(self.enum.values()) + 1, + ",\n ".join(['{"' + val + '", ' + str(len(val)) + '}' for val in self.enum.values()])) + +def getUnionAccessorSignatureType(type, descriptorProvider): + """ + Returns the types that are used in the getter and setter signatures for + union types + """ + if type.isArray(): + raise TypeError("Can't handle array arguments yet") + + if type.isSequence(): + nullable = type.nullable(); + if nullable: + type = type.inner.inner + else: + type = type.inner + (elementTemplate, elementDeclType, + elementHolderType, dealWithOptional) = getJSToNativeConversionTemplate( + type, descriptorProvider, isSequenceMember=True) + typeName = CGWrapper(elementDeclType, pre="Sequence< ", post=" >&") + if nullable: + typeName = CGWrapper(typeName, pre="Nullable< ", post=" >&") + + return typeName + + if type.isUnion(): + typeName = CGGeneric(type.name) + if type.nullable(): + typeName = CGWrapper(typeName, pre="Nullable< ", post=" >&") + + return typeName + + if type.isGeckoInterface(): + descriptor = descriptorProvider.getDescriptor( + type.unroll().inner.identifier.name) + typeName = CGGeneric(descriptor.nativeType) + # Allow null pointers for nullable types and old-binding classes + if type.nullable() or type.unroll().inner.isExternal(): + typeName = CGWrapper(typeName, post="*") + else: + typeName = CGWrapper(typeName, post="&") + return typeName + + if type.isSpiderMonkeyInterface(): + typeName = CGGeneric(type.name) + if type.nullable(): + typeName = CGWrapper(typeName, post="*") + else: + typeName = CGWrapper(typeName, post="&") + return typeName + + if type.isString(): + return CGGeneric("const nsAString&") + + if type.isEnum(): + if type.nullable(): + raise TypeError("We don't support nullable enumerated arguments or " + "union members yet") + return CGGeneric(type.inner.identifier.name) + + if type.isCallback(): + return CGGeneric("JSObject*") + + if type.isAny(): + return CGGeneric("JS::Value") + + if type.isObject(): + typeName = CGGeneric("JSObject") + if type.nullable(): + typeName = CGWrapper(typeName, post="*") + else: + typeName = CGWrapper(typeName, post="&") + return typeName + + if not type.isPrimitive(): + raise TypeError("Need native type for argument type '%s'" % str(type)) + + typeName = CGGeneric(builtinNames[type.tag()]) + if type.nullable(): + typeName = CGWrapper(typeName, pre="Nullable< ", post=" >&") + return typeName + +def getUnionTypeTemplateVars(type, descriptorProvider): + # For dictionaries and sequences we need to pass None as the failureCode + # for getJSToNativeConversionTemplate. + # Also, for dictionaries we would need to handle conversion of + # null/undefined to the dictionary correctly. + if type.isDictionary() or type.isSequence(): + raise TypeError("Can't handle dictionaries or sequences in unions") + + if type.isGeckoInterface(): + name = type.inner.identifier.name + elif type.isEnum(): + name = type.inner.identifier.name + elif type.isArray() or type.isSequence(): + name = str(type) + else: + name = type.name + + tryNextCode = """tryNext = true; +return true;""" + if type.isGeckoInterface(): + tryNextCode = ("""if (mUnion.mType != mUnion.eUninitialized) { + mUnion.Destroy%s(); +}""" % name) + tryNextCode + (template, declType, holderType, + dealWithOptional) = getJSToNativeConversionTemplate( + type, descriptorProvider, failureCode=tryNextCode, + isDefinitelyObject=True) + + # This is ugly, but UnionMember needs to call a constructor with no + # arguments so the type can't be const. + structType = declType.define() + if structType.startswith("const "): + structType = structType[6:] + externalType = getUnionAccessorSignatureType(type, descriptorProvider).define() + + if type.isObject(): + setter = CGGeneric("void SetToObject(JSObject* obj)\n" + "{\n" + " mUnion.mValue.mObject.SetValue() = obj;\n" + " mUnion.mType = mUnion.eObject;\n" + "}") + else: + jsConversion = string.Template(template).substitute( + { + "val": "value", + "valPtr": "pvalue", + "declName": "SetAs" + name + "()", + "holderName": "m" + name + "Holder" + } + ) + jsConversion = CGWrapper(CGGeneric(jsConversion), + post="\n" + "return true;") + setter = CGWrapper(CGIndenter(jsConversion), + pre="bool TrySetTo" + name + "(JSContext* cx, const JS::Value& value, JS::Value* pvalue, bool& tryNext)\n" + "{\n" + " tryNext = false;\n", + post="\n" + "}") + + return { + "name": name, + "structType": structType, + "externalType": externalType, + "setter": CGIndenter(setter).define(), + "holderType": holderType.define() if holderType else None + } + +def mapTemplate(template, templateVarArray): + return map(lambda v: string.Template(template).substitute(v), + templateVarArray) + +class CGUnionStruct(CGThing): + def __init__(self, type, descriptorProvider): + CGThing.__init__(self) + self.type = type.unroll() + self.descriptorProvider = descriptorProvider + + def declare(self): + templateVars = map(lambda t: getUnionTypeTemplateVars(t, self.descriptorProvider), + self.type.flatMemberTypes) + + callDestructors = [] + enumValues = [] + methods = [] + if self.type.hasNullableType: + callDestructors.append(" case eNull:\n" + " break;") + enumValues.append("eNull") + methods.append(""" bool IsNull() const + { + return mType == eNull; + }""") + + destructorTemplate = """ void Destroy${name}() + { + MOZ_ASSERT(Is${name}(), "Wrong type!"); + mValue.m${name}.Destroy(); + mType = eUninitialized; + }""" + destructors = mapTemplate(destructorTemplate, templateVars) + callDestructors.extend(mapTemplate(" case e${name}:\n" + " Destroy${name}();\n" + " break;", templateVars)) + enumValues.extend(mapTemplate("e${name}", templateVars)) + methodTemplate = """ bool Is${name}() const + { + return mType == e${name}; + } + ${externalType} GetAs${name}() const + { + MOZ_ASSERT(Is${name}(), "Wrong type!"); + // The cast to ${externalType} is needed to work around a bug in Apple's + // clang compiler, for some reason it doesn't call |S::operator T&| when + // casting S<T> to T& and T is forward declared. + return (${externalType})mValue.m${name}.Value(); + } + ${structType}& SetAs${name}() + { + mType = e${name}; + return mValue.m${name}.SetValue(); + }""" + methods.extend(mapTemplate(methodTemplate, templateVars)) + values = mapTemplate("UnionMember<${structType} > m${name};", templateVars) + return string.Template(""" +class ${structName} { +public: + ${structName}() : mType(eUninitialized) + { + } + ~${structName}() + { + switch (mType) { +${callDestructors} + case eUninitialized: + break; + } + } + +${methods} + +private: + friend class ${structName}Argument; + +${destructors} + + enum Type { + eUninitialized, + ${enumValues} + }; + union Value { + ${values} + }; + + Type mType; + Value mValue; +}; + +""").substitute( + { + "structName": self.type.__str__(), + "callDestructors": "\n".join(callDestructors), + "destructors": "\n".join(destructors), + "methods": "\n\n".join(methods), + "enumValues": ",\n ".join(enumValues), + "values": "\n ".join(values), + }) + + def define(self): + return """ +""" + +class CGUnionConversionStruct(CGThing): + def __init__(self, type, descriptorProvider): + CGThing.__init__(self) + self.type = type.unroll() + self.descriptorProvider = descriptorProvider + + def declare(self): + setters = [] + + if self.type.hasNullableType: + setters.append(""" bool SetNull() + { + mUnion.mType = mUnion.eNull; + return true; + }""") + + templateVars = map(lambda t: getUnionTypeTemplateVars(t, self.descriptorProvider), + self.type.flatMemberTypes) + structName = self.type.__str__() + + setters.extend(mapTemplate("${setter}", templateVars)) + private = "\n".join(mapTemplate(""" ${structType}& SetAs${name}() + { + mUnion.mType = mUnion.e${name}; + return mUnion.mValue.m${name}.SetValue(); + }""", templateVars)) + private += "\n\n" + holders = filter(lambda v: v["holderType"] is not None, templateVars) + if len(holders) > 0: + private += "\n".join(mapTemplate(" ${holderType} m${name}Holder;", holders)) + private += "\n\n" + private += " " + structName + "& mUnion;" + return string.Template(""" +class ${structName}Argument { +public: + ${structName}Argument(const ${structName}& aUnion) : mUnion(const_cast<${structName}&>(aUnion)) + { + } + +${setters} + +private: +${private} +}; +""").substitute({"structName": structName, + "setters": "\n\n".join(setters), + "private": private + }) + + def define(self): + return """ +""" + +class ClassItem: + """ Use with CGClass """ + def __init__(self, name, visibility): + self.name = name + self.visibility = visibility + def declare(self, cgClass): + assert False + def define(self, cgClass): + assert False + +class ClassBase(ClassItem): + def __init__(self, name, visibility='public'): + ClassItem.__init__(self, name, visibility) + def declare(self, cgClass): + return '%s %s' % (self.visibility, self.name) + def define(self, cgClass): + # Only in the header + return '' + +class ClassMethod(ClassItem): + def __init__(self, name, returnType, args, inline=False, static=False, + virtual=False, const=False, bodyInHeader=False, + templateArgs=None, visibility='public', body=None): + self.returnType = returnType + self.args = args + self.inline = inline or bodyInHeader + self.static = static + self.virtual = virtual + self.const = const + self.bodyInHeader = bodyInHeader + self.templateArgs = templateArgs + self.body = body + ClassItem.__init__(self, name, visibility) + + def getDecorators(self, declaring): + decorators = [] + if self.inline: + decorators.append('inline') + if declaring: + if self.static: + decorators.append('static') + if self.virtual: + decorators.append('virtual') + if decorators: + return ' '.join(decorators) + ' ' + return '' + + def getBody(self): + # Override me or pass a string to constructor + assert self.body is not None + return self.body + + def declare(self, cgClass): + templateClause = 'template <%s>\n' % ', '.join(self.templateArgs) \ + if self.bodyInHeader and self.templateArgs else '' + args = ', '.join([str(a) for a in self.args]) + if self.bodyInHeader: + body = CGIndenter(CGGeneric(self.getBody())).define() + body = '\n{\n' + body + '\n}' + else: + body = ';' + + return string.Template("""${templateClause}${decorators}${returnType} +${name}(${args})${const}${body} +""").substitute({ 'templateClause': templateClause, + 'decorators': self.getDecorators(True), + 'returnType': self.returnType, + 'name': self.name, + 'const': ' const' if self.const else '', + 'args': args, + 'body': body }) + + def define(self, cgClass): + if self.bodyInHeader: + return '' + + templateArgs = cgClass.templateArgs + if templateArgs: + if cgClass.templateSpecialization: + templateArgs = \ + templateArgs[len(cgClass.templateSpecialization):] + + if templateArgs: + templateClause = \ + 'template <%s>\n' % ', '.join([str(a) for a in templateArgs]) + else: + templateClause = '' + + args = ', '.join([str(a) for a in self.args]) + + body = CGIndenter(CGGeneric(self.getBody())).define() + + return string.Template("""${templateClause}${decorators}${returnType} +${className}::${name}(${args})${const} +{ +${body} +}\n +""").substitute({ 'templateClause': templateClause, + 'decorators': self.getDecorators(False), + 'returnType': self.returnType, + 'className': cgClass.getNameString(), + 'name': self.name, + 'args': args, + 'const': ' const' if self.const else '', + 'body': body }) + +class ClassConstructor(ClassItem): + """ + Used for adding a constructor to a CGClass. + + args is a list of Argument objects that are the arguments taken by the + constructor. + + inline should be True if the constructor should be marked inline. + + bodyInHeader should be True if the body should be placed in the class + declaration in the header. + + visibility determines the visibility of the constructor (public, + protected, private), defaults to private. + + baseConstructors is a list of strings containing calls to base constructors, + defaults to None. + + body contains a string with the code for the constructor, defaults to None. + """ + def __init__(self, args, inline=False, bodyInHeader=False, + visibility="private", baseConstructors=None, body=None): + self.args = args + self.inline = inline or bodyInHeader + self.bodyInHeader = bodyInHeader + self.baseConstructors = baseConstructors + self.body = body + ClassItem.__init__(self, None, visibility) + + def getDecorators(self, declaring): + decorators = [] + if self.inline and declaring: + decorators.append('inline') + if decorators: + return ' '.join(decorators) + ' ' + return '' + + def getInitializationList(self, cgClass): + items = [str(c) for c in self.baseConstructors] + for m in cgClass.members: + if not m.static: + initialize = m.getBody() + if initialize: + items.append(m.name + "(" + initialize + ")") + + if len(items) > 0: + return '\n : ' + ',\n '.join(items) + return '' + + def getBody(self): + assert self.body is not None + return self.body + + def declare(self, cgClass): + args = ', '.join([str(a) for a in self.args]) + if self.bodyInHeader: + body = ' ' + self.getBody(); + body = stripTrailingWhitespace(body.replace('\n', '\n ')) + if len(body) > 0: + body += '\n' + body = self.getInitializationList(cgClass) + '\n{\n' + body + '}' + else: + body = ';' + + return string.Template("""${decorators}${className}(${args})${body} +""").substitute({ 'decorators': self.getDecorators(True), + 'className': cgClass.getNameString(), + 'args': args, + 'body': body }) + + def define(self, cgClass): + if self.bodyInHeader: + return '' + + args = ', '.join([str(a) for a in self.args]) + + body = ' ' + self.getBody() + body = '\n' + stripTrailingWhitespace(body.replace('\n', '\n ')) + if len(body) > 0: + body += '\n' + + return string.Template("""${decorators} +${className}::${className}(${args})${initializationList} +{${body}}\n +""").substitute({ 'decorators': self.getDecorators(False), + 'className': cgClass.getNameString(), + 'args': args, + 'initializationList': self.getInitializationList(cgClass), + 'body': body }) + +class ClassMember(ClassItem): + def __init__(self, name, type, visibility="private", static=False, + body=None): + self.type = type; + self.static = static + self.body = body + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return '%s%s %s;\n' % ('static ' if self.static else '', self.type, + self.name) + + def define(self, cgClass): + if not self.static: + return '' + if self.body: + body = " = " + self.body + else: + body = "" + return '%s %s::%s%s;\n' % (self.type, cgClass.getNameString(), + self.name, body) + +class ClassTypedef(ClassItem): + def __init__(self, name, type, visibility="public"): + self.type = type + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return 'typedef %s %s;\n' % (self.type, self.name) + + def define(self, cgClass): + # Only goes in the header + return '' + +class ClassEnum(ClassItem): + def __init__(self, name, entries, values=None, visibility="public"): + self.entries = entries + self.values = values + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + entries = [] + for i in range(0, len(self.entries)): + if i >= len(self.values): + entry = '%s' % self.entries[i] + else: + entry = '%s = %s' % (self.entries[i], self.values[i]) + entries.append(entry) + name = '' if not self.name else ' ' + self.name + return 'enum%s\n{\n %s\n};\n' % (name, ',\n '.join(entries)) + + def define(self, cgClass): + # Only goes in the header + return '' + +class CGClass(CGThing): + def __init__(self, name, bases=[], members=[], constructors=[], methods=[], + typedefs = [], enums=[], templateArgs=[], + templateSpecialization=[], isStruct=False, indent=''): + CGThing.__init__(self) + self.name = name + self.bases = bases + self.members = members + self.constructors = constructors + self.methods = methods + self.typedefs = typedefs + self.enums = enums + self.templateArgs = templateArgs + self.templateSpecialization = templateSpecialization + self.isStruct = isStruct + self.indent = indent + self.defaultVisibility ='public' if isStruct else 'private' + + def getNameString(self): + className = self.name + if self.templateSpecialization: + className = className + \ + '<%s>' % ', '.join([str(a) for a + in self.templateSpecialization]) + return className + + def declare(self): + result = '' + if self.templateArgs: + templateArgs = [str(a) for a in self.templateArgs] + templateArgs = templateArgs[len(self.templateSpecialization):] + result = result + self.indent + 'template <%s>\n' \ + % ','.join([str(a) for a in templateArgs]) + + type = 'struct' if self.isStruct else 'class' + + if self.templateSpecialization: + specialization = \ + '<%s>' % ', '.join([str(a) for a in self.templateSpecialization]) + else: + specialization = '' + + result = result + '%s%s %s%s' \ + % (self.indent, type, self.name, specialization) + + if self.bases: + result = result + ' : %s' % ', '.join([d.declare(self) for d in self.bases]) + + result = result + '\n%s{\n' % self.indent + + def declareMembers(cgClass, memberList, defaultVisibility, itemCount, + separator=''): + members = { 'private': [], 'protected': [], 'public': [] } + + for member in memberList: + members[member.visibility].append(member) + + + if defaultVisibility == 'public': + order = [ 'public', 'protected', 'private' ] + else: + order = [ 'private', 'protected', 'public' ] + + result = '' + + lastVisibility = defaultVisibility + for visibility in order: + list = members[visibility] + if list: + if visibility != lastVisibility: + if itemCount: + result = result + '\n' + result = result + visibility + ':\n' + itemCount = 0 + for member in list: + if itemCount != 0: + result = result + separator + declaration = member.declare(cgClass) + declaration = CGIndenter(CGGeneric(declaration)).define() + result = result + declaration + itemCount = itemCount + 1 + lastVisibility = visibility + return (result, lastVisibility, itemCount) + + order = [(self.enums, ''), (self.typedefs, ''), (self.members, ''), + (self.constructors, '\n'), (self.methods, '\n')] + + lastVisibility = self.defaultVisibility + itemCount = 0 + for (memberList, separator) in order: + (memberString, lastVisibility, itemCount) = \ + declareMembers(self, memberList, lastVisibility, itemCount, + separator) + if self.indent: + memberString = CGIndenter(CGGeneric(memberString), + len(self.indent)).define() + result = result + memberString + + result = result + self.indent + '};\n' + return result + + def define(self): + def defineMembers(cgClass, memberList, itemCount, separator=''): + result = '' + for member in memberList: + if itemCount != 0: + result = result + separator + result = result + member.define(cgClass) + itemCount = itemCount + 1 + return (result, itemCount) + + order = [(self.members, '\n'), (self.constructors, '\n'), + (self.methods, '\n')] + + result = '' + itemCount = 0 + for (memberList, separator) in order: + (memberString, itemCount) = defineMembers(self, memberList, + itemCount, separator) + result = result + memberString + return result + +class CGResolveOwnProperty(CGAbstractMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'wrapper'), + Argument('jsid', 'id'), Argument('bool', 'set'), + Argument('JSPropertyDescriptor*', 'desc')] + CGAbstractMethod.__init__(self, descriptor, "ResolveOwnProperty", "bool", args) + def definition_body(self): + return """ JSObject* obj = wrapper; + if (xpc::WrapperFactory::IsXrayWrapper(obj)) { + obj = js::UnwrapObject(obj); + } + // We rely on getOwnPropertyDescriptor not shadowing prototype properties by named + // properties. If that changes we'll need to filter here. + return js::GetProxyHandler(obj)->getOwnPropertyDescriptor(cx, wrapper, id, set, desc); +""" + +class CGEnumerateOwnProperties(CGAbstractMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'wrapper'), + Argument('JS::AutoIdVector&', 'props')] + CGAbstractMethod.__init__(self, descriptor, "EnumerateOwnProperties", "bool", args) + def definition_body(self): + return """ JSObject* obj = wrapper; + if (xpc::WrapperFactory::IsXrayWrapper(obj)) { + obj = js::UnwrapObject(obj); + } + // We rely on getOwnPropertyNames not shadowing prototype properties by named + // properties. If that changes we'll need to filter here. + return js::GetProxyHandler(obj)->getOwnPropertyNames(cx, wrapper, props); +""" + +class CGXrayHelper(CGAbstractMethod): + def __init__(self, descriptor, name, args, properties): + CGAbstractMethod.__init__(self, descriptor, name, "bool", args) + self.properties = properties + + def definition_body(self): + varNames = self.properties.variableNames(True) + + methods = self.properties.methods + if methods.hasNonChromeOnly() or methods.hasChromeOnly(): + methodArgs = """// %(methods)s has an end-of-list marker at the end that we ignore +%(methods)s, %(methods)s_ids, %(methods)s_specs, ArrayLength(%(methods)s) - 1""" % varNames + else: + methodArgs = "NULL, NULL, NULL, 0" + methodArgs = CGGeneric(methodArgs) + + attrs = self.properties.attrs + if attrs.hasNonChromeOnly() or attrs.hasChromeOnly(): + attrArgs = """// %(attrs)s has an end-of-list marker at the end that we ignore +%(attrs)s, %(attrs)s_ids, %(attrs)s_specs, ArrayLength(%(attrs)s) - 1""" % varNames + else: + attrArgs = "NULL, NULL, NULL, 0" + attrArgs = CGGeneric(attrArgs) + + consts = self.properties.consts + if consts.hasNonChromeOnly() or consts.hasChromeOnly(): + constArgs = """// %(consts)s has an end-of-list marker at the end that we ignore +%(consts)s, %(consts)s_ids, %(consts)s_specs, ArrayLength(%(consts)s) - 1""" % varNames + else: + constArgs = "NULL, NULL, NULL, 0" + constArgs = CGGeneric(constArgs) + + prefixArgs = CGGeneric(self.getPrefixArgs()) + + return CGIndenter( + CGWrapper(CGList([prefixArgs, methodArgs, attrArgs, constArgs], ",\n"), + pre=("return Xray%s(" % self.name), + post=");", + reindent=True)).define() + +class CGResolveProperty(CGXrayHelper): + def __init__(self, descriptor, properties): + args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'wrapper'), + Argument('jsid', 'id'), Argument('bool', 'set'), + Argument('JSPropertyDescriptor*', 'desc')] + CGXrayHelper.__init__(self, descriptor, "ResolveProperty", args, + properties) + + def getPrefixArgs(self): + return "cx, wrapper, id, desc" + + +class CGEnumerateProperties(CGXrayHelper): + def __init__(self, descriptor, properties): + args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'wrapper'), + Argument('JS::AutoIdVector&', 'props')] + CGXrayHelper.__init__(self, descriptor, "EnumerateProperties", args, + properties) + + def getPrefixArgs(self): + return "props" + +class CGPrototypeTraitsClass(CGClass): + def __init__(self, descriptor, indent=''): + templateArgs = [Argument('prototypes::ID', 'PrototypeID')] + templateSpecialization = ['prototypes::id::' + descriptor.name] + enums = [ClassEnum('', ['Depth'], + [descriptor.interface.inheritanceDepth()])] + typedefs = [ClassTypedef('NativeType', descriptor.nativeType)] + CGClass.__init__(self, 'PrototypeTraits', indent=indent, + templateArgs=templateArgs, + templateSpecialization=templateSpecialization, + enums=enums, typedefs=typedefs, isStruct=True) + +class CGPrototypeIDMapClass(CGClass): + def __init__(self, descriptor, indent=''): + templateArgs = [Argument('class', 'ConcreteClass')] + templateSpecialization = [descriptor.nativeType] + enums = [ClassEnum('', ['PrototypeID'], + ['prototypes::id::' + descriptor.name])] + CGClass.__init__(self, 'PrototypeIDMap', indent=indent, + templateArgs=templateArgs, + templateSpecialization=templateSpecialization, + enums=enums, isStruct=True) + +class CGClassForwardDeclare(CGThing): + def __init__(self, name, isStruct=False): + CGThing.__init__(self) + self.name = name + self.isStruct = isStruct + def declare(self): + type = 'struct' if self.isStruct else 'class' + return '%s %s;\n' % (type, self.name) + def define(self): + # Header only + return '' + +class CGProxySpecialOperation(CGPerSignatureCall): + """ + Base class for classes for calling an indexed or named special operation + (don't use this directly, use the derived classes below). + """ + def __init__(self, descriptor, operation): + nativeName = MakeNativeName(descriptor.binaryNames.get(operation, operation)) + operation = descriptor.operations[operation] + assert len(operation.signatures()) == 1 + signature = operation.signatures()[0] + extendedAttributes = descriptor.getExtendedAttributes(operation) + + (returnType, arguments) = signature + + # We pass len(arguments) as the final argument so that the + # CGPerSignatureCall won't do any argument conversion of its own. + CGPerSignatureCall.__init__(self, returnType, "", arguments, nativeName, + False, descriptor, operation, + len(arguments)) + + if operation.isSetter() or operation.isCreator(): + # arguments[0] is the index or name of the item that we're setting. + argument = arguments[1] + template = getJSToNativeConversionTemplate(argument.type, descriptor, + treatNullAs=argument.treatNullAs, + treatUndefinedAs=argument.treatUndefinedAs) + templateValues = { + "declName": argument.identifier.name, + "holderName": argument.identifier.name + "_holder", + "val": "desc->value", + "valPtr": "&desc->value" + } + self.cgRoot.prepend(instantiateJSToNativeConversionTemplate(template, templateValues)) + elif operation.isGetter(): + self.cgRoot.prepend(CGGeneric("bool found;")) + + def getArguments(self): + args = [(a, a.identifier.name) for a in self.arguments] + if self.idlNode.isGetter(): + args.append((FakeArgument(BuiltinTypes[IDLBuiltinType.Types.boolean], + self.idlNode), + "found")) + return args + + def wrap_return_value(self): + if not self.idlNode.isGetter() or self.templateValues is None: + return "" + + wrap = CGGeneric(wrapForType(self.returnType, self.descriptor, self.templateValues)) + wrap = CGIfWrapper(wrap, "found") + return "\n" + wrap.define() + +class CGProxyIndexedGetter(CGProxySpecialOperation): + """ + Class to generate a call to an indexed getter. If templateValues is not None + the returned value will be wrapped with wrapForType using templateValues. + """ + def __init__(self, descriptor, templateValues=None): + self.templateValues = templateValues + CGProxySpecialOperation.__init__(self, descriptor, 'IndexedGetter') + +class CGProxyIndexedSetter(CGProxySpecialOperation): + """ + Class to generate a call to an indexed setter. + """ + def __init__(self, descriptor): + CGProxySpecialOperation.__init__(self, descriptor, 'IndexedSetter') + +class CGProxyNamedGetter(CGProxySpecialOperation): + """ + Class to generate a call to an named getter. If templateValues is not None + the returned value will be wrapped with wrapForType using templateValues. + """ + def __init__(self, descriptor, templateValues=None): + self.templateValues = templateValues + CGProxySpecialOperation.__init__(self, descriptor, 'NamedGetter') + +class CGProxyNamedSetter(CGProxySpecialOperation): + """ + Class to generate a call to a named setter. + """ + def __init__(self, descriptor): + CGProxySpecialOperation.__init__(self, descriptor, 'NamedSetter') + +class CGProxyIsProxy(CGAbstractMethod): + def __init__(self, descriptor): + args = [Argument('JSObject*', 'obj')] + CGAbstractMethod.__init__(self, descriptor, "IsProxy", "bool", args, alwaysInline=True) + def declare(self): + return "" + def definition_body(self): + return " return js::IsProxy(obj) && js::GetProxyHandler(obj) == DOMProxyHandler::getInstance();" + +class CGProxyUnwrap(CGAbstractMethod): + def __init__(self, descriptor): + args = [Argument('JSObject*', 'obj')] + CGAbstractMethod.__init__(self, descriptor, "UnwrapProxy", descriptor.nativeType + '*', args, alwaysInline=True) + def declare(self): + return "" + def definition_body(self): + return """ if (xpc::WrapperFactory::IsXrayWrapper(obj)) { + obj = js::UnwrapObject(obj); + } + MOZ_ASSERT(IsProxy(obj)); + return static_cast<%s*>(js::GetProxyPrivate(obj).toPrivate());""" % (self.descriptor.nativeType) + +class CGDOMJSProxyHandlerDOMClass(CGThing): + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + def declare(self): + return "extern const DOMClass Class;\n" + def define(self): + return """ +const DOMClass Class = """ + DOMClass(self.descriptor) + """; + +""" + +class CGDOMJSProxyHandler_CGDOMJSProxyHandler(ClassConstructor): + def __init__(self): + ClassConstructor.__init__(self, [], inline=True, visibility="private", + baseConstructors=["mozilla::dom::DOMProxyHandler(Class)"], + body="") + +class CGDOMJSProxyHandler_getOwnPropertyDescriptor(ClassMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'proxy'), + Argument('jsid', 'id'), Argument('bool', 'set'), + Argument('JSPropertyDescriptor*', 'desc')] + ClassMethod.__init__(self, "getOwnPropertyDescriptor", "bool", args) + self.descriptor = descriptor + def getBody(self): + indexedGetter = self.descriptor.operations['IndexedGetter'] + indexedSetter = self.descriptor.operations['IndexedSetter'] + + setOrIndexedGet = "" + if indexedGetter or indexedSetter: + setOrIndexedGet += "int32_t index = GetArrayIndexFromId(cx, id);\n" + + if indexedGetter: + readonly = toStringBool(self.descriptor.operations['IndexedSetter'] is None) + fillDescriptor = "FillPropertyDescriptor(desc, proxy, %s);\nreturn true;" % readonly + templateValues = {'jsvalRef': 'desc->value', 'jsvalPtr': '&desc->value', + 'obj': 'proxy', 'successCode': fillDescriptor} + get = ("if (index >= 0) {\n" + + " %s* self = UnwrapProxy(proxy);\n" + + CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define() + "\n" + + "}\n") % (self.descriptor.nativeType) + + if indexedSetter or self.descriptor.operations['NamedSetter']: + setOrIndexedGet += "if (set) {\n" + if indexedSetter: + setOrIndexedGet += (" if (index >= 0) {\n") + if not 'IndexedCreator' in self.descriptor.operations: + # FIXME need to check that this is a 'supported property index' + assert False + setOrIndexedGet += (" FillPropertyDescriptor(desc, proxy, JSVAL_VOID, false);\n" + + " return true;\n" + + " }\n") + if self.descriptor.operations['NamedSetter']: + setOrIndexedGet += " if (JSID_IS_STRING(id)) {\n" + if not 'NamedCreator' in self.descriptor.operations: + # FIXME need to check that this is a 'supported property name' + assert False + setOrIndexedGet += (" FillPropertyDescriptor(desc, proxy, JSVAL_VOID, false);\n" + + " return true;\n" + + " }\n") + setOrIndexedGet += "}" + if indexedGetter: + setOrIndexedGet += (" else {\n" + + CGIndenter(CGGeneric(get)).define() + + "}") + setOrIndexedGet += "\n\n" + elif indexedGetter: + setOrIndexedGet += ("if (!set) {\n" + + CGIndenter(CGGeneric(get)).define() + + "}\n\n") + + namedGetter = self.descriptor.operations['NamedGetter'] + if namedGetter: + readonly = toStringBool(self.descriptor.operations['NamedSetter'] is None) + fillDescriptor = "FillPropertyDescriptor(desc, proxy, %s);\nreturn true;" % readonly + templateValues = {'jsvalRef': 'desc->value', 'jsvalPtr': '&desc->value', + 'obj': 'proxy', 'successCode': fillDescriptor} + # Once we start supporting OverrideBuiltins we need to make + # ResolveOwnProperty or EnumerateOwnProperties filter out named + # properties that shadow prototype properties. + namedGet = ("\n" + + "if (!set && JSID_IS_STRING(id) && !HasPropertyOnPrototype(cx, proxy, this, id)) {\n" + + " JS::Value nameVal = STRING_TO_JSVAL(JSID_TO_STRING(id));\n" + + " FakeDependentString name;\n" + " if (!ConvertJSValueToString(cx, nameVal, &nameVal,\n" + + " eStringify, eStringify, name)) {\n" + + " return false;\n" + + " }\n" + + "\n" + + " %s* self = UnwrapProxy(proxy);\n" + + CGIndenter(CGProxyNamedGetter(self.descriptor, templateValues)).define() + "\n" + + "}\n") % (self.descriptor.nativeType) + else: + namedGet = "" + + return setOrIndexedGet + """JSObject* expando; +if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = GetExpandoObject(proxy))) { + unsigned flags = (set ? JSRESOLVE_ASSIGNING : 0) | JSRESOLVE_QUALIFIED; + if (!JS_GetPropertyDescriptorById(cx, expando, id, flags, desc)) { + return false; + } + if (desc->obj) { + // Pretend the property lives on the wrapper. + desc->obj = proxy; + return true; + } +} +""" + namedGet + """ +desc->obj = NULL; +return true;""" + +class CGDOMJSProxyHandler_defineProperty(ClassMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'proxy'), + Argument('jsid', 'id'), + Argument('JSPropertyDescriptor*', 'desc')] + ClassMethod.__init__(self, "defineProperty", "bool", args) + self.descriptor = descriptor + def getBody(self): + set = "" + + indexedSetter = self.descriptor.operations['IndexedSetter'] + if indexedSetter: + if not (self.descriptor.operations['IndexedCreator'] is indexedSetter): + raise TypeError("Can't handle creator that's different from the setter") + set += ("int32_t index = GetArrayIndexFromId(cx, id);\n" + + "if (index >= 0) {\n" + + " %s* self = UnwrapProxy(proxy);\n" + + CGIndenter(CGProxyIndexedSetter(self.descriptor)).define() + + " return true;\n" + + "}\n") % (self.descriptor.nativeType) + elif self.descriptor.operations['IndexedGetter']: + set += ("if (GetArrayIndexFromId(cx, id) >= 0) {\n" + + " return ThrowErrorMessage(cx, MSG_NO_PROPERTY_SETTER, \"%s\");\n" + + "}\n") % self.descriptor.name + + namedSetter = self.descriptor.operations['NamedSetter'] + if namedSetter: + if not self.descriptor.operations['NamedCreator'] is namedSetter: + raise TypeError("Can't handle creator that's different from the setter") + set += ("if (JSID_IS_STRING(id)) {\n" + + " JS::Value nameVal = STRING_TO_JSVAL(JSID_TO_STRING(id));\n" + + " FakeDependentString name;\n" + " if (!ConvertJSValueToString(cx, nameVal, &nameVal,\n" + + " eStringify, eStringify, name)) {\n" + + " return false;\n" + + " }\n" + + "\n" + + " %s* self = UnwrapProxy(proxy);\n" + + CGIndenter(CGProxyNamedSetter(self.descriptor)).define() + "\n" + + "}\n") % (self.descriptor.nativeType) + elif self.descriptor.operations['NamedGetter']: + set += ("if (JSID_IS_STRING(id)) {\n" + + " JS::Value nameVal = STRING_TO_JSVAL(JSID_TO_STRING(id));\n" + + " FakeDependentString name;\n" + " if (!ConvertJSValueToString(cx, nameVal, &nameVal,\n" + + " eStringify, eStringify, name)) {\n" + + " return false;\n" + + " }\n" + + " %s* self = UnwrapProxy(proxy);\n" + + CGIndenter(CGProxyNamedGetter(self.descriptor)).define() + + " if (found) {\n" + " return ThrowErrorMessage(cx, MSG_NO_PROPERTY_SETTER, \"%s\");\n" + + " }\n" + + " return true;\n" + "}\n") % (self.descriptor.nativeType, self.descriptor.name) + return set + """return mozilla::dom::DOMProxyHandler::defineProperty(%s);""" % ", ".join(a.name for a in self.args) + +class CGDOMJSProxyHandler_getOwnPropertyNames(ClassMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'proxy'), + Argument('JS::AutoIdVector&', 'props')] + ClassMethod.__init__(self, "getOwnPropertyNames", "bool", args) + self.descriptor = descriptor + def getBody(self): + indexedGetter = self.descriptor.operations['IndexedGetter'] + if indexedGetter: + addIndices = """uint32_t length = UnwrapProxy(proxy)->Length(); +MOZ_ASSERT(int32_t(length) >= 0); +for (int32_t i = 0; i < int32_t(length); ++i) { + if (!props.append(INT_TO_JSID(i))) { + return false; + } +} + +""" + else: + addIndices = "" + + return addIndices + """JSObject* expando; +if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = DOMProxyHandler::GetExpandoObject(proxy)) && + !js::GetPropertyNames(cx, expando, JSITER_OWNONLY | JSITER_HIDDEN, &props)) { + return false; +} + +// FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=772869 Add named items +return true;""" + +class CGDOMJSProxyHandler_hasOwn(ClassMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'proxy'), + Argument('jsid', 'id'), Argument('bool*', 'bp')] + ClassMethod.__init__(self, "hasOwn", "bool", args) + self.descriptor = descriptor + def getBody(self): + indexedGetter = self.descriptor.operations['IndexedGetter'] + if indexedGetter: + indexed = ("int32_t index = GetArrayIndexFromId(cx, id);\n" + + "if (index >= 0) {\n" + + " %s* self = UnwrapProxy(proxy);\n" + + CGIndenter(CGProxyIndexedGetter(self.descriptor)).define() + "\n" + + " *bp = found;\n" + + " return true;\n" + + "}\n\n") % (self.descriptor.nativeType) + else: + indexed = "" + + namedGetter = self.descriptor.operations['NamedGetter'] + if namedGetter: + named = ("if (JSID_IS_STRING(id) && !HasPropertyOnPrototype(cx, proxy, this, id)) {\n" + + " jsval nameVal = STRING_TO_JSVAL(JSID_TO_STRING(id));\n" + + " FakeDependentString name;\n" + " if (!ConvertJSValueToString(cx, nameVal, &nameVal,\n" + + " eStringify, eStringify, name)) {\n" + + " return false;\n" + + " }\n" + + "\n" + + " %s* self = UnwrapProxy(proxy);\n" + + CGIndenter(CGProxyNamedGetter(self.descriptor)).define() + "\n" + + " *bp = found;\n" + " return true;\n" + "}\n" + + "\n") % (self.descriptor.nativeType) + else: + named = "" + + return indexed + """JSObject* expando = GetExpandoObject(proxy); +if (expando) { + JSBool b = true; + JSBool ok = JS_HasPropertyById(cx, expando, id, &b); + *bp = !!b; + if (!ok || *bp) { + return ok; + } +} + +""" + named + """*bp = false; +return true;""" + +class CGDOMJSProxyHandler_get(ClassMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'proxy'), + Argument('JSObject*', 'receiver'), Argument('jsid', 'id'), + Argument('JS::Value*', 'vp')] + ClassMethod.__init__(self, "get", "bool", args) + self.descriptor = descriptor + def getBody(self): + getFromExpando = """JSObject* expando = DOMProxyHandler::GetExpandoObject(proxy); +if (expando) { + JSBool hasProp; + if (!JS_HasPropertyById(cx, expando, id, &hasProp)) { + return false; + } + + if (hasProp) { + return JS_GetPropertyById(cx, expando, id, vp); + } +}""" + + templateValues = {'jsvalRef': '*vp', 'jsvalPtr': 'vp', 'obj': 'proxy'} + + indexedGetter = self.descriptor.operations['IndexedGetter'] + if indexedGetter: + getIndexedOrExpando = ("int32_t index = GetArrayIndexFromId(cx, id);\n" + + "if (index >= 0) {\n" + + " %s* self = UnwrapProxy(proxy);\n" + + CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define()) % (self.descriptor.nativeType) + getIndexedOrExpando += """ + // Even if we don't have this index, we don't forward the + // get on to our expando object. +} else { + %s +} +""" % (stripTrailingWhitespace(getFromExpando.replace('\n', '\n '))) + else: + getIndexedOrExpando = getFromExpando + "\n" + + namedGetter = self.descriptor.operations['NamedGetter'] + if namedGetter: + getNamed = ("if (JSID_IS_STRING(id)) {\n" + + " JS::Value nameVal = STRING_TO_JSVAL(JSID_TO_STRING(id));\n" + + " FakeDependentString name;\n" + " if (!ConvertJSValueToString(cx, nameVal, &nameVal,\n" + + " eStringify, eStringify, name)) {\n" + + " return false;\n" + + " }\n" + + "\n" + + " %s* self = UnwrapProxy(proxy);\n" + + CGIndenter(CGProxyNamedGetter(self.descriptor, templateValues)).define() + + "}\n") % (self.descriptor.nativeType) + else: + getNamed = "" + + return """MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), + "Should not have a XrayWrapper here"); + +%s +bool found; +if (!GetPropertyOnPrototype(cx, proxy, id, &found, vp)) { + return false; +} + +if (found) { + return true; +} +%s +vp->setUndefined(); +return true;""" % (getIndexedOrExpando, getNamed) + +class CGDOMJSProxyHandler_obj_toString(ClassMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'proxy')] + ClassMethod.__init__(self, "obj_toString", "JSString*", args) + self.descriptor = descriptor + def getBody(self): + stringifier = self.descriptor.operations['Stringifier'] + if stringifier: + name = stringifier.identifier.name + nativeName = MakeNativeName(self.descriptor.binaryNames.get(name, name)) + signature = stringifier.signatures()[0] + returnType = signature[0] + extendedAttributes = self.descriptor.getExtendedAttributes(stringifier) + infallible = 'infallible' in extendedAttributes + if not infallible: + error = CGGeneric( + ('ThrowMethodFailedWithDetails(cx, rv, "%s", "toString");\n' + + "return NULL;") % self.descriptor.interface.identifier.name) + else: + error = None + call = CGCallGenerator(error, [], "", returnType, extendedAttributes, self.descriptor, nativeName, False, object="UnwrapProxy(proxy)") + return call.define() + """ + +JSString* jsresult; +return xpc_qsStringToJsstring(cx, result, &jsresult) ? jsresult : NULL;""" + + return "return mozilla::dom::DOMProxyHandler::obj_toString(cx, \"%s\");" % self.descriptor.name + +class CGDOMJSProxyHandler_finalize(ClassMethod): + def __init__(self, descriptor): + args = [Argument('JSFreeOp*', 'fop'), Argument('JSObject*', 'proxy')] + ClassMethod.__init__(self, "finalize", "void", args) + self.descriptor = descriptor + def getBody(self): + return ("%s self = UnwrapProxy(proxy);\n\n" % (self.descriptor.nativeType + "*") + + finalizeHook(self.descriptor, FINALIZE_HOOK_NAME, self.args[0].name)) + +class CGDOMJSProxyHandler_getElementIfPresent(ClassMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'proxy'), + Argument('JSObject*', 'receiver'), + Argument('uint32_t', 'index'), + Argument('JS::Value*', 'vp'), Argument('bool*', 'present')] + ClassMethod.__init__(self, "getElementIfPresent", "bool", args) + self.descriptor = descriptor + def getBody(self): + indexedGetter = self.descriptor.operations['IndexedGetter'] + if indexedGetter: + successCode = """*present = found; +return true;""" + templateValues = {'jsvalRef': '*vp', 'jsvalPtr': 'vp', + 'obj': 'proxy', 'successCode': successCode} + get = ("%s* self = UnwrapProxy(proxy);\n" + + CGProxyIndexedGetter(self.descriptor, templateValues).define() + "\n" + "// We skip the expando object if there is an indexed getter.\n" + + "\n") % (self.descriptor.nativeType) + else: + get = """ + +JSObject* expando = GetExpandoObject(proxy); +if (expando) { + JSBool isPresent; + if (!JS_GetElementIfPresent(cx, expando, index, expando, vp, &isPresent)) { + return false; + } + if (isPresent) { + *present = true; + return true; + } +} +""" + + return """MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), + "Should not have a XrayWrapper here"); + +""" + get + """ +// No need to worry about name getters here, so just check the proto. + +JSObject *proto; +if (!js::GetObjectProto(cx, proxy, &proto)) { + return false; +} +if (proto) { + JSBool isPresent; + if (!JS_GetElementIfPresent(cx, proto, index, proxy, vp, &isPresent)) { + return false; + } + *present = isPresent; + return true; +} + +*present = false; +// Can't Debug_SetValueRangeToCrashOnTouch because it's not public +return true;""" + +class CGDOMJSProxyHandler_getInstance(ClassMethod): + def __init__(self): + ClassMethod.__init__(self, "getInstance", "DOMProxyHandler*", [], static=True) + def getBody(self): + return """static DOMProxyHandler instance; +return &instance;""" + +class CGDOMJSProxyHandler(CGClass): + def __init__(self, descriptor): + constructors = [CGDOMJSProxyHandler_CGDOMJSProxyHandler()] + methods = [CGDOMJSProxyHandler_getOwnPropertyDescriptor(descriptor)] + if descriptor.operations['IndexedSetter'] or descriptor.operations['NamedSetter']: + methods.append(CGDOMJSProxyHandler_defineProperty(descriptor)) + methods.extend([CGDOMJSProxyHandler_getOwnPropertyNames(descriptor), + CGDOMJSProxyHandler_hasOwn(descriptor), + CGDOMJSProxyHandler_get(descriptor), + CGDOMJSProxyHandler_obj_toString(descriptor), + CGDOMJSProxyHandler_finalize(descriptor), + CGDOMJSProxyHandler_getElementIfPresent(descriptor), + CGDOMJSProxyHandler_getInstance()]) + CGClass.__init__(self, 'DOMProxyHandler', + bases=[ClassBase('mozilla::dom::DOMProxyHandler')], + constructors=constructors, + methods=methods) + +def stripTrailingWhitespace(text): + tail = '\n' if text.endswith('\n') else '' + lines = text.splitlines() + for i in range(len(lines)): + lines[i] = lines[i].rstrip() + return '\n'.join(lines) + tail + +class CGDescriptor(CGThing): + def __init__(self, descriptor): + CGThing.__init__(self) + + assert not descriptor.concrete or descriptor.interface.hasInterfacePrototypeObject() + + cgThings = [] + if descriptor.interface.hasInterfacePrototypeObject(): + (hasMethod, hasGetter, hasLenientGetter, + hasSetter, hasLenientSetter) = False, False, False, False, False + for m in descriptor.interface.members: + if m.isMethod() and not m.isStatic() and not m.isIdentifierLess(): + cgThings.append(CGSpecializedMethod(descriptor, m)) + cgThings.append(CGMemberJITInfo(descriptor, m)) + hasMethod = True + elif m.isAttr(): + cgThings.append(CGSpecializedGetter(descriptor, m)) + if m.hasLenientThis(): + hasLenientGetter = True + else: + hasGetter = True + if not m.readonly: + cgThings.append(CGSpecializedSetter(descriptor, m)) + if m.hasLenientThis(): + hasLenientSetter = True + else: + hasSetter = True + cgThings.append(CGMemberJITInfo(descriptor, m)) + if hasMethod: cgThings.append(CGGenericMethod(descriptor)) + if hasGetter: cgThings.append(CGGenericGetter(descriptor)) + if hasLenientGetter: cgThings.append(CGGenericGetter(descriptor, + lenientThis=True)) + if hasSetter: cgThings.append(CGGenericSetter(descriptor)) + if hasLenientSetter: cgThings.append(CGGenericSetter(descriptor, + lenientThis=True)) + + if descriptor.concrete and not descriptor.proxy: + if not descriptor.workers and descriptor.wrapperCache: + cgThings.append(CGAddPropertyHook(descriptor)) + + # Always have a finalize hook, regardless of whether the class wants a + # custom hook. + cgThings.append(CGClassFinalizeHook(descriptor)) + + # Only generate a trace hook if the class wants a custom hook. + if (descriptor.customTrace): + cgThings.append(CGClassTraceHook(descriptor)) + + if descriptor.interface.hasInterfaceObject(): + cgThings.append(CGClassConstructHook(descriptor)) + cgThings.append(CGClassHasInstanceHook(descriptor)) + cgThings.append(CGInterfaceObjectJSClass(descriptor)) + + if descriptor.interface.hasInterfacePrototypeObject(): + cgThings.append(CGPrototypeJSClass(descriptor)) + + properties = PropertyArrays(descriptor) + cgThings.append(CGGeneric(define=str(properties))) + cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties)) + if descriptor.interface.hasInterfacePrototypeObject(): + cgThings.append(CGGetProtoObjectMethod(descriptor)) + else: + cgThings.append(CGGetConstructorObjectMethod(descriptor)) + + # Set up our Xray callbacks as needed. Note that we don't need to do + # it in workers. + if (descriptor.interface.hasInterfacePrototypeObject() and + not descriptor.workers): + if descriptor.concrete and descriptor.proxy: + cgThings.append(CGResolveOwnProperty(descriptor)) + cgThings.append(CGEnumerateOwnProperties(descriptor)) + cgThings.append(CGResolveProperty(descriptor, properties)) + cgThings.append(CGEnumerateProperties(descriptor, properties)) + + if descriptor.interface.hasInterfaceObject(): + cgThings.append(CGDefineDOMInterfaceMethod(descriptor)) + if (not descriptor.interface.isExternal() and + # Workers stuff is never pref-controlled + not descriptor.workers and + descriptor.interface.getExtendedAttribute("PrefControlled") is not None): + cgThings.append(CGPrefEnabled(descriptor)) + + if descriptor.interface.hasInterfacePrototypeObject(): + cgThings.append(CGNativePropertyHooks(descriptor)) + + if descriptor.concrete: + if descriptor.proxy: + cgThings.append(CGProxyIsProxy(descriptor)) + cgThings.append(CGProxyUnwrap(descriptor)) + cgThings.append(CGDOMJSProxyHandlerDOMClass(descriptor)) + cgThings.append(CGDOMJSProxyHandler(descriptor)) + cgThings.append(CGIsMethod(descriptor)) + else: + cgThings.append(CGDOMJSClass(descriptor)) + + if descriptor.wrapperCache: + cgThings.append(CGWrapWithCacheMethod(descriptor)) + cgThings.append(CGWrapMethod(descriptor)) + else: + cgThings.append(CGWrapNonWrapperCacheMethod(descriptor)) + + cgThings = CGList((CGIndenter(t, declareOnly=True) for t in cgThings), "\n") + cgThings = CGWrapper(cgThings, pre='\n', post='\n') + self.cgRoot = CGWrapper(CGNamespace(toBindingNamespace(descriptor.name), + cgThings), + post='\n') + + def declare(self): + return self.cgRoot.declare() + def define(self): + return self.cgRoot.define() + +class CGNamespacedEnum(CGThing): + def __init__(self, namespace, enumName, names, values, comment=""): + + if not values: + values = [] + + # Account for explicit enum values. + entries = [] + for i in range(0, len(names)): + if len(values) > i and values[i] is not None: + entry = "%s = %s" % (names[i], values[i]) + else: + entry = names[i] + entries.append(entry) + + # Append a Count. + entries.append('_' + enumName + '_Count') + + # Indent. + entries = [' ' + e for e in entries] + + # Build the enum body. + enumstr = comment + 'enum %s\n{\n%s\n};\n' % (enumName, ',\n'.join(entries)) + curr = CGGeneric(declare=enumstr) + + # Add some whitespace padding. + curr = CGWrapper(curr, pre='\n',post='\n') + + # Add the namespace. + curr = CGNamespace(namespace, curr) + + # Add the typedef + typedef = '\ntypedef %s::%s %s;\n\n' % (namespace, enumName, enumName) + curr = CGList([curr, CGGeneric(declare=typedef)]) + + # Save the result. + self.node = curr + + def declare(self): + return self.node.declare() + def define(self): + assert False # Only for headers. + +class CGDictionary(CGThing): + def __init__(self, dictionary, descriptorProvider): + self.dictionary = dictionary; + self.workers = descriptorProvider.workers + if all(CGDictionary(d, descriptorProvider).generatable for + d in CGDictionary.getDictionaryDependencies(dictionary)): + self.generatable = True + else: + self.generatable = False + # Nothing else to do here + return + # Getting a conversion template for interface types can fail + # if we don't have a relevant descriptor when self.workers is True. + # If that happens, just mark ourselves as not being + # generatable and move on. + try: + self.memberInfo = [ + (member, + getJSToNativeConversionTemplate(member.type, + descriptorProvider, + isMember=True, + isOptional=(not member.defaultValue), + defaultValue=member.defaultValue)) + for member in dictionary.members ] + except NoSuchDescriptorError, err: + if not self.workers: + raise err + self.generatable = False + + def declare(self): + if not self.generatable: + return "" + d = self.dictionary + if d.parent: + inheritance = ": public %s " % self.makeClassName(d.parent) + else: + inheritance = "" + memberDecls = [" %s %s;" % + (self.getMemberType(m), m[0].identifier.name) + for m in self.memberInfo] + + return (string.Template( + "struct ${selfName} ${inheritance}{\n" + " ${selfName}() {}\n" + " bool Init(JSContext* cx, const JS::Value& val);\n" + "\n" + + "\n".join(memberDecls) + "\n" + "private:\n" + " // Disallow copy-construction\n" + " ${selfName}(const ${selfName}&) MOZ_DELETE;\n" + + # NOTE: jsids are per-runtime, so don't use them in workers + (" static bool InitIds(JSContext* cx);\n" + " static bool initedIds;\n" if not self.workers else "") + + "\n".join(" static jsid " + + self.makeIdName(m.identifier.name) + ";" for + m in d.members) + "\n" + "};").substitute( { "selfName": self.makeClassName(d), + "inheritance": inheritance })) + + def define(self): + if not self.generatable: + return "" + d = self.dictionary + if d.parent: + initParent = ("// Per spec, we init the parent's members first\n" + "if (!%s::Init(cx, val)) {\n" + " return false;\n" + "}\n" % self.makeClassName(d.parent)) + else: + initParent = "" + + memberInits = [CGIndenter(self.getMemberConversion(m)).define() + for m in self.memberInfo] + idinit = [CGGeneric('!InternJSString(cx, %s, "%s")' % + (m.identifier.name + "_id", m.identifier.name)) + for m in d.members] + idinit = CGList(idinit, " ||\n") + idinit = CGWrapper(idinit, pre="if (", + post=(") {\n" + " return false;\n" + "}"), + reindent=True) + + return string.Template( + # NOTE: jsids are per-runtime, so don't use them in workers + ("bool ${selfName}::initedIds = false;\n" + + "\n".join("jsid ${selfName}::%s = JSID_VOID;" % + self.makeIdName(m.identifier.name) + for m in d.members) + "\n" + "\n" + "bool\n" + "${selfName}::InitIds(JSContext* cx)\n" + "{\n" + " MOZ_ASSERT(!initedIds);\n" + "${idInit}\n" + " initedIds = true;\n" + " return true;\n" + "}\n" + "\n" if not self.workers else "") + + "bool\n" + "${selfName}::Init(JSContext* cx, const JS::Value& val)\n" + "{\n" + + # NOTE: jsids are per-runtime, so don't use them in workers + (" if (!initedIds && !InitIds(cx)) {\n" + " return false;\n" + " }\n" if not self.workers else "") + + "${initParent}" + " JSBool found;\n" + " JS::Value temp;\n" + " bool isNull = val.isNullOrUndefined();\n" + " if (!isNull && !val.isObject()) {\n" + " return Throw<${isMainThread}>(cx, NS_ERROR_XPC_BAD_CONVERT_JS);\n" + " }\n" + "\n" + "${initMembers}\n" + " return true;\n" + "}").substitute({ + "selfName": self.makeClassName(d), + "initParent": CGIndenter(CGGeneric(initParent)).define(), + "initMembers": "\n\n".join(memberInits), + "idInit": CGIndenter(idinit).define(), + "isMainThread": toStringBool(not self.workers) + }) + + @staticmethod + def makeDictionaryName(dictionary, workers): + suffix = "Workers" if workers else "" + return dictionary.identifier.name + suffix + + def makeClassName(self, dictionary): + return self.makeDictionaryName(dictionary, self.workers) + + def getMemberType(self, memberInfo): + (member, (templateBody, declType, + holderType, dealWithOptional)) = memberInfo + # We can't handle having a holderType here + assert holderType is None + if dealWithOptional: + declType = CGWrapper(declType, pre="Optional< ", post=" >") + return declType.define() + + def getMemberConversion(self, memberInfo): + (member, (templateBody, declType, + holderType, dealWithOptional)) = memberInfo + replacements = { "val": "temp", + "valPtr": "&temp", + # Use this->%s to refer to members, because we don't + # control the member names and want to make sure we're + # talking about the member, not some local that + # shadows the member. Another option would be to move + # the guts of init to a static method which is passed + # an explicit reference to our dictionary object, so + # we couldn't screw this up even if we wanted to.... + "declName": ("(this->%s)" % member.identifier.name), + # We need a holder name for external interfaces, but + # it's scoped down to the conversion so we can just use + # anything we want. + "holderName": "holder"} + # We can't handle having a holderType here + assert holderType is None + if dealWithOptional: + replacements["declName"] = "(" + replacements["declName"] + ".Value())" + if member.defaultValue: + replacements["haveValue"] = "found" + + # NOTE: jsids are per-runtime, so don't use them in workers + if self.workers: + propName = member.identifier.name + propCheck = ('JS_HasProperty(cx, &val.toObject(), "%s", &found)' % + propName) + propGet = ('JS_GetProperty(cx, &val.toObject(), "%s", &temp)' % + propName) + else: + propId = self.makeIdName(member.identifier.name); + propCheck = ("JS_HasPropertyById(cx, &val.toObject(), %s, &found)" % + propId) + propGet = ("JS_GetPropertyById(cx, &val.toObject(), %s, &temp)" % + propId) + + conversionReplacements = { + "prop": "(this->%s)" % member.identifier.name, + "convert": string.Template(templateBody).substitute(replacements), + "propCheck": propCheck, + "propGet": propGet + } + conversion = ("if (isNull) {\n" + " found = false;\n" + "} else if (!${propCheck}) {\n" + " return false;\n" + "}\n") + if member.defaultValue: + conversion += ( + "if (found) {\n" + " if (!${propGet}) {\n" + " return false;\n" + " }\n" + "}\n" + "${convert}") + else: + conversion += ( + "if (found) {\n" + " ${prop}.Construct();\n" + " if (!${propGet}) {\n" + " return false;\n" + " }\n" + "${convert}\n" + "}") + conversionReplacements["convert"] = CGIndenter( + CGGeneric(conversionReplacements["convert"])).define() + + return CGGeneric( + string.Template(conversion).substitute(conversionReplacements) + ) + + @staticmethod + def makeIdName(name): + return name + "_id" + + @staticmethod + def getDictionaryDependencies(dictionary): + deps = set(); + if dictionary.parent: + deps.add(dictionary.parent) + for member in dictionary.members: + if member.type.isDictionary(): + deps.add(member.type.unroll().inner) + return deps + + +class CGRegisterProtos(CGAbstractMethod): + def __init__(self, config): + CGAbstractMethod.__init__(self, None, 'Register', 'void', + [Argument('nsScriptNameSpaceManager*', 'aNameSpaceManager')]) + self.config = config + + def _defineMacro(self): + return """ +#define REGISTER_PROTO(_dom_class, _pref_check) \\ + aNameSpaceManager->RegisterDefineDOMInterface(NS_LITERAL_STRING(#_dom_class), _dom_class##Binding::DefineDOMInterface, _pref_check);\n\n""" + def _undefineMacro(self): + return "\n#undef REGISTER_PROTO" + def _registerProtos(self): + def getPrefCheck(desc): + if desc.interface.getExtendedAttribute("PrefControlled") is None: + return "nullptr" + return "%sBinding::PrefEnabled" % desc.name + lines = ["REGISTER_PROTO(%s, %s);" % (desc.name, getPrefCheck(desc)) + for desc in self.config.getDescriptors(hasInterfaceObject=True, + isExternal=False, + workers=False, + register=True)] + return '\n'.join(lines) + '\n' + def definition_body(self): + return self._defineMacro() + self._registerProtos() + self._undefineMacro() + +class CGBindingRoot(CGThing): + """ + Root codegen class for binding generation. Instantiate the class, and call + declare or define to generate header or cpp code (respectively). + """ + def __init__(self, config, prefix, webIDLFile): + descriptors = config.getDescriptors(webIDLFile=webIDLFile, + hasInterfaceOrInterfacePrototypeObject=True) + dictionaries = config.getDictionaries(webIDLFile) + + forwardDeclares = [CGClassForwardDeclare('XPCWrappedNativeScope')] + + descriptorsForForwardDeclaration = list(descriptors) + for dictionary in dictionaries: + curDict = dictionary + ifacemembers = [] + while curDict: + ifacemembers.extend([m.type.unroll().inner for m + in curDict.members + if m.type.unroll().isInterface()]) + curDict = curDict.parent + # Put in all the non-worker descriptors + descriptorsForForwardDeclaration.extend( + [config.getDescriptor(iface.identifier.name, False) for + iface in ifacemembers]) + # And now the worker ones. But these may not exist, so we + # have to be more careful. + for iface in ifacemembers: + try: + descriptorsForForwardDeclaration.append( + config.getDescriptor(iface.identifier.name, True)) + except NoSuchDescriptorError: + # just move along + pass + + for x in descriptorsForForwardDeclaration: + nativeType = x.nativeType + components = x.nativeType.split('::') + className = components[-1] + # JSObject is a struct, not a class + declare = CGClassForwardDeclare(className, className is "JSObject") + if len(components) > 1: + declare = CGNamespace.build(components[:-1], + CGWrapper(declare, declarePre='\n', + declarePost='\n'), + declareOnly=True) + forwardDeclares.append(CGWrapper(declare, declarePost='\n')) + + forwardDeclares = CGList(forwardDeclares) + + descriptorsWithPrototype = filter(lambda d: d.interface.hasInterfacePrototypeObject(), + descriptors) + traitsClasses = [CGPrototypeTraitsClass(d) for d in descriptorsWithPrototype] + + # We must have a 1:1 mapping here, skip for prototypes that have more + # than one concrete class implementation. + traitsClasses.extend([CGPrototypeIDMapClass(d) for d in descriptorsWithPrototype + if d.uniqueImplementation]) + + # Wrap all of that in our namespaces. + if len(traitsClasses) > 0: + traitsClasses = CGNamespace.build(['mozilla', 'dom'], + CGWrapper(CGList(traitsClasses), + declarePre='\n'), + declareOnly=True) + traitsClasses = CGWrapper(traitsClasses, declarePost='\n') + else: + traitsClasses = None + + # Do codegen for all the enums + def makeEnum(e): + return CGNamespace.build([e.identifier.name + "Values"], + CGEnum(e)) + def makeEnumTypedef(e): + return CGGeneric(declare=("typedef %sValues::valuelist %s;\n" % + (e.identifier.name, e.identifier.name))) + cgthings = [ fun(e) for e in config.getEnums(webIDLFile) + for fun in [makeEnum, makeEnumTypedef] ] + + # Do codegen for all the dictionaries. We have to be a bit careful + # here, because we have to generate these in order from least derived + # to most derived so that class inheritance works out. We also have to + # generate members before the dictionary that contains them. + # + # XXXbz this will fail if we have two webidl files A and B such that A + # declares a dictionary which inherits from a dictionary in B and B + # declares a dictionary (possibly a different one!) that inherits from a + # dictionary in A. The good news is that I expect this to never happen. + reSortedDictionaries = [] + dictionaries = set(dictionaries) + while len(dictionaries) != 0: + # Find the dictionaries that don't depend on anything else anymore + # and move them over. + toMove = [d for d in dictionaries if + len(CGDictionary.getDictionaryDependencies(d) & + dictionaries) == 0] + if len(toMove) == 0: + raise TypeError("Loop in dictionary dependency graph") + dictionaries = dictionaries - set(toMove) + reSortedDictionaries.extend(toMove) + + dictionaries = reSortedDictionaries + cgthings.extend([CGDictionary(d, config.getDescriptorProvider(True)) + for d in dictionaries]) + cgthings.extend([CGDictionary(d, config.getDescriptorProvider(False)) + for d in dictionaries]) + + # Do codegen for all the descriptors + cgthings.extend([CGDescriptor(x) for x in descriptors]) + + # And make sure we have the right number of newlines at the end + curr = CGWrapper(CGList(cgthings, "\n\n"), post="\n\n") + + # Wrap all of that in our namespaces. + curr = CGNamespace.build(['mozilla', 'dom'], + CGWrapper(curr, pre="\n")) + + curr = CGList([forwardDeclares, + CGWrapper(CGGeneric("using namespace mozilla::dom;"), + defineOnly=True), + traitsClasses, curr], + "\n") + + # Add header includes. + curr = CGHeaders(descriptors, + dictionaries, + ['mozilla/dom/BindingUtils.h', + 'mozilla/dom/DOMJSClass.h', + 'mozilla/dom/DOMJSProxyHandler.h'], + ['mozilla/dom/Nullable.h', + 'PrimitiveConversions.h', + 'XPCQuickStubs.h', + 'nsDOMQS.h', + 'AccessCheck.h', + 'WorkerPrivate.h', + 'nsContentUtils.h', + 'mozilla/Preferences.h', + # Have to include nsDOMQS.h to get fast arg unwrapping + # for old-binding things with castability. + 'nsDOMQS.h' + ], + curr) + + # Add include guards. + curr = CGIncludeGuard(prefix, curr) + + # Add the auto-generated comment. + curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) + + # Store the final result. + self.root = curr + + def declare(self): + return stripTrailingWhitespace(self.root.declare()) + def define(self): + return stripTrailingWhitespace(self.root.define()) + + +class GlobalGenRoots(): + """ + Roots for global codegen. + + To generate code, call the method associated with the target, and then + call the appropriate define/declare method. + """ + + @staticmethod + def PrototypeList(config): + + # Prototype ID enum. + protos = [d.name for d in config.getDescriptors(hasInterfacePrototypeObject=True)] + idEnum = CGNamespacedEnum('id', 'ID', protos, [0]) + idEnum = CGList([idEnum]) + idEnum.append(CGGeneric(declare="const unsigned MaxProtoChainLength = " + + str(config.maxProtoChainLength) + ";\n\n")) + + # Wrap all of that in our namespaces. + idEnum = CGNamespace.build(['mozilla', 'dom', 'prototypes'], + CGWrapper(idEnum, pre='\n')) + idEnum = CGWrapper(idEnum, post='\n') + + curr = CGList([idEnum]) + + # Constructor ID enum. + constructors = [d.name for d in config.getDescriptors(hasInterfaceObject=True, + hasInterfacePrototypeObject=False)] + idEnum = CGNamespacedEnum('id', 'ID', constructors, [0]) + + # Wrap all of that in our namespaces. + idEnum = CGNamespace.build(['mozilla', 'dom', 'constructors'], + CGWrapper(idEnum, pre='\n')) + idEnum = CGWrapper(idEnum, post='\n') + + curr.append(idEnum) + + traitsDecl = CGGeneric(declare=""" +template <prototypes::ID PrototypeID> +struct PrototypeTraits; + +template <class ConcreteClass> +struct PrototypeIDMap; +""") + + traitsDecl = CGNamespace.build(['mozilla', 'dom'], + CGWrapper(traitsDecl, post='\n')) + + curr.append(traitsDecl) + + # Add include guards. + curr = CGIncludeGuard('PrototypeList', curr) + + # Add the auto-generated comment. + curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) + + # Done. + return curr + + @staticmethod + def RegisterBindings(config): + + # TODO - Generate the methods we want + curr = CGRegisterProtos(config) + + # Wrap all of that in our namespaces. + curr = CGNamespace.build(['mozilla', 'dom'], + CGWrapper(curr, post='\n')) + curr = CGWrapper(curr, post='\n') + + # Add the includes + defineIncludes = [CGHeaders.getDeclarationFilename(desc.interface) + for desc in config.getDescriptors(hasInterfaceObject=True, + workers=False, + register=True)] + defineIncludes.append('nsScriptNameSpaceManager.h') + curr = CGHeaders([], [], [], defineIncludes, curr) + + # Add include guards. + curr = CGIncludeGuard('RegisterBindings', curr) + + # Done. + return curr + + @staticmethod + def UnionTypes(config): + + (includes, declarations, unions) = UnionTypes(config.getDescriptors()) + includes.add("mozilla/dom/BindingUtils.h") + + # Wrap all of that in our namespaces. + curr = CGNamespace.build(['mozilla', 'dom'], unions) + + curr = CGWrapper(curr, post='\n') + + namespaces = [] + stack = [CGList([])] + for (clazz, isStruct) in SortedTuples(declarations): + elements = clazz.split("::") + clazz = CGClassForwardDeclare(elements.pop(), isStruct=isStruct) + i = 0 + if len(elements) > 0: + common = min(len(namespaces), len(elements)) + while i < common and namespaces[i] == elements[i]: + i += 1 + + # pop all the namespaces that should be closed + namespaces = namespaces[:i] + + # add all the namespaces that should be opened + for j, namespace in enumerate(elements[i:]): + namespaces.append(namespace) + # every CGNamespace that we add holds a CGList + list = CGList([]) + # add the new namespace to the list on top of the stack + stack[i + j].append(CGNamespace(namespace, list)) + # set the top of the namespace stack to the list of the new + # namespace + stack[i + j + 1:] = [list] + + stack[len(elements)].append(clazz) + + curr = CGList([stack[0], curr], "\n") + + curr = CGHeaders([], [], includes, [], curr) + + # Add include guards. + curr = CGIncludeGuard('UnionTypes', curr) + + # Done. + return curr + + @staticmethod + def UnionConversions(config): + + unions = UnionConversions(config.getDescriptors()) + + # Wrap all of that in our namespaces. + curr = CGNamespace.build(['mozilla', 'dom'], unions) + + curr = CGWrapper(curr, post='\n') + + curr = CGHeaders([], [], ["nsDebug.h", "mozilla/dom/UnionTypes.h", "nsDOMQS.h"], [], curr) + + # Add include guards. + curr = CGIncludeGuard('UnionConversions', curr) + + # Done. + return curr diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py new file mode 100644 index 00000000000..1666589940e --- /dev/null +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -0,0 +1,5534 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# Common codegen classes. + +import operator +import os +import re +import string + +from WebIDL import ( + BuiltinTypes, + IDLBuiltinType, + IDLNullValue, + IDLType, + IDLUndefinedValue, +) + +from Configuration import getTypesFromDescriptor, getTypesFromDictionary, getTypesFromCallback + +AUTOGENERATED_WARNING_COMMENT = \ + "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n" +ADDPROPERTY_HOOK_NAME = '_addProperty' +FINALIZE_HOOK_NAME = '_finalize' +TRACE_HOOK_NAME = '_trace' +CONSTRUCT_HOOK_NAME = '_constructor' +HASINSTANCE_HOOK_NAME = '_hasInstance' + +def replaceFileIfChanged(filename, newContents): + """ + Read a copy of the old file, so that we don't touch it if it hasn't changed. + Returns True if the file was updated, false otherwise. + """ + #XXXjdm This doesn't play well with make right now. + # Force the file to always be updated, or else changing CodegenRust.py + # will cause many autogenerated bindings to be regenerated perpetually + # until the result is actually different. + + #oldFileContents = "" + #try: + # oldFile = open(filename, 'rb') + # oldFileContents = ''.join(oldFile.readlines()) + # oldFile.close() + #except: + # pass + + #if newContents == oldFileContents: + # return False + + f = open(filename, 'wb') + f.write(newContents) + f.close() + + return True + +def toStringBool(arg): + return str(not not arg).lower() + +def toBindingNamespace(arg): + return re.sub("((_workers)?$)", "Binding\\1", arg); + +def stripTrailingWhitespace(text): + tail = '\n' if text.endswith('\n') else '' + lines = text.splitlines() + for i in range(len(lines)): + lines[i] = lines[i].rstrip() + return '\n'.join(lines) + tail + +def MakeNativeName(name): + return name[0].upper() + name[1:] + +builtinNames = { + IDLType.Tags.bool: 'bool', + IDLType.Tags.int8: 'i8', + IDLType.Tags.int16: 'i16', + IDLType.Tags.int32: 'i32', + IDLType.Tags.int64: 'i64', + IDLType.Tags.uint8: 'u8', + IDLType.Tags.uint16: 'u16', + IDLType.Tags.uint32: 'u32', + IDLType.Tags.uint64: 'u64', + IDLType.Tags.float: 'f32', + IDLType.Tags.double: 'f64' +} + +numericTags = [ + IDLType.Tags.int8, IDLType.Tags.uint8, + IDLType.Tags.int16, IDLType.Tags.uint16, + IDLType.Tags.int32, IDLType.Tags.uint32, + IDLType.Tags.int64, IDLType.Tags.uint64, + IDLType.Tags.float, IDLType.Tags.double + ] + +class CastableObjectUnwrapper(): + """ + A class for unwrapping an object named by the "source" argument + based on the passed-in descriptor. Stringifies to a Rust expression of + the appropriate type. + + codeOnFailure is the code to run if unwrapping fails. + """ + def __init__(self, descriptor, source, codeOnFailure): + self.substitution = { + "type": descriptor.nativeType, + "depth": descriptor.interface.inheritanceDepth(), + "prototype": "PrototypeList::id::" + descriptor.name, + "protoID": "PrototypeList::id::" + descriptor.name + " as uint", + "source": source, + "codeOnFailure": CGIndenter(CGGeneric(codeOnFailure), 4).define(), + } + + def __str__(self): + return string.Template( +"""match unwrap_jsmanaged(${source}, ${prototype}, ${depth}) { + Ok(val) => val, + Err(()) => { +${codeOnFailure} + } +}""").substitute(self.substitution) + + +class CGThing(): + """ + Abstract base class for things that spit out code. + """ + def __init__(self): + pass # Nothing for now + + def define(self): + """Produce code for a Rust file.""" + assert(False) # Override me! + + +class CGNativePropertyHooks(CGThing): + """ + Generate a NativePropertyHooks for a given descriptor + """ + def __init__(self, descriptor, properties): + CGThing.__init__(self) + self.descriptor = descriptor + self.properties = properties + + def define(self): + parent = self.descriptor.interface.parent + if parent: + parentHooks = "Some(&::dom::bindings::codegen::Bindings::%sBinding::sNativePropertyHooks)" % parent.identifier.name + else: + parentHooks = "None" + + substitutions = { + "parentHooks": parentHooks + } + + return string.Template( + "pub static sNativePropertyHooks: NativePropertyHooks = NativePropertyHooks {\n" + " native_properties: &sNativeProperties,\n" + " proto_hooks: ${parentHooks},\n" + "};\n").substitute(substitutions) + + +class CGMethodCall(CGThing): + """ + A class to generate selection of a method signature from a set of + signatures and generation of a call to that signature. + """ + def __init__(self, argsPre, nativeMethodName, static, descriptor, method): + CGThing.__init__(self) + + methodName = '\\"%s.%s\\"' % (descriptor.interface.identifier.name, method.identifier.name) + + def requiredArgCount(signature): + arguments = signature[1] + if len(arguments) == 0: + return 0 + requiredArgs = len(arguments) + while requiredArgs and arguments[requiredArgs-1].optional: + requiredArgs -= 1 + return requiredArgs + + def getPerSignatureCall(signature, argConversionStartsAt=0, signatureIndex=0): + return CGPerSignatureCall(signature[0], argsPre, signature[1], + nativeMethodName + '_'*signatureIndex, + static, descriptor, + method, argConversionStartsAt) + + + signatures = method.signatures() + if len(signatures) == 1: + # Special case: we can just do a per-signature method call + # here for our one signature and not worry about switching + # on anything. + signature = signatures[0] + self.cgRoot = CGList([getPerSignatureCall(signature)]) + requiredArgs = requiredArgCount(signature) + + + if requiredArgs > 0: + code = ( + "if argc < %d {\n" + " throw_type_error(cx, \"Not enough arguments to %s.\");\n" + " return 0;\n" + "}" % (requiredArgs, methodName)) + self.cgRoot.prepend( + CGWrapper(CGGeneric(code), pre="\n", post="\n")) + + return + + # Need to find the right overload + maxArgCount = method.maxArgCount + allowedArgCounts = method.allowedArgCounts + + argCountCases = [] + for argCount in allowedArgCounts: + possibleSignatures = method.signaturesForArgCount(argCount) + if len(possibleSignatures) == 1: + # easy case! + signature = possibleSignatures[0] + + + sigIndex = signatures.index(signature) + argCountCases.append( + CGCase(str(argCount), getPerSignatureCall(signature, + signatureIndex=sigIndex))) + continue + + distinguishingIndex = method.distinguishingIndexForArgCount(argCount) + + # We can't handle unions at the distinguishing index. + for (returnType, args) in possibleSignatures: + if args[distinguishingIndex].type.isUnion(): + raise TypeError("No support for unions as distinguishing " + "arguments yet: %s", + args[distinguishingIndex].location) + + # Convert all our arguments up to the distinguishing index. + # Doesn't matter which of the possible signatures we use, since + # they all have the same types up to that point; just use + # possibleSignatures[0] + caseBody = [CGGeneric("let argv_start = JS_ARGV(cx, vp);")] + caseBody.extend([ CGArgumentConverter(possibleSignatures[0][1][i], + i, "argv_start", "argc", + descriptor) for i in + range(0, distinguishingIndex) ]) + + # Select the right overload from our set. + distinguishingArg = "(*argv_start.offset(%d))" % distinguishingIndex + + def pickFirstSignature(condition, filterLambda): + sigs = filter(filterLambda, possibleSignatures) + assert len(sigs) < 2 + if len(sigs) > 0: + if condition is None: + caseBody.append( + getPerSignatureCall(sigs[0], distinguishingIndex, + possibleSignatures.index(sigs[0]))) + else: + caseBody.append(CGGeneric("if " + condition + " {")) + caseBody.append(CGIndenter( + getPerSignatureCall(sigs[0], distinguishingIndex, + possibleSignatures.index(sigs[0])))) + caseBody.append(CGGeneric("}")) + return True + return False + + # First check for null or undefined + pickFirstSignature("%s.isNullOrUndefined()" % distinguishingArg, + lambda s: (s[1][distinguishingIndex].type.nullable() or + s[1][distinguishingIndex].type.isDictionary())) + + # Now check for distinguishingArg being an object that implements a + # non-callback interface. That includes typed arrays and + # arraybuffers. + interfacesSigs = [ + s for s in possibleSignatures + if (s[1][distinguishingIndex].type.isObject() or + s[1][distinguishingIndex].type.isNonCallbackInterface()) ] + # There might be more than one of these; we need to check + # which ones we unwrap to. + + if len(interfacesSigs) > 0: + # The spec says that we should check for "platform objects + # implementing an interface", but it's enough to guard on these + # being an object. The code for unwrapping non-callback + # interfaces and typed arrays will just bail out and move on to + # the next overload if the object fails to unwrap correctly. We + # could even not do the isObject() check up front here, but in + # cases where we have multiple object overloads it makes sense + # to do it only once instead of for each overload. That will + # also allow the unwrapping test to skip having to do codegen + # for the null-or-undefined case, which we already handled + # above. + caseBody.append(CGGeneric("if (%s).is_object() {" % + (distinguishingArg))) + for idx, sig in enumerate(interfacesSigs): + caseBody.append(CGIndenter(CGGeneric("loop {"))); + type = sig[1][distinguishingIndex].type + + # The argument at index distinguishingIndex can't possibly + # be unset here, because we've already checked that argc is + # large enough that we can examine this argument. + template, _, declType, needsRooting = getJSToNativeConversionTemplate( + type, descriptor, failureCode="break;", isDefinitelyObject=True) + + testCode = instantiateJSToNativeConversionTemplate( + template, + {"val": distinguishingArg}, + declType, + "arg%d" % distinguishingIndex, + needsRooting) + + # Indent by 4, since we need to indent further than our "do" statement + caseBody.append(CGIndenter(testCode, 4)); + # If we got this far, we know we unwrapped to the right + # interface, so just do the call. Start conversion with + # distinguishingIndex + 1, since we already converted + # distinguishingIndex. + caseBody.append(CGIndenter( + getPerSignatureCall(sig, distinguishingIndex + 1, idx), 4)) + caseBody.append(CGIndenter(CGGeneric("}"))) + + caseBody.append(CGGeneric("}")) + + # XXXbz Now we're supposed to check for distinguishingArg being + # an array or a platform object that supports indexed + # properties... skip that last for now. It's a bit of a pain. + pickFirstSignature("%s.isObject() && IsArrayLike(cx, &%s.toObject())" % + (distinguishingArg, distinguishingArg), + lambda s: + (s[1][distinguishingIndex].type.isArray() or + s[1][distinguishingIndex].type.isSequence() or + s[1][distinguishingIndex].type.isObject())) + + # Check for Date objects + # XXXbz Do we need to worry about security wrappers around the Date? + pickFirstSignature("%s.isObject() && JS_ObjectIsDate(cx, &%s.toObject())" % + (distinguishingArg, distinguishingArg), + lambda s: (s[1][distinguishingIndex].type.isDate() or + s[1][distinguishingIndex].type.isObject())) + + # Check for vanilla JS objects + # XXXbz Do we need to worry about security wrappers? + pickFirstSignature("%s.isObject() && !IsPlatformObject(cx, &%s.toObject())" % + (distinguishingArg, distinguishingArg), + lambda s: (s[1][distinguishingIndex].type.isCallback() or + s[1][distinguishingIndex].type.isCallbackInterface() or + s[1][distinguishingIndex].type.isDictionary() or + s[1][distinguishingIndex].type.isObject())) + + # The remaining cases are mutually exclusive. The + # pickFirstSignature calls are what change caseBody + # Check for strings or enums + if pickFirstSignature(None, + lambda s: (s[1][distinguishingIndex].type.isString() or + s[1][distinguishingIndex].type.isEnum())): + pass + # Check for primitives + elif pickFirstSignature(None, + lambda s: s[1][distinguishingIndex].type.isPrimitive()): + pass + # Check for "any" + elif pickFirstSignature(None, + lambda s: s[1][distinguishingIndex].type.isAny()): + pass + else: + # Just throw; we have no idea what we're supposed to + # do with this. + caseBody.append(CGGeneric("return Throw(cx, NS_ERROR_XPC_BAD_CONVERT_JS);")) + + argCountCases.append(CGCase(str(argCount), + CGList(caseBody, "\n"))) + + overloadCGThings = [] + overloadCGThings.append( + CGGeneric("let argcount = cmp::min(argc, %d);" % + maxArgCount)) + overloadCGThings.append( + CGSwitch("argcount", + argCountCases, + CGGeneric("throw_type_error(cx, \"Not enough arguments to %s.\");\n" + "return 0;\n" % methodName))) + #XXXjdm Avoid unreachable statement warnings + #overloadCGThings.append( + # CGGeneric('fail!("We have an always-returning default case");\n' + # 'return 0;')) + self.cgRoot = CGWrapper(CGList(overloadCGThings, "\n"), + pre="\n") + + def define(self): + return self.cgRoot.define() + +class FakeCastableDescriptor(): + def __init__(self, descriptor): + self.nativeType = "*const %s" % descriptor.concreteType + self.name = descriptor.name + class FakeInterface: + def inheritanceDepth(self): + return descriptor.interface.inheritanceDepth() + self.interface = FakeInterface() + +def dictionaryHasSequenceMember(dictionary): + return (any(typeIsSequenceOrHasSequenceMember(m.type) for m in + dictionary.members) or + (dictionary.parent and + dictionaryHasSequenceMember(dictionary.parent))) + +def typeIsSequenceOrHasSequenceMember(type): + if type.nullable(): + type = type.inner + if type.isSequence(): + return True + if type.isArray(): + elementType = type.inner + return typeIsSequenceOrHasSequenceMember(elementType) + if type.isDictionary(): + return dictionaryHasSequenceMember(type.inner) + if type.isUnion(): + return any(typeIsSequenceOrHasSequenceMember(m.type) for m in + type.flatMemberTypes) + return False + +def typeNeedsRooting(type, descriptorProvider): + return type.isGeckoInterface() and descriptorProvider.getDescriptor(type.name).needsRooting + +def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, + isDefinitelyObject=False, + isMember=False, + isArgument=False, + invalidEnumValueFatal=True, + defaultValue=None, + treatNullAs="Default", + isEnforceRange=False, + isClamp=False, + exceptionCode=None, + allowTreatNonObjectAsNull=False, + isCallbackReturnValue=False, + sourceDescription="value"): + """ + Get a template for converting a JS value to a native object based on the + given type and descriptor. If failureCode is given, then we're actually + testing whether we can convert the argument to the desired type. That + means that failures to convert due to the JS value being the wrong type of + value need to use failureCode instead of throwing exceptions. Failures to + convert that are due to JS exceptions (from toString or valueOf methods) or + out of memory conditions need to throw exceptions no matter what + failureCode is. + + If isDefinitelyObject is True, that means we know the value + isObject() and we have no need to recheck that. + + if isMember is True, we're being converted from a property of some + JS object, not from an actual method argument, so we can't rely on + our jsval being rooted or outliving us in any way. Any caller + passing true needs to ensure that it is handled correctly in + typeIsSequenceOrHasSequenceMember. + + invalidEnumValueFatal controls whether an invalid enum value conversion + attempt will throw (if true) or simply return without doing anything (if + false). + + If defaultValue is not None, it's the IDL default value for this conversion + + If isEnforceRange is true, we're converting an integer and throwing if the + value is out of range. + + If isClamp is true, we're converting an integer and clamping if the + value is out of range. + + If allowTreatNonObjectAsNull is true, then [TreatNonObjectAsNull] + extended attributes on nullable callback functions will be honored. + + The return value from this function is a tuple consisting of four things: + + 1) A string representing the conversion code. This will have template + substitution performed on it as follows: + + ${val} replaced by an expression for the JS::Value in question + + 2) A string or None representing Rust code for the default value (if any). + + 3) A CGThing representing the native C++ type we're converting to + (declType). This is allowed to be None if the conversion code is + supposed to be used as-is. + + 4) A boolean indicating whether the caller has to root the result. + + """ + # We should not have a defaultValue if we know we're an object + assert(not isDefinitelyObject or defaultValue is None) + + # If exceptionCode is not set, we'll just rethrow the exception we got. + # Note that we can't just set failureCode to exceptionCode, because setting + # failureCode will prevent pending exceptions from being set in cases when + # they really should be! + if exceptionCode is None: + exceptionCode = "return 0;" + + needsRooting = typeNeedsRooting(type, descriptorProvider) + + def handleOptional(template, declType, default): + assert (defaultValue is None) == (default is None) + return (template, default, declType, needsRooting) + + # Unfortunately, .capitalize() on a string will lowercase things inside the + # string, which we do not want. + def firstCap(string): + return string[0].upper() + string[1:] + + # Helper functions for dealing with failures due to the JS value being the + # wrong type of value + # Helper functions for dealing with failures due to the JS value being the + # wrong type of value + def onFailureNotAnObject(failureCode): + return CGWrapper( + CGGeneric( + failureCode or + ('throw_type_error(cx, "%s is not an object.");\n' + '%s' % (firstCap(sourceDescription), exceptionCode))), + post="\n") + def onFailureBadType(failureCode, typeName): + return CGWrapper( + CGGeneric( + failureCode or + ('throw_type_error(cx, \"%s does not implement interface %s.\");\n' + '%s' % (firstCap(sourceDescription), typeName, + exceptionCode))), + post="\n") + def onFailureNotCallable(failureCode): + return CGWrapper( + CGGeneric( + failureCode or + ('throw_type_error(cx, \"%s is not callable.\");\n' + '%s' % (firstCap(sourceDescription), exceptionCode))), + post="\n") + + + # A helper function for handling null default values. Checks that the + # default value, if it exists, is null. + def handleDefaultNull(nullValue): + if defaultValue is None: + return None + + if not isinstance(defaultValue, IDLNullValue): + raise TypeError("Can't handle non-null default value here") + + assert type.nullable() or type.isDictionary() + return nullValue + + # A helper function for wrapping up the template body for + # possibly-nullable objecty stuff + def wrapObjectTemplate(templateBody, isDefinitelyObject, type, + failureCode=None): + if not isDefinitelyObject: + # Handle the non-object cases by wrapping up the whole + # thing in an if cascade. + templateBody = ( + "if (${val}).is_object() {\n" + + CGIndenter(CGGeneric(templateBody)).define() + "\n") + if type.nullable(): + templateBody += ( + "} else if (${val}).is_null_or_undefined() {\n" + " None\n") + templateBody += ( + "} else {\n" + + CGIndenter(onFailureNotAnObject(failureCode)).define() + + "}\n") + + return templateBody + + assert not (isEnforceRange and isClamp) # These are mutually exclusive + + if type.isArray(): + raise TypeError("Can't handle array arguments yet") + + if type.isSequence(): + raise TypeError("Can't handle sequence arguments yet") + + if type.isUnion(): + declType = CGGeneric(type.name + "::" + type.name) + if type.nullable(): + declType = CGWrapper(declType, pre="Option<", post=" >") + + templateBody = ("match FromJSValConvertible::from_jsval(cx, ${val}, ()) {\n" + " Ok(value) => value,\n" + " Err(()) => { %s },\n" + "}" % exceptionCode) + + return handleOptional(templateBody, declType, handleDefaultNull("None")) + + if type.isGeckoInterface(): + assert not isEnforceRange and not isClamp + + descriptor = descriptorProvider.getDescriptor( + type.unroll().inner.identifier.name) + + if descriptor.interface.isCallback(): + name = descriptor.nativeType + declType = CGGeneric("Option<%s>" % name); + conversion = ("Some(%s::new((${val}).to_object()))" % name) + + template = wrapObjectTemplate(conversion, isDefinitelyObject, type, + failureCode) + return handleOptional(template, declType, handleDefaultNull("None")) + + if isMember: + descriptorType = descriptor.memberType + elif isArgument: + descriptorType = descriptor.argumentType + else: + descriptorType = descriptor.nativeType + + templateBody = "" + if descriptor.interface.isConsequential(): + raise TypeError("Consequential interface %s being used as an " + "argument" % descriptor.interface.identifier.name) + + if failureCode is None: + substitutions = { + "sourceDescription": sourceDescription, + "interface": descriptor.interface.identifier.name, + "exceptionCode": exceptionCode, + } + unwrapFailureCode = string.Template( + 'throw_type_error(cx, "${sourceDescription} does not ' + 'implement interface ${interface}.");\n' + '${exceptionCode}').substitute(substitutions) + else: + unwrapFailureCode = failureCode + + templateBody = str(CastableObjectUnwrapper( + descriptor, + "(${val}).to_object()", + unwrapFailureCode)) + + declType = CGGeneric(descriptorType) + if type.nullable(): + templateBody = "Some(%s)" % templateBody + declType = CGWrapper(declType, pre="Option<", post=">") + + if isMember: + templateBody += ".root()" + + templateBody = wrapObjectTemplate(templateBody, isDefinitelyObject, + type, failureCode) + + return handleOptional(templateBody, declType, handleDefaultNull("None")) + + if type.isSpiderMonkeyInterface(): + raise TypeError("Can't handle SpiderMonkey interface arguments yet") + + if type.isDOMString(): + assert not isEnforceRange and not isClamp + + treatAs = { + "Default": "Default", + "EmptyString": "Empty", + } + if treatNullAs not in treatAs: + raise TypeError("We don't support [TreatNullAs=%s]" % treatNullAs) + if type.nullable(): + nullBehavior = "()" + else: + nullBehavior = treatAs[treatNullAs] + + conversionCode = ( + "match FromJSValConvertible::from_jsval(cx, ${val}, %s) {\n" + " Ok(strval) => strval,\n" + " Err(_) => { %s },\n" + "}" % (nullBehavior, exceptionCode)) + + if defaultValue is None: + default = None + elif isinstance(defaultValue, IDLNullValue): + assert type.nullable() + default = "None" + else: + assert defaultValue.type.tag() == IDLType.Tags.domstring + value = "str::from_utf8(data).unwrap().to_string()" + if type.nullable(): + value = "Some(%s)" % value + + default = ( + "static data: [u8, ..%s] = [ %s ];\n" + "%s" % + (len(defaultValue.value) + 1, + ", ".join(["'" + char + "' as u8" for char in defaultValue.value] + ["0"]), + value)) + + declType = "DOMString" + if type.nullable(): + declType = "Option<%s>" % declType + + return handleOptional(conversionCode, CGGeneric(declType), default) + + if type.isByteString(): + assert not isEnforceRange and not isClamp + + conversionCode = ( + "match FromJSValConvertible::from_jsval(cx, ${val}, ()) {\n" + " Ok(strval) => strval,\n" + " Err(_) => { %s },\n" + "}" % exceptionCode) + + declType = CGGeneric("ByteString") + if type.nullable(): + declType = CGWrapper(declType, pre="Option<", post=">") + + return handleOptional(conversionCode, declType, handleDefaultNull("None")) + + if type.isEnum(): + assert not isEnforceRange and not isClamp + + if type.nullable(): + raise TypeError("We don't support nullable enumerated arguments " + "yet") + enum = type.inner.identifier.name + if invalidEnumValueFatal: + handleInvalidEnumValueCode = exceptionCode + else: + handleInvalidEnumValueCode = "return 1;" + + template = ( + "match FindEnumStringIndex(cx, ${val}, %(values)s) {\n" + " Err(_) => { %(exceptionCode)s },\n" + " Ok(None) => { %(handleInvalidEnumValueCode)s },\n" + " Ok(Some(index)) => {\n" + " //XXXjdm need some range checks up in here.\n" + " unsafe { mem::transmute(index) }\n" + " },\n" + "}" % { "values" : enum + "Values::strings", + "exceptionCode" : exceptionCode, +"handleInvalidEnumValueCode" : handleInvalidEnumValueCode }) + + if defaultValue is not None: + assert(defaultValue.type.tag() == IDLType.Tags.domstring) + default = "%sValues::%s" % (enum, getEnumValueName(defaultValue.value)) + else: + default = None + + return handleOptional(template, CGGeneric(enum), default) + + if type.isCallback(): + assert not isEnforceRange and not isClamp + assert not type.treatNonCallableAsNull() + assert not type.treatNonObjectAsNull() or type.nullable() + assert not type.treatNonObjectAsNull() or not type.treatNonCallableAsNull() + + declType = CGGeneric('%s::%s' % (type.unroll().module(), type.unroll().identifier.name)) + + conversion = CGCallbackTempRoot(declType.define()) + + if type.nullable(): + declType = CGTemplatedType("Option", declType) + conversion = CGWrapper(conversion, pre="Some(", post=")") + + if allowTreatNonObjectAsNull and type.treatNonObjectAsNull(): + if not isDefinitelyObject: + haveObject = "${val}.is_object()" + template = CGIfElseWrapper(haveObject, + conversion, + CGGeneric("None")).define() + else: + template = conversion + else: + template = CGIfElseWrapper("JS_ObjectIsCallable(cx, ${val}.to_object()) != 0", + conversion, + onFailureNotCallable(failureCode)).define() + template = wrapObjectTemplate( + template, + isDefinitelyObject, + type, + failureCode) + + if defaultValue is not None: + assert allowTreatNonObjectAsNull + assert type.treatNonObjectAsNull() + assert type.nullable() + assert isinstance(defaultValue, IDLNullValue) + default = "None" + else: + default = None + + return (template, default, declType, needsRooting) + + if type.isAny(): + assert not isEnforceRange and not isClamp + + declType = CGGeneric("JSVal") + + if defaultValue is None: + default = None + elif isinstance(defaultValue, IDLNullValue): + default = "NullValue()" + elif isinstance(defaultValue, IDLUndefinedValue): + default = "UndefinedValue()" + else: + raise TypeError("Can't handle non-null, non-undefined default value here") + + return handleOptional("${val}", declType, default) + + if type.isObject(): + raise TypeError("Can't handle object arguments yet") + + if type.isDictionary(): + if failureCode is not None: + raise TypeError("Can't handle dictionaries when failureCode is not None") + # There are no nullable dictionaries + assert not type.nullable() + + typeName = CGDictionary.makeDictionaryName(type.inner) + declType = CGGeneric(typeName) + template = ("match %s::new(cx, ${val}) {\n" + " Ok(dictionary) => dictionary,\n" + " Err(_) => return 0,\n" + "}" % typeName) + + return handleOptional(template, declType, handleDefaultNull("%s::empty()" % typeName)) + + if type.isVoid(): + # This one only happens for return values, and its easy: Just + # ignore the jsval. + return ("", None, None, False) + + if not type.isPrimitive(): + raise TypeError("Need conversion for argument type '%s'" % str(type)) + + assert not isEnforceRange and not isClamp + + if failureCode is None: + failureCode = 'return 0' + + declType = CGGeneric(builtinNames[type.tag()]) + if type.nullable(): + declType = CGWrapper(declType, pre="Option<", post=">") + + #XXXjdm support conversionBehavior here + template = ( + "match FromJSValConvertible::from_jsval(cx, ${val}, ()) {\n" + " Ok(v) => v,\n" + " Err(_) => { %s }\n" + "}" % exceptionCode) + + if defaultValue is not None: + if isinstance(defaultValue, IDLNullValue): + assert type.nullable() + defaultStr = "None" + else: + tag = defaultValue.type.tag() + if tag in numericTags: + defaultStr = str(defaultValue.value) + else: + assert(tag == IDLType.Tags.bool) + defaultStr = toStringBool(defaultValue.value) + + if type.nullable(): + defaultStr = "Some(%s)" % defaultStr + else: + defaultStr = None + + return handleOptional(template, declType, defaultStr) + +def instantiateJSToNativeConversionTemplate(templateBody, replacements, + declType, declName, needsRooting): + """ + Take the templateBody and declType as returned by + getJSToNativeConversionTemplate, a set of replacements as required by the + strings in such a templateBody, and a declName, and generate code to + convert into a stack Rust binding with that name. + """ + result = CGList([], "\n") + + conversion = CGGeneric( + string.Template(templateBody).substitute(replacements) + ) + + if declType is not None: + newDecl = [ + CGGeneric("let "), + CGGeneric(declName), + CGGeneric(": "), + declType, + CGGeneric(" = "), + conversion, + CGGeneric(";"), + ] + result.append(CGList(newDecl)) + else: + result.append(conversion) + + # Add an empty CGGeneric to get an extra newline after the argument + # conversion. + result.append(CGGeneric("")) + + if needsRooting: + rootBody = "let %s = %s.root();" % (declName, declName) + result.append(CGGeneric(rootBody)) + result.append(CGGeneric("")) + + return result; + +def convertConstIDLValueToJSVal(value): + if isinstance(value, IDLNullValue): + return "NullVal" + tag = value.type.tag() + if tag in [IDLType.Tags.int8, IDLType.Tags.uint8, IDLType.Tags.int16, + IDLType.Tags.uint16, IDLType.Tags.int32]: + return "IntVal(%s)" % (value.value) + if tag == IDLType.Tags.uint32: + return "UintVal(%s)" % (value.value) + if tag in [IDLType.Tags.int64, IDLType.Tags.uint64]: + return "DoubleVal(%s)" % (value.value) + if tag == IDLType.Tags.bool: + return "BoolVal(true)" if value.value else "BoolVal(false)" + if tag in [IDLType.Tags.float, IDLType.Tags.double]: + return "DoubleVal(%s)" % (value.value) + raise TypeError("Const value of unhandled type: " + value.type) + +class CGArgumentConverter(CGThing): + """ + A class that takes an IDL argument object, its index in the + argument list, and the argv and argc strings and generates code to + unwrap the argument to the right native type. + """ + def __init__(self, argument, index, argv, argc, descriptorProvider, + invalidEnumValueFatal=True): + CGThing.__init__(self) + assert(not argument.defaultValue or argument.optional) + + replacer = { + "index": index, + "argc": argc, + "argv": argv + } + condition = string.Template("${index} < ${argc}").substitute(replacer) + + replacementVariables = { + "val": string.Template("(*${argv}.offset(${index}))").substitute(replacer), + } + + template, default, declType, needsRooting = getJSToNativeConversionTemplate( + argument.type, + descriptorProvider, + invalidEnumValueFatal=invalidEnumValueFatal, + defaultValue=argument.defaultValue, + treatNullAs=argument.treatNullAs, + isEnforceRange=argument.enforceRange, + isClamp=argument.clamp, + isMember="Variadic" if argument.variadic else False, + allowTreatNonObjectAsNull=argument.allowTreatNonCallableAsNull()) + + if not argument.variadic: + if argument.optional: + if argument.defaultValue: + assert default + template = CGIfElseWrapper(condition, + CGGeneric(template), + CGGeneric(default)).define() + else: + assert not default + declType = CGWrapper(declType, pre="Option<", post=">") + template = CGIfElseWrapper(condition, + CGGeneric("Some(%s)" % template), + CGGeneric("None")).define() + else: + assert not default + + self.converter = instantiateJSToNativeConversionTemplate( + template, replacementVariables, declType, "arg%d" % index, + needsRooting) + else: + assert argument.optional + variadicConversion = { + "val": string.Template("(*${argv}.offset(variadicArg as int))").substitute(replacer), + } + innerConverter = instantiateJSToNativeConversionTemplate( + template, variadicConversion, declType, "slot", + needsRooting) + + seqType = CGTemplatedType("Vec", declType) + variadicConversion = string.Template( + "{\n" + " let mut vector: ${seqType} = Vec::with_capacity((${argc} - ${index}) as uint);\n" + " for variadicArg in range(${index}, ${argc}) {\n" + "${inner}\n" + " vector.push(slot);\n" + " }\n" + " vector\n" + "}" + ).substitute({ + "index": index, + "argc": argc, + "seqType": seqType.define(), + "inner": CGIndenter(innerConverter, 4).define(), + }) + + self.converter = instantiateJSToNativeConversionTemplate( + variadicConversion, replacementVariables, seqType, "arg%d" % index, + False) + + def define(self): + return self.converter.define() + + +def wrapForType(jsvalRef, result='result', successCode='return 1;'): + """ + Reflect a Rust value into JS. + + * 'jsvalRef': a Rust reference to the JSVal in which to store the result + of the conversion; + * 'result': the name of the variable in which the Rust value is stored; + * 'successCode': the code to run once we have done the conversion. + """ + return "%s = (%s).to_jsval(cx);\n%s" % (jsvalRef, result, successCode) + + +def typeNeedsCx(type, retVal=False): + if type is None: + return False + if type.nullable(): + type = type.inner + if type.isSequence() or type.isArray(): + type = type.inner + if type.isUnion(): + return any(typeNeedsCx(t) for t in type.unroll().flatMemberTypes) + if retVal and type.isSpiderMonkeyInterface(): + return True + return type.isAny() or type.isObject() + +def typeRetValNeedsRooting(type): + if type is None: + return False + if type.nullable(): + type = type.inner + return type.isGeckoInterface() and not type.isCallback() and not type.isCallbackInterface() + +def memberIsCreator(member): + return member.getExtendedAttribute("Creator") is not None + +# Returns a CGThing containing the type of the return value. +def getRetvalDeclarationForType(returnType, descriptorProvider): + if returnType is None or returnType.isVoid(): + # Nothing to declare + return CGGeneric("()") + if returnType.isPrimitive() and returnType.tag() in builtinNames: + result = CGGeneric(builtinNames[returnType.tag()]) + if returnType.nullable(): + result = CGWrapper(result, pre="Option<", post=">") + return result + if returnType.isDOMString(): + result = CGGeneric("DOMString") + if returnType.nullable(): + result = CGWrapper(result, pre="Option<", post=">") + return result + if returnType.isByteString(): + result = CGGeneric("ByteString") + if returnType.nullable(): + result = CGWrapper(result, pre="Option<", post=">") + return result + if returnType.isEnum(): + result = CGGeneric(returnType.unroll().inner.identifier.name) + if returnType.nullable(): + result = CGWrapper(result, pre="Option<", post=">") + return result + if returnType.isGeckoInterface(): + descriptor = descriptorProvider.getDescriptor( + returnType.unroll().inner.identifier.name) + result = CGGeneric(descriptor.returnType) + if returnType.nullable(): + result = CGWrapper(result, pre="Option<", post=">") + return result + if returnType.isCallback(): + result = CGGeneric('%s::%s' % (returnType.unroll().module(), + returnType.unroll().identifier.name)) + if returnType.nullable(): + result = CGWrapper(result, pre="Option<", post=">") + return result + if returnType.isUnion(): + result = CGGeneric('%s::%s' % (returnType.unroll().name, returnType.unroll().name)) + if returnType.nullable(): + result = CGWrapper(result, pre="Option<", post=">") + return result + if returnType.isAny(): + return CGGeneric("JSVal") + if returnType.isObject() or returnType.isSpiderMonkeyInterface(): + return CGGeneric("*mut JSObject") + if returnType.isSequence(): + raise TypeError("We don't support sequence return values") + + raise TypeError("Don't know how to declare return value for %s" % + returnType) + +class PropertyDefiner: + """ + A common superclass for defining things on prototype objects. + + Subclasses should implement generateArray to generate the actual arrays of + things we're defining. They should also set self.regular to the list of + things exposed to web pages. + """ + def __init__(self, descriptor, name): + self.descriptor = descriptor + self.name = name + + def variableName(self): + return "s" + self.name + + def length(self): + return len(self.regular) + + def __str__(self): + # We only need to generate id arrays for things that will end + # up used via ResolveProperty or EnumerateProperties. + return self.generateArray(self.regular, self.variableName()) + + def generatePrefableArray(self, array, name, specTemplate, specTerminator, + specType, getDataTuple): + """ + This method generates our various arrays. + + array is an array of interface members as passed to generateArray + + name is the name as passed to generateArray + + specTemplate is a template for each entry of the spec array + + specTerminator is a terminator for the spec array (inserted at the end + of the array), or None + + specType is the actual typename of our spec + + getDataTuple is a callback function that takes an array entry and + returns a tuple suitable for substitution into specTemplate. + """ + + assert(len(array) is not 0) + specs = [] + + for member in array: + specs.append(specTemplate % getDataTuple(member)) + if specTerminator: + specs.append(specTerminator) + + return (("static %s: &'static [%s] = &[\n" + + ",\n".join(specs) + "\n" + + "];\n\n") % (name, specType)) + +# The length of a method is the maximum of the lengths of the +# argument lists of all its overloads. +def methodLength(method): + signatures = method.signatures() + return max([len(arguments) for (retType, arguments) in signatures]) + +class MethodDefiner(PropertyDefiner): + """ + A class for defining methods on a prototype object. + """ + def __init__(self, descriptor, name, static): + PropertyDefiner.__init__(self, descriptor, name) + + # FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=772822 + # We should be able to check for special operations without an + # identifier. For now we check if the name starts with __ + methods = [m for m in descriptor.interface.members if + m.isMethod() and m.isStatic() == static and + not m.isIdentifierLess()] + self.regular = [{"name": m.identifier.name, + "methodInfo": not m.isStatic(), + "length": methodLength(m), + "flags": "JSPROP_ENUMERATE" } + for m in methods] + + # FIXME Check for an existing iterator on the interface first. + if any(m.isGetter() and m.isIndexed() for m in methods): + self.regular.append({"name": 'iterator', + "methodInfo": False, + "nativeName": "JS_ArrayIterator", + "length": 0, + "flags": "JSPROP_ENUMERATE" }) + + def generateArray(self, array, name): + if len(array) == 0: + return "" + + def specData(m): + if m.get("methodInfo", True): + jitinfo = ("&%s_methodinfo" % m["name"]) + accessor = "genericMethod" + else: + jitinfo = "0 as *const JSJitInfo" + accessor = m.get("nativeName", m["name"]) + return (m["name"], accessor, jitinfo, m["length"], m["flags"]) + + def stringDecl(m): + return "static %s_name: [u8, ..%i] = %s;\n" % (m["name"], len(m["name"]) + 1, + str_to_const_array(m["name"])) + + decls = ''.join([stringDecl(m) for m in array]) + return decls + self.generatePrefableArray( + array, name, + ' JSFunctionSpec {name: &%s_name as *const u8 as *const libc::c_char, call: JSNativeWrapper {op: Some(%s), info: %s}, nargs: %s, flags: %s as u16, selfHostedName: 0 as *const libc::c_char }', + ' JSFunctionSpec {name: 0 as *const libc::c_char, call: JSNativeWrapper {op: None, info: 0 as *const JSJitInfo}, nargs: 0, flags: 0, selfHostedName: 0 as *const libc::c_char }', + 'JSFunctionSpec', + specData) + +class AttrDefiner(PropertyDefiner): + def __init__(self, descriptor, name, static): + PropertyDefiner.__init__(self, descriptor, name) + self.name = name + self.regular = [ + m + for m in descriptor.interface.members + if m.isAttr() and m.isStatic() == static + ] + self.static = static + + def generateArray(self, array, name): + if len(array) == 0: + return "" + + def flags(attr): + return "JSPROP_SHARED | JSPROP_ENUMERATE | JSPROP_NATIVE_ACCESSORS" + + def getter(attr): + if self.static: + accessor = 'get_' + attr.identifier.name + jitinfo = "0" + else: + if attr.hasLenientThis(): + accessor = "genericLenientGetter" + else: + accessor = "genericGetter" + jitinfo = "&%s_getterinfo" % attr.identifier.name + + return ("JSPropertyOpWrapper {op: Some(%(native)s), info: %(info)s as *const JSJitInfo}" + % {"info" : jitinfo, + "native" : accessor}) + + def setter(attr): + if attr.readonly: + return "JSStrictPropertyOpWrapper {op: None, info: 0 as *const JSJitInfo}" + + if self.static: + accessor = 'set_' + attr.identifier.name + jitinfo = "0" + else: + if attr.hasLenientThis(): + accessor = "genericLenientSetter" + else: + accessor = "genericSetter" + jitinfo = "&%s_setterinfo" % attr.identifier.name + + return ("JSStrictPropertyOpWrapper {op: Some(%(native)s), info: %(info)s as *const JSJitInfo}" + % {"info" : jitinfo, + "native" : accessor}) + + def specData(attr): + return (attr.identifier.name, flags(attr), getter(attr), + setter(attr)) + + def stringDecl(attr): + name = attr.identifier.name + return "static %s_name: [u8, ..%i] = %s;\n" % (name, len(name) + 1, + str_to_const_array(name)) + + decls = ''.join([stringDecl(m) for m in array]) + + return decls + self.generatePrefableArray( + array, name, + ' JSPropertySpec { name: &%s_name as *const u8 as *const libc::c_char, tinyid: 0, flags: ((%s) & 0xFF) as u8, getter: %s, setter: %s }', + ' JSPropertySpec { name: 0 as *const libc::c_char, tinyid: 0, flags: 0, getter: JSPropertyOpWrapper {op: None, info: 0 as *const JSJitInfo}, setter: JSStrictPropertyOpWrapper {op: None, info: 0 as *const JSJitInfo} }', + 'JSPropertySpec', + specData) + +class ConstDefiner(PropertyDefiner): + """ + A class for definining constants on the interface object + """ + def __init__(self, descriptor, name): + PropertyDefiner.__init__(self, descriptor, name) + self.name = name + self.regular = [m for m in descriptor.interface.members if m.isConst()] + + def generateArray(self, array, name): + if len(array) == 0: + return "" + + def specData(const): + return (const.identifier.name, + convertConstIDLValueToJSVal(const.value)) + + def stringDecl(const): + name = const.identifier.name + return "static %s_name: &'static [u8] = &%s;\n" % (name, str_to_const_array(name)) + + decls = ''.join([stringDecl(m) for m in array]) + + return decls + self.generatePrefableArray( + array, name, + ' ConstantSpec { name: %s_name, value: %s }', + None, + 'ConstantSpec', + specData) + +# We'll want to insert the indent at the beginnings of lines, but we +# don't want to indent empty lines. So only indent lines that have a +# non-newline character on them. +lineStartDetector = re.compile("^(?=[^\n])", re.MULTILINE) +class CGIndenter(CGThing): + """ + A class that takes another CGThing and generates code that indents that + CGThing by some number of spaces. The default indent is two spaces. + """ + def __init__(self, child, indentLevel=2): + CGThing.__init__(self) + self.child = child + self.indent = " " * indentLevel + + def define(self): + defn = self.child.define() + if defn is not "": + return re.sub(lineStartDetector, self.indent, defn) + else: + return defn + +class CGWrapper(CGThing): + """ + Generic CGThing that wraps other CGThings with pre and post text. + """ + def __init__(self, child, pre="", post="", reindent=False): + CGThing.__init__(self) + self.child = child + self.pre = pre + self.post = post + self.reindent = reindent + + def define(self): + defn = self.child.define() + if self.reindent: + # We don't use lineStartDetector because we don't want to + # insert whitespace at the beginning of our _first_ line. + defn = stripTrailingWhitespace( + defn.replace("\n", "\n" + (" " * len(self.pre)))) + return self.pre + defn + self.post + +class CGImports(CGWrapper): + """ + Generates the appropriate import/use statements. + """ + def __init__(self, child, descriptors, imports): + """ + Adds a set of imports. + """ + ignored_warnings = [ + # Allow unreachable_code because we use 'break' in a way that + # sometimes produces two 'break's in a row. See for example + # CallbackMember.getArgConversions. + 'unreachable_code', + 'non_camel_case_types', + 'non_uppercase_statics', + 'unnecessary_parens', + 'unused_imports', + 'unused_variable', + 'unused_unsafe', + 'unused_mut', + 'dead_assignment', + 'dead_code', + ] + + statements = ['#![allow(%s)]' % ','.join(ignored_warnings)] + statements.extend('use %s;' % i for i in sorted(imports)) + + CGWrapper.__init__(self, child, + pre='\n'.join(statements) + '\n\n') + + @staticmethod + def getDeclarationFilename(decl): + # Use our local version of the header, not the exported one, so that + # test bindings, which don't export, will work correctly. + basename = os.path.basename(decl.filename()) + return basename.replace('.webidl', 'Binding.rs') + +class CGIfWrapper(CGWrapper): + def __init__(self, child, condition): + pre = CGWrapper(CGGeneric(condition), pre="if ", post=" {\n", + reindent=True) + CGWrapper.__init__(self, CGIndenter(child), pre=pre.define(), + post="\n}") + +class CGTemplatedType(CGWrapper): + def __init__(self, templateName, child): + CGWrapper.__init__(self, child, pre=templateName + "<", post=">") + +class CGNamespace(CGWrapper): + def __init__(self, namespace, child, public=False): + pre = "%smod %s {\n" % ("pub " if public else "", namespace) + post = "} // mod %s\n" % namespace + CGWrapper.__init__(self, child, pre=pre, post=post) + + @staticmethod + def build(namespaces, child, public=False): + """ + Static helper method to build multiple wrapped namespaces. + """ + if not namespaces: + return child + inner = CGNamespace.build(namespaces[1:], child, public=public) + return CGNamespace(namespaces[0], inner, public=public) + +def DOMClass(descriptor): + protoList = ['PrototypeList::id::' + proto for proto in descriptor.prototypeChain] + # Pad out the list to the right length with IDCount so we + # guarantee that all the lists are the same length. IDCount + # is never the ID of any prototype, so it's safe to use as + # padding. + protoList.extend(['PrototypeList::id::IDCount'] * (descriptor.config.maxProtoChainLength - len(protoList))) + prototypeChainString = ', '.join(protoList) + return """DOMClass { + interface_chain: [ %s ], + native_hooks: &sNativePropertyHooks, +}""" % prototypeChainString + +class CGDOMJSClass(CGThing): + """ + Generate a DOMJSClass for a given descriptor + """ + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + + def define(self): + traceHook = "Some(%s)" % TRACE_HOOK_NAME + if self.descriptor.isGlobal(): + flags = "JSCLASS_IS_GLOBAL | JSCLASS_DOM_GLOBAL" + slots = "JSCLASS_GLOBAL_SLOT_COUNT + 1" + else: + flags = "0" + slots = "1" + return """ +static Class_name: [u8, ..%i] = %s; +static Class: DOMJSClass = DOMJSClass { + base: js::Class { + name: &Class_name as *const u8 as *const libc::c_char, + flags: JSCLASS_IS_DOMJSCLASS | %s | (((%s) & JSCLASS_RESERVED_SLOTS_MASK) << JSCLASS_RESERVED_SLOTS_SHIFT as uint), //JSCLASS_HAS_RESERVED_SLOTS(%s), + addProperty: Some(JS_PropertyStub), + delProperty: Some(JS_PropertyStub), + getProperty: Some(JS_PropertyStub), + setProperty: Some(JS_StrictPropertyStub), + enumerate: Some(JS_EnumerateStub), + resolve: Some(JS_ResolveStub), + convert: Some(JS_ConvertStub), + finalize: Some(%s), + checkAccess: None, + call: None, + hasInstance: None, + construct: None, + trace: %s, + + ext: js::ClassExtension { + equality: 0 as *const u8, + outerObject: %s, + innerObject: None, + iteratorObject: 0 as *const u8, + unused: 0 as *const u8, + isWrappedNative: 0 as *const u8, + }, + + ops: js::ObjectOps { + lookupGeneric: 0 as *const u8, + lookupProperty: 0 as *const u8, + lookupElement: 0 as *const u8, + lookupSpecial: 0 as *const u8, + defineGeneric: 0 as *const u8, + defineProperty: 0 as *const u8, + defineElement: 0 as *const u8, + defineSpecial: 0 as *const u8, + getGeneric: 0 as *const u8, + getProperty: 0 as *const u8, + getElement: 0 as *const u8, + getElementIfPresent: 0 as *const u8, + getSpecial: 0 as *const u8, + setGeneric: 0 as *const u8, + setProperty: 0 as *const u8, + setElement: 0 as *const u8, + setSpecial: 0 as *const u8, + getGenericAttributes: 0 as *const u8, + getPropertyAttributes: 0 as *const u8, + getElementAttributes: 0 as *const u8, + getSpecialAttributes: 0 as *const u8, + setGenericAttributes: 0 as *const u8, + setPropertyAttributes: 0 as *const u8, + setElementAttributes: 0 as *const u8, + setSpecialAttributes: 0 as *const u8, + deleteProperty: 0 as *const u8, + deleteElement: 0 as *const u8, + deleteSpecial: 0 as *const u8, + + enumerate: 0 as *const u8, + typeOf: 0 as *const u8, + thisObject: %s, + clear: 0 as *const u8, + }, + }, + dom_class: %s +}; +""" % (len(self.descriptor.interface.identifier.name) + 1, + str_to_const_array(self.descriptor.interface.identifier.name), + flags, slots, slots, + FINALIZE_HOOK_NAME, traceHook, + self.descriptor.outerObjectHook, + self.descriptor.outerObjectHook, + CGIndenter(CGGeneric(DOMClass(self.descriptor))).define()) + +def str_to_const_array(s): + return "[" + (", ".join(map(lambda x: "'" + x + "' as u8", list(s)) + ['0 as u8'])) + "]" + +class CGPrototypeJSClass(CGThing): + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + + def define(self): + return """ +static PrototypeClassName__: [u8, ..%s] = %s; +static PrototypeClass: JSClass = JSClass { + name: &PrototypeClassName__ as *const u8 as *const libc::c_char, + flags: (1 & JSCLASS_RESERVED_SLOTS_MASK) << JSCLASS_RESERVED_SLOTS_SHIFT as uint, //JSCLASS_HAS_RESERVED_SLOTS(1) + addProperty: Some(JS_PropertyStub), + delProperty: Some(JS_PropertyStub), + getProperty: Some(JS_PropertyStub), + setProperty: Some(JS_StrictPropertyStub), + enumerate: Some(JS_EnumerateStub), + resolve: Some(JS_ResolveStub), + convert: Some(JS_ConvertStub), + finalize: None, + checkAccess: None, + call: None, + hasInstance: None, + construct: None, + trace: None, + reserved: [0 as *mut libc::c_void, ..40] +}; +""" % (len(self.descriptor.interface.identifier.name + "Prototype") + 1, + str_to_const_array(self.descriptor.interface.identifier.name + "Prototype")) + +class CGInterfaceObjectJSClass(CGThing): + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + + def define(self): + if True: + return "" + ctorname = "0 as *const u8" if not self.descriptor.interface.ctor() else CONSTRUCT_HOOK_NAME + hasinstance = HASINSTANCE_HOOK_NAME + return """ +static InterfaceObjectClass: JSClass = { + %s, 0, + JS_PropertyStub, + JS_PropertyStub, + JS_PropertyStub, + JS_StrictPropertyStub, + JS_EnumerateStub, + JS_ResolveStub, + JS_ConvertStub, + 0 as *const u8, + 0 as *const u8, + %s, + %s, + %s, + 0 as *const u8, + JSCLASS_NO_INTERNAL_MEMBERS +}; +""" % (str_to_const_array("Function"), ctorname, hasinstance, ctorname) + +class CGList(CGThing): + """ + Generate code for a list of GCThings. Just concatenates them together, with + an optional joiner string. "\n" is a common joiner. + """ + def __init__(self, children, joiner=""): + CGThing.__init__(self) + self.children = children + self.joiner = joiner + def append(self, child): + self.children.append(child) + def prepend(self, child): + self.children.insert(0, child) + def join(self, generator): + return self.joiner.join(filter(lambda s: len(s) > 0, (child for child in generator))) + + def define(self): + return self.join(child.define() for child in self.children if child is not None) + + +class CGIfElseWrapper(CGList): + def __init__(self, condition, ifTrue, ifFalse): + kids = [ CGIfWrapper(ifTrue, condition), + CGWrapper(CGIndenter(ifFalse), pre=" else {\n", post="\n}") ] + CGList.__init__(self, kids) + + +class CGGeneric(CGThing): + """ + A class that spits out a fixed string into the codegen. Can spit out a + separate string for the declaration too. + """ + def __init__(self, text): + self.text = text + + def define(self): + return self.text + +class CGCallbackTempRoot(CGGeneric): + def __init__(self, name): + val = "%s::new(tempRoot)" % name + define = """{ + let tempRoot = ${val}.to_object(); + %s +}""" % val + CGGeneric.__init__(self, define) + + +def getAllTypes(descriptors, dictionaries, callbacks): + """ + Generate all the types we're dealing with. For each type, a tuple + containing type, descriptor, dictionary is yielded. The + descriptor and dictionary can be None if the type does not come + from a descriptor or dictionary; they will never both be non-None. + """ + for d in descriptors: + for t in getTypesFromDescriptor(d): + yield (t, d, None) + for dictionary in dictionaries: + for t in getTypesFromDictionary(dictionary): + yield (t, None, dictionary) + for callback in callbacks: + for t in getTypesFromCallback(callback): + yield (t, None, None) + +def SortedTuples(l): + """ + Sort a list of tuples based on the first item in the tuple + """ + return sorted(l, key=operator.itemgetter(0)) + +def SortedDictValues(d): + """ + Returns a list of values from the dict sorted by key. + """ + # Create a list of tuples containing key and value, sorted on key. + d = SortedTuples(d.items()) + # We're only interested in the values. + return (i[1] for i in d) + +def UnionTypes(descriptors, dictionaries, callbacks, config): + """ + Returns a CGList containing CGUnionStructs for every union. + """ + + imports = [ + 'dom::bindings::utils::unwrap_jsmanaged', + 'dom::bindings::codegen::PrototypeList', + 'dom::bindings::conversions::FromJSValConvertible', + 'dom::bindings::conversions::ToJSValConvertible', + 'dom::bindings::conversions::Default', + 'dom::bindings::error::throw_not_in_union', + 'dom::bindings::js::JS', + 'dom::types::*', + 'js::jsapi::JSContext', + 'js::jsval::JSVal', + 'servo_util::str::DOMString', + ] + + # Now find all the things we'll need as arguments and return values because + # we need to wrap or unwrap them. + unionStructs = dict() + for (t, descriptor, dictionary) in getAllTypes(descriptors, dictionaries, callbacks): + assert not descriptor or not dictionary + t = t.unroll() + if not t.isUnion(): + continue + name = str(t) + if not name in unionStructs: + provider = descriptor or config.getDescriptorProvider() + unionStructs[name] = CGNamespace(name, + CGImports(CGList([ + CGUnionStruct(t, provider), + CGUnionConversionStruct(t, provider) + ]), [], imports), + public=True) + + return CGList(SortedDictValues(unionStructs), "\n\n") + + +class Argument(): + """ + A class for outputting the type and name of an argument + """ + def __init__(self, argType, name, default=None, mutable=False): + self.argType = argType + self.name = name + self.default = default + self.mutable = mutable + def declare(self): + string = ('mut ' if self.mutable else '') + self.name + ((': ' + self.argType) if self.argType else '') + #XXXjdm Support default arguments somehow :/ + #if self.default is not None: + # string += " = " + self.default + return string + def define(self): + return self.argType + ' ' + self.name + +class CGAbstractMethod(CGThing): + """ + An abstract class for generating code for a method. Subclasses + should override definition_body to create the actual code. + + descriptor is the descriptor for the interface the method is associated with + + name is the name of the method as a string + + returnType is the IDLType of the return value + + args is a list of Argument objects + + inline should be True to generate an inline method, whose body is + part of the declaration. + + alwaysInline should be True to generate an inline method annotated with + MOZ_ALWAYS_INLINE. + + If templateArgs is not None it should be a list of strings containing + template arguments, and the function will be templatized using those + arguments. + """ + def __init__(self, descriptor, name, returnType, args, inline=False, alwaysInline=False, extern=False, pub=False, templateArgs=None, unsafe=True): + CGThing.__init__(self) + self.descriptor = descriptor + self.name = name + self.returnType = returnType + self.args = args + self.alwaysInline = alwaysInline + self.extern = extern + self.templateArgs = templateArgs + self.pub = pub; + self.unsafe = unsafe + def _argstring(self): + return ', '.join([a.declare() for a in self.args]) + def _template(self): + if self.templateArgs is None: + return '' + return '<%s>\n' % ', '.join(self.templateArgs) + + def _decorators(self): + decorators = [] + if self.alwaysInline: + decorators.append('#[inline(always)]') + + if self.extern: + decorators.append('extern') + + if self.pub: + decorators.append('pub') + + if not decorators: + return '' + return ' '.join(decorators) + ' ' + + def _returnType(self): + return (" -> %s" % self.returnType) if self.returnType != "void" else "" + + def define(self): + body = self.definition_body() + if self.unsafe: + body = CGWrapper(body, pre="unsafe {\n", post="\n}") + + return CGWrapper(CGIndenter(body), + pre=self.definition_prologue(), + post=self.definition_epilogue()).define() + + def definition_prologue(self): + return "%sfn %s%s(%s)%s {\n" % (self._decorators(), self.name, self._template(), + self._argstring(), self._returnType()) + def definition_epilogue(self): + return "\n}\n" + def definition_body(self): + assert(False) # Override me! + +def CreateBindingJSObject(descriptor, parent=None): + create = "let mut raw: JS<%s> = JS::from_raw(&*aObject);\n" % descriptor.concreteType + if descriptor.proxy: + assert not descriptor.isGlobal() + create += """ +let handler = RegisterBindings::proxy_handlers[PrototypeList::proxies::%s as uint]; +let mut private = PrivateValue(squirrel_away_unique(aObject) as *const libc::c_void); +let obj = with_compartment(aCx, proto, || { + NewProxyObject(aCx, handler, + &private, + proto, %s, + ptr::mut_null(), ptr::mut_null()) +}); +assert!(obj.is_not_null()); + +""" % (descriptor.name, parent) + else: + if descriptor.isGlobal(): + create += "let obj = CreateDOMGlobal(aCx, &Class.base as *const js::Class as *const JSClass);\n" + else: + create += ("let obj = with_compartment(aCx, proto, || {\n" + " JS_NewObject(aCx, &Class.base as *const js::Class as *const JSClass, &*proto, &*%s)\n" + "});\n" % parent) + create += """assert!(obj.is_not_null()); + +JS_SetReservedSlot(obj, DOM_OBJECT_SLOT as u32, + PrivateValue(squirrel_away_unique(aObject) as *const libc::c_void)); +""" + return create + +class CGWrapMethod(CGAbstractMethod): + """ + Class that generates the FooBinding::Wrap function for non-callback + interfaces. + """ + def __init__(self, descriptor): + assert not descriptor.interface.isCallback() + if not descriptor.isGlobal(): + args = [Argument('*mut JSContext', 'aCx'), Argument('&GlobalRef', 'aScope'), + Argument("Box<%s>" % descriptor.concreteType, 'aObject', mutable=True)] + else: + args = [Argument('*mut JSContext', 'aCx'), + Argument("Box<%s>" % descriptor.concreteType, 'aObject', mutable=True)] + retval = 'Temporary<%s>' % descriptor.concreteType + CGAbstractMethod.__init__(self, descriptor, 'Wrap', retval, args, pub=True) + + def definition_body(self): + if not self.descriptor.isGlobal(): + return CGGeneric("""\ +let scope = aScope.reflector().get_jsobject(); +assert!(scope.is_not_null()); +assert!(((*JS_GetClass(scope)).flags & JSCLASS_IS_GLOBAL) != 0); + +let proto = with_compartment(aCx, scope, || GetProtoObject(aCx, scope, scope)); +assert!(proto.is_not_null()); + +%s + +raw.reflector().set_jsobject(obj); + +Temporary::new(raw)""" % CreateBindingJSObject(self.descriptor, "scope")) + else: + return CGGeneric("""\ +%s +with_compartment(aCx, obj, || { + let proto = GetProtoObject(aCx, obj, obj); + JS_SetPrototype(aCx, obj, proto); + + raw.reflector().set_jsobject(obj); + + RegisterBindings::Register(aCx, obj); +}); + +Temporary::new(raw)""" % CreateBindingJSObject(self.descriptor)) + + +class CGIDLInterface(CGThing): + """ + Class for codegen of an implementation of the IDLInterface trait. + """ + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + + def define(self): + replacer = { + 'type': self.descriptor.name, + 'depth': self.descriptor.interface.inheritanceDepth(), + } + return string.Template(""" +impl IDLInterface for ${type} { + fn get_prototype_id(_: Option<${type}>) -> PrototypeList::id::ID { + PrototypeList::id::${type} + } + fn get_prototype_depth(_: Option<${type}>) -> uint { + ${depth} + } +} +""").substitute(replacer) + + +class CGAbstractExternMethod(CGAbstractMethod): + """ + Abstract base class for codegen of implementation-only (no + declaration) static methods. + """ + def __init__(self, descriptor, name, returnType, args): + CGAbstractMethod.__init__(self, descriptor, name, returnType, args, + inline=False, extern=True) + +class PropertyArrays(): + def __init__(self, descriptor): + self.staticMethods = MethodDefiner(descriptor, "StaticMethods", + static=True) + self.staticAttrs = AttrDefiner(descriptor, "StaticAttributes", + static=True) + self.methods = MethodDefiner(descriptor, "Methods", static=False) + self.attrs = AttrDefiner(descriptor, "Attributes", static=False) + self.consts = ConstDefiner(descriptor, "Constants") + pass + + @staticmethod + def arrayNames(): + return [ "staticMethods", "staticAttrs", "methods", "attrs", "consts" ] + + def variableNames(self): + names = {} + for array in self.arrayNames(): + names[array] = getattr(self, array).variableName() + return names + def __str__(self): + define = "" + for array in self.arrayNames(): + define += str(getattr(self, array)) + return define + + +class CGNativeProperties(CGThing): + def __init__(self, descriptor, properties): + CGThing.__init__(self) + self.properties = properties + + def define(self): + def getField(array): + propertyArray = getattr(self.properties, array) + if propertyArray.length() > 0: + value = "Some(%s)" % propertyArray.variableName() + else: + value = "None" + + return CGGeneric(string.Template('${name}: ${value},').substitute({ + 'name': array, + 'value': value, + })) + + nativeProps = CGList([getField(array) for array in self.properties.arrayNames()], '\n') + return CGWrapper(CGIndenter(nativeProps), + pre="static sNativeProperties: NativeProperties = NativeProperties {\n", + post="\n};\n").define() + + +class CGCreateInterfaceObjectsMethod(CGAbstractMethod): + """ + Generate the CreateInterfaceObjects method for an interface descriptor. + + properties should be a PropertyArrays instance. + """ + def __init__(self, descriptor, properties): + assert not descriptor.interface.isCallback() + args = [Argument('*mut JSContext', 'aCx'), Argument('*mut JSObject', 'aGlobal'), + Argument('*mut JSObject', 'aReceiver')] + CGAbstractMethod.__init__(self, descriptor, 'CreateInterfaceObjects', '*mut JSObject', args) + self.properties = properties + def definition_body(self): + protoChain = self.descriptor.prototypeChain + if len(protoChain) == 1: + getParentProto = "JS_GetObjectPrototype(aCx, aGlobal)" + else: + parentProtoName = self.descriptor.prototypeChain[-2] + getParentProto = ("%s::GetProtoObject(aCx, aGlobal, aReceiver)" % + toBindingNamespace(parentProtoName)) + + getParentProto = ("let parentProto: *mut JSObject = %s;\n" + "assert!(parentProto.is_not_null());\n") % getParentProto + + if self.descriptor.concrete: + if self.descriptor.proxy: + domClass = "&Class" + else: + domClass = "&Class.dom_class" + else: + domClass = "ptr::null()" + + if self.descriptor.interface.hasInterfaceObject(): + if self.descriptor.interface.ctor(): + constructHook = CONSTRUCT_HOOK_NAME + constructArgs = methodLength(self.descriptor.interface.ctor()) + else: + constructHook = "ThrowingConstructor" + constructArgs = 0 + + constructor = 'Some((%s, "%s", %d))' % ( + constructHook, self.descriptor.interface.identifier.name, + constructArgs) + else: + constructor = 'None' + + call = """return CreateInterfaceObjects2(aCx, aGlobal, aReceiver, parentProto, + &PrototypeClass, %s, + %s, + &sNativeProperties);""" % (constructor, domClass) + + return CGList([ + CGGeneric(getParentProto), + CGGeneric(call % self.properties.variableNames()) + ], "\n") + +class CGGetPerInterfaceObject(CGAbstractMethod): + """ + A method for getting a per-interface object (a prototype object or interface + constructor object). + """ + def __init__(self, descriptor, name, idPrefix="", pub=False): + args = [Argument('*mut JSContext', 'aCx'), Argument('*mut JSObject', 'aGlobal'), + Argument('*mut JSObject', 'aReceiver')] + CGAbstractMethod.__init__(self, descriptor, name, + '*mut JSObject', args, pub=pub) + self.id = idPrefix + "id::" + self.descriptor.name + def definition_body(self): + return CGGeneric(""" + +/* aGlobal and aReceiver are usually the same, but they can be different + too. For example a sandbox often has an xray wrapper for a window as the + prototype of the sandbox's global. In that case aReceiver is the xray + wrapper and aGlobal is the sandbox's global. + */ + +assert!(((*JS_GetClass(aGlobal)).flags & JSCLASS_DOM_GLOBAL) != 0); + +/* Check to see whether the interface objects are already installed */ +let protoOrIfaceArray = GetProtoOrIfaceArray(aGlobal); +let cachedObject: *mut JSObject = *protoOrIfaceArray.offset(%s as int); +if cachedObject.is_null() { + let tmp: *mut JSObject = CreateInterfaceObjects(aCx, aGlobal, aReceiver); + assert!(tmp.is_not_null()); + *protoOrIfaceArray.offset(%s as int) = tmp; + tmp +} else { + cachedObject +}""" % (self.id, self.id)) + +class CGGetProtoObjectMethod(CGGetPerInterfaceObject): + """ + A method for getting the interface prototype object. + """ + def __init__(self, descriptor): + CGGetPerInterfaceObject.__init__(self, descriptor, "GetProtoObject", + "PrototypeList::", pub=True) + def definition_body(self): + return CGList([ + CGGeneric("""\ +/* Get the interface prototype object for this class. This will create the + object as needed. */"""), + CGGetPerInterfaceObject.definition_body(self), + ]) + +class CGGetConstructorObjectMethod(CGGetPerInterfaceObject): + """ + A method for getting the interface constructor object. + """ + def __init__(self, descriptor): + CGGetPerInterfaceObject.__init__(self, descriptor, "GetConstructorObject", + "constructors::") + def definition_body(self): + return CGList([ + CGGeneric("""\ +/* Get the interface object for this class. This will create the object as + needed. */"""), + CGGetPerInterfaceObject.definition_body(self), + ]) + + +class CGDefineProxyHandler(CGAbstractMethod): + """ + A method to create and cache the proxy trap for a given interface. + """ + def __init__(self, descriptor): + assert descriptor.proxy + CGAbstractMethod.__init__(self, descriptor, 'DefineProxyHandler', '*const libc::c_void', [], pub=True) + + def define(self): + return CGAbstractMethod.define(self) + + def definition_body(self): + body = """\ +let traps = ProxyTraps { + getPropertyDescriptor: Some(getPropertyDescriptor), + getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor), + defineProperty: Some(defineProperty), + getOwnPropertyNames: ptr::null(), + delete_: Some(delete_), + enumerate: ptr::null(), + + has: None, + hasOwn: Some(hasOwn), + get: Some(get), + set: None, + keys: ptr::null(), + iterate: None, + + call: None, + construct: None, + nativeCall: ptr::null(), + hasInstance: None, + typeOf: None, + objectClassIs: None, + obj_toString: Some(obj_toString), + fun_toString: None, + //regexp_toShared: ptr::null(), + defaultValue: None, + iteratorNext: None, + finalize: Some(%s), + getElementIfPresent: None, + getPrototypeOf: None, + trace: Some(%s) +}; + +CreateProxyHandler(&traps, &Class as *const _ as *const _) +""" % (FINALIZE_HOOK_NAME, + TRACE_HOOK_NAME) + return CGGeneric(body) + + + +class CGDefineDOMInterfaceMethod(CGAbstractMethod): + """ + A method for resolve hooks to try to lazily define the interface object for + a given interface. + """ + def __init__(self, descriptor): + assert descriptor.interface.hasInterfaceObject() + args = [ + Argument('*mut JSContext', 'cx'), + Argument('*mut JSObject', 'global'), + ] + CGAbstractMethod.__init__(self, descriptor, 'DefineDOMInterface', 'void', args, pub=True) + + def define(self): + return CGAbstractMethod.define(self) + + def definition_body(self): + return CGGeneric("""\ +assert!(global.is_not_null()); +assert!(GetProtoObject(cx, global, global).is_not_null());""") + +def needCx(returnType, arguments, considerTypes): + return (considerTypes and + (typeNeedsCx(returnType, True) or + any(typeNeedsCx(a.type) for a in arguments))) + +class CGCallGenerator(CGThing): + """ + A class to generate an actual call to a C++ object. Assumes that the C++ + object is stored in a variable whose name is given by the |object| argument. + + errorResult should be a string for the value to return in case of an + exception from the native code, or None if no error reporting is needed. + """ + def __init__(self, errorResult, arguments, argsPre, returnType, + extendedAttributes, descriptorProvider, nativeMethodName, + static, object="this"): + CGThing.__init__(self) + + assert errorResult is None or isinstance(errorResult, str) + + isFallible = errorResult is not None + + result = getRetvalDeclarationForType(returnType, descriptorProvider) + if isFallible: + result = CGWrapper(result, pre="Result<", post=", Error>") + + args = CGList([CGGeneric(arg) for arg in argsPre], ", ") + for (a, name) in arguments: + #XXXjdm Perhaps we should pass all nontrivial types by borrowed pointer + if a.type.isGeckoInterface(): + if not (a.type.nullable() or a.optional): + name = "&" + name + elif a.type.isDictionary(): + name = "&" + name + args.append(CGGeneric(name)) + + needsCx = needCx(returnType, (a for (a, _) in arguments), True) + + if not "cx" in argsPre and needsCx: + args.prepend(CGGeneric("cx")) + + # Build up our actual call + self.cgRoot = CGList([], "\n") + + call = CGGeneric(nativeMethodName) + if static: + call = CGWrapper(call, pre="%s::" % descriptorProvider.interface.identifier.name) + else: + call = CGWrapper(call, pre="(*%s)." % object) + call = CGList([call, CGWrapper(args, pre="(", post=")")]) + + self.cgRoot.append(CGList([ + CGGeneric("let result: "), + result, + CGGeneric(" = "), + call, + CGGeneric(";"), + ])) + + if isFallible: + if static: + glob = "" + else: + glob = " let global = global_object_for_js_object(this.reflector().get_jsobject());\n"\ + " let global = global.root();\n" + + self.cgRoot.append(CGGeneric( + "let result = match result {\n" + " Ok(result) => result,\n" + " Err(e) => {\n" + "%s" + " throw_dom_exception(cx, &global.root_ref(), e);\n" + " return%s;\n" + " },\n" + "};\n" % (glob, errorResult))) + + if typeRetValNeedsRooting(returnType): + self.cgRoot.append(CGGeneric("let result = result.root();")) + + def define(self): + return self.cgRoot.define() + +class MethodNotCreatorError(Exception): + def __init__(self, typename): + self.typename = typename + +class CGPerSignatureCall(CGThing): + """ + This class handles the guts of generating code for a particular + call signature. A call signature consists of four things: + + 1) A return type, which can be None to indicate that there is no + actual return value (e.g. this is an attribute setter) or an + IDLType if there's an IDL type involved (including |void|). + 2) An argument list, which is allowed to be empty. + 3) A name of a native method to call. + 4) Whether or not this method is static. + + We also need to know whether this is a method or a getter/setter + to do error reporting correctly. + + The idlNode parameter can be either a method or an attr. We can query + |idlNode.identifier| in both cases, so we can be agnostic between the two. + """ + # XXXbz For now each entry in the argument list is either an + # IDLArgument or a FakeArgument, but longer-term we may want to + # have ways of flagging things like JSContext* or optional_argc in + # there. + + def __init__(self, returnType, argsPre, arguments, nativeMethodName, static, + descriptor, idlNode, argConversionStartsAt=0, + getter=False, setter=False): + CGThing.__init__(self) + self.returnType = returnType + self.descriptor = descriptor + self.idlNode = idlNode + self.extendedAttributes = descriptor.getExtendedAttributes(idlNode, + getter=getter, + setter=setter) + self.argsPre = argsPre + self.arguments = arguments + self.argCount = len(arguments) + if self.argCount > argConversionStartsAt: + # Insert our argv in there + cgThings = [CGGeneric(self.getArgvDecl())] + else: + cgThings = [] + cgThings.extend([CGArgumentConverter(arguments[i], i, self.getArgv(), + self.getArgc(), self.descriptor, + invalidEnumValueFatal=not setter) for + i in range(argConversionStartsAt, self.argCount)]) + + cgThings.append(CGCallGenerator( + ' false as JSBool' if self.isFallible() else None, + self.getArguments(), self.argsPre, returnType, + self.extendedAttributes, descriptor, nativeMethodName, + static)) + self.cgRoot = CGList(cgThings, "\n") + + def getArgv(self): + return "argv" if self.argCount > 0 else "" + def getArgvDecl(self): + return "\nlet argv = JS_ARGV(cx, vp);\n" + def getArgc(self): + return "argc" + def getArguments(self): + def process(arg, i): + argVal = "arg" + str(i) + if arg.type.isGeckoInterface() and not arg.type.unroll().inner.isCallback(): + argVal += ".root_ref()" + return argVal + return [(a, process(a, i)) for (i, a) in enumerate(self.arguments)] + + def isFallible(self): + return not 'infallible' in self.extendedAttributes + + def wrap_return_value(self): + return wrapForType('*vp') + + def define(self): + return (self.cgRoot.define() + "\n" + self.wrap_return_value()) + +class CGSwitch(CGList): + """ + A class to generate code for a switch statement. + + Takes three constructor arguments: an expression, a list of cases, + and an optional default. + + Each case is a CGCase. The default is a CGThing for the body of + the default case, if any. + """ + def __init__(self, expression, cases, default=None): + CGList.__init__(self, [CGIndenter(c) for c in cases], "\n") + self.prepend(CGWrapper(CGGeneric(expression), + pre="match ", post=" {")); + if default is not None: + self.append( + CGIndenter( + CGWrapper( + CGIndenter(default), + pre="_ => {\n", + post="\n}" + ) + ) + ) + + self.append(CGGeneric("}")) + +class CGCase(CGList): + """ + A class to generate code for a case statement. + + Takes three constructor arguments: an expression, a CGThing for + the body (allowed to be None if there is no body), and an optional + argument (defaulting to False) for whether to fall through. + """ + def __init__(self, expression, body, fallThrough=False): + CGList.__init__(self, [], "\n") + self.append(CGWrapper(CGGeneric(expression), post=" => {")) + bodyList = CGList([body], "\n") + if fallThrough: + raise TypeError("fall through required but unsupported") + #bodyList.append(CGGeneric('fail!("fall through unsupported"); /* Fall through */')) + self.append(CGIndenter(bodyList)); + self.append(CGGeneric("}")) + +class CGGetterCall(CGPerSignatureCall): + """ + A class to generate a native object getter call for a particular IDL + getter. + """ + def __init__(self, argsPre, returnType, nativeMethodName, descriptor, attr): + CGPerSignatureCall.__init__(self, returnType, argsPre, [], + nativeMethodName, attr.isStatic(), descriptor, + attr, getter=True) + +class FakeArgument(): + """ + A class that quacks like an IDLArgument. This is used to make + setters look like method calls or for special operations. + """ + def __init__(self, type, interfaceMember, allowTreatNonObjectAsNull=False): + self.type = type + self.optional = False + self.variadic = False + self.defaultValue = None + self._allowTreatNonObjectAsNull = allowTreatNonObjectAsNull + self.treatNullAs = interfaceMember.treatNullAs + self.enforceRange = False + self.clamp = False + + def allowTreatNonCallableAsNull(self): + return self._allowTreatNonObjectAsNull + +class CGSetterCall(CGPerSignatureCall): + """ + A class to generate a native object setter call for a particular IDL + setter. + """ + def __init__(self, argsPre, argType, nativeMethodName, descriptor, attr): + CGPerSignatureCall.__init__(self, None, argsPre, + [FakeArgument(argType, attr, allowTreatNonObjectAsNull=True)], + nativeMethodName, attr.isStatic(), descriptor, attr, + setter=True) + def wrap_return_value(self): + # We have no return value + return "\nreturn 1;" + def getArgc(self): + return "1" + def getArgvDecl(self): + # We just get our stuff from our last arg no matter what + return "" + +class CGAbstractBindingMethod(CGAbstractExternMethod): + """ + Common class to generate the JSNatives for all our methods, getters, and + setters. This will generate the function declaration and unwrap the + |this| object. Subclasses are expected to override the generate_code + function to do the rest of the work. This function should return a + CGThing which is already properly indented. + """ + def __init__(self, descriptor, name, args, unwrapFailureCode=None): + CGAbstractExternMethod.__init__(self, descriptor, name, "JSBool", args) + + if unwrapFailureCode is None: + self.unwrapFailureCode = ( + 'throw_type_error(cx, "\\"this\\" object does not ' + 'implement interface %s.");\n' + 'return 0;' % descriptor.interface.identifier.name) + else: + self.unwrapFailureCode = unwrapFailureCode + + def definition_body(self): + # Our descriptor might claim that we're not castable, simply because + # we're someone's consequential interface. But for this-unwrapping, we + # know that we're the real deal. So fake a descriptor here for + # consumption by FailureFatalCastableObjectUnwrapper. + unwrapThis = str(CastableObjectUnwrapper( + FakeCastableDescriptor(self.descriptor), + "obj", self.unwrapFailureCode)) + unwrapThis = CGGeneric( + "let obj: *mut JSObject = JS_THIS_OBJECT(cx, vp as *mut JSVal);\n" + "if obj.is_null() {\n" + " return false as JSBool;\n" + "}\n" + "\n" + "let this: JS<%s> = %s;\n" % (self.descriptor.concreteType, unwrapThis)) + return CGList([ unwrapThis, self.generate_code() ], "\n") + + def generate_code(self): + assert(False) # Override me + + +class CGAbstractStaticBindingMethod(CGAbstractMethod): + """ + Common class to generate the JSNatives for all our static methods, getters + and setters. This will generate the function declaration and unwrap the + global object. Subclasses are expected to override the generate_code + function to do the rest of the work. This function should return a + CGThing which is already properly indented. + """ + def __init__(self, descriptor, name): + args = [ + Argument('*mut JSContext', 'cx'), + Argument('libc::c_uint', 'argc'), + Argument('*mut JSVal', 'vp'), + ] + CGAbstractMethod.__init__(self, descriptor, name, "JSBool", args, extern=True) + + def definition_body(self): + return self.generate_code() + + def generate_code(self): + assert False # Override me + + +class CGGenericMethod(CGAbstractBindingMethod): + """ + A class for generating the C++ code for an IDL method.. + """ + def __init__(self, descriptor): + args = [Argument('*mut JSContext', 'cx'), Argument('libc::c_uint', 'argc'), + Argument('*mut JSVal', 'vp')] + CGAbstractBindingMethod.__init__(self, descriptor, 'genericMethod', args) + + def generate_code(self): + return CGGeneric( + "let _info: *const JSJitInfo = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n" + "return CallJitMethodOp(_info, cx, obj, this.unsafe_get() as *mut libc::c_void, argc, vp);") + +class CGSpecializedMethod(CGAbstractExternMethod): + """ + A class for generating the C++ code for a specialized method that the JIT + can call with lower overhead. + """ + def __init__(self, descriptor, method): + self.method = method + name = method.identifier.name + args = [Argument('*mut JSContext', 'cx'), Argument('JSHandleObject', '_obj'), + Argument('*const %s' % descriptor.concreteType, 'this'), + Argument('libc::c_uint', 'argc'), Argument('*mut JSVal', 'vp')] + CGAbstractExternMethod.__init__(self, descriptor, name, 'JSBool', args) + + def definition_body(self): + nativeName = CGSpecializedMethod.makeNativeName(self.descriptor, + self.method) + return CGWrapper(CGMethodCall([], nativeName, self.method.isStatic(), + self.descriptor, self.method), + pre="let this = JS::from_raw(this);\n" + "let this = this.root();\n") + + @staticmethod + def makeNativeName(descriptor, method): + return MakeNativeName(method.identifier.name) + +class CGStaticMethod(CGAbstractStaticBindingMethod): + """ + A class for generating the Rust code for an IDL static method. + """ + def __init__(self, descriptor, method): + self.method = method + name = method.identifier.name + CGAbstractStaticBindingMethod.__init__(self, descriptor, name) + + def generate_code(self): + nativeName = CGSpecializedMethod.makeNativeName(self.descriptor, + self.method) + return CGMethodCall([], nativeName, True, self.descriptor, self.method) + + +class CGGenericGetter(CGAbstractBindingMethod): + """ + A class for generating the C++ code for an IDL attribute getter. + """ + def __init__(self, descriptor, lenientThis=False): + args = [Argument('*mut JSContext', 'cx'), Argument('libc::c_uint', 'argc'), + Argument('*mut JSVal', 'vp')] + if lenientThis: + name = "genericLenientGetter" + unwrapFailureCode = ( + "MOZ_ASSERT(!JS_IsExceptionPending(cx));\n" + "JS_SET_RVAL(cx, vp, JS::UndefinedValue());\n" + "return true;") + else: + name = "genericGetter" + unwrapFailureCode = None + CGAbstractBindingMethod.__init__(self, descriptor, name, args, + unwrapFailureCode) + + def generate_code(self): + return CGGeneric( + "let info: *const JSJitInfo = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n" + "return CallJitPropertyOp(info, cx, obj, this.unsafe_get() as *mut libc::c_void, vp);\n") + +class CGSpecializedGetter(CGAbstractExternMethod): + """ + A class for generating the code for a specialized attribute getter + that the JIT can call with lower overhead. + """ + def __init__(self, descriptor, attr): + self.attr = attr + name = 'get_' + attr.identifier.name + args = [ Argument('*mut JSContext', 'cx'), + Argument('JSHandleObject', '_obj'), + Argument('*const %s' % descriptor.concreteType, 'this'), + Argument('*mut JSVal', 'vp') ] + CGAbstractExternMethod.__init__(self, descriptor, name, "JSBool", args) + + def definition_body(self): + nativeName = CGSpecializedGetter.makeNativeName(self.descriptor, + self.attr) + + return CGWrapper(CGGetterCall([], self.attr.type, nativeName, + self.descriptor, self.attr), + pre="let this = JS::from_raw(this);\n" + "let this = this.root();\n") + + @staticmethod + def makeNativeName(descriptor, attr): + nativeName = MakeNativeName(attr.identifier.name) + infallible = ('infallible' in + descriptor.getExtendedAttributes(attr, getter=True)) + if attr.type.nullable() or not infallible: + return "Get" + nativeName + + return nativeName + + +class CGStaticGetter(CGAbstractStaticBindingMethod): + """ + A class for generating the C++ code for an IDL static attribute getter. + """ + def __init__(self, descriptor, attr): + self.attr = attr + name = 'get_' + attr.identifier.name + CGAbstractStaticBindingMethod.__init__(self, descriptor, name) + + def generate_code(self): + nativeName = CGSpecializedGetter.makeNativeName(self.descriptor, + self.attr) + return CGGetterCall([], self.attr.type, nativeName, self.descriptor, + self.attr) + + +class CGGenericSetter(CGAbstractBindingMethod): + """ + A class for generating the Rust code for an IDL attribute setter. + """ + def __init__(self, descriptor, lenientThis=False): + args = [Argument('*mut JSContext', 'cx'), Argument('libc::c_uint', 'argc'), + Argument('*mut JSVal', 'vp')] + if lenientThis: + name = "genericLenientSetter" + unwrapFailureCode = ( + "MOZ_ASSERT(!JS_IsExceptionPending(cx));\n" + "return true;") + else: + name = "genericSetter" + unwrapFailureCode = None + CGAbstractBindingMethod.__init__(self, descriptor, name, args, + unwrapFailureCode) + + def generate_code(self): + return CGGeneric( + "let mut undef = UndefinedValue();\n" + "let argv: *mut JSVal = if argc != 0 { JS_ARGV(cx, vp) } else { &mut undef as *mut JSVal };\n" + "let info: *const JSJitInfo = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n" + "if CallJitPropertyOp(info, cx, obj, this.unsafe_get() as *mut libc::c_void, argv) == 0 {\n" + " return 0;\n" + "}\n" + "*vp = UndefinedValue();\n" + "return 1;") + +class CGSpecializedSetter(CGAbstractExternMethod): + """ + A class for generating the code for a specialized attribute setter + that the JIT can call with lower overhead. + """ + def __init__(self, descriptor, attr): + self.attr = attr + name = 'set_' + attr.identifier.name + args = [ Argument('*mut JSContext', 'cx'), + Argument('JSHandleObject', '_obj'), + Argument('*const %s' % descriptor.concreteType, 'this'), + Argument('*mut JSVal', 'argv')] + CGAbstractExternMethod.__init__(self, descriptor, name, "JSBool", args) + + def definition_body(self): + nativeName = CGSpecializedSetter.makeNativeName(self.descriptor, + self.attr) + return CGWrapper(CGSetterCall([], self.attr.type, nativeName, + self.descriptor, self.attr), + pre="let this = JS::from_raw(this);\n" + "let this = this.root();\n") + + @staticmethod + def makeNativeName(descriptor, attr): + return "Set" + MakeNativeName(attr.identifier.name) + + +class CGStaticSetter(CGAbstractStaticBindingMethod): + """ + A class for generating the C++ code for an IDL static attribute setter. + """ + def __init__(self, descriptor, attr): + self.attr = attr + name = 'set_' + attr.identifier.name + CGAbstractStaticBindingMethod.__init__(self, descriptor, name) + + def generate_code(self): + nativeName = CGSpecializedSetter.makeNativeName(self.descriptor, + self.attr) + checkForArg = CGGeneric( + "let argv = JS_ARGV(cx, vp);\n" + "if (argc == 0) {\n" + " throw_type_error(cx, \"Not enough arguments to %s setter.\");\n" + " return 0;\n" + "}\n" % self.attr.identifier.name) + call = CGSetterCall([], self.attr.type, nativeName, self.descriptor, + self.attr) + return CGList([checkForArg, call]) + + +class CGMemberJITInfo(CGThing): + """ + A class for generating the JITInfo for a property that points to + our specialized getter and setter. + """ + def __init__(self, descriptor, member): + self.member = member + self.descriptor = descriptor + + def defineJitInfo(self, infoName, opName, infallible): + protoID = "PrototypeList::id::%s as u32" % self.descriptor.name + depth = self.descriptor.interface.inheritanceDepth() + failstr = "true" if infallible else "false" + return ("\n" + "static %s: JSJitInfo = JSJitInfo {\n" + " op: %s as *const u8,\n" + " protoID: %s,\n" + " depth: %s,\n" + " isInfallible: %s, /* False in setters. */\n" + " isConstant: false /* Only relevant for getters. */\n" + "};\n" % (infoName, opName, protoID, depth, failstr)) + + def define(self): + if self.member.isAttr(): + getterinfo = ("%s_getterinfo" % self.member.identifier.name) + getter = ("get_%s" % self.member.identifier.name) + getterinfal = "infallible" in self.descriptor.getExtendedAttributes(self.member, getter=True) + result = self.defineJitInfo(getterinfo, getter, getterinfal) + if not self.member.readonly: + setterinfo = ("%s_setterinfo" % self.member.identifier.name) + setter = ("set_%s" % self.member.identifier.name) + # Setters are always fallible, since they have to do a typed unwrap. + result += self.defineJitInfo(setterinfo, setter, False) + return result + if self.member.isMethod(): + methodinfo = ("%s_methodinfo" % self.member.identifier.name) + # Actually a JSJitMethodOp, but JSJitPropertyOp by struct definition. + method = ("%s" % self.member.identifier.name) + + # Methods are infallible if they are infallible, have no arguments + # to unwrap, and have a return type that's infallible to wrap up for + # return. + methodInfal = False + sigs = self.member.signatures() + if len(sigs) == 1: + # Don't handle overloading. If there's more than one signature, + # one of them must take arguments. + sig = sigs[0] + if len(sig[1]) == 0: + # No arguments and infallible return boxing + methodInfal = True + + result = self.defineJitInfo(methodinfo, method, methodInfal) + return result + raise TypeError("Illegal member type to CGPropertyJITInfo") + +def getEnumValueName(value): + # Some enum values can be empty strings. Others might have weird + # characters in them. Deal with the former by returning "_empty", + # deal with possible name collisions from that by throwing if the + # enum value is actually "_empty", and throw on any value + # containing non-ASCII chars for now. Replace all chars other than + # [0-9A-Za-z_] with '_'. + if re.match("[^\x20-\x7E]", value): + raise SyntaxError('Enum value "' + value + '" contains non-ASCII characters') + if re.match("^[0-9]", value): + raise SyntaxError('Enum value "' + value + '" starts with a digit') + value = re.sub(r'[^0-9A-Za-z_]', '_', value) + if re.match("^_[A-Z]|__", value): + raise SyntaxError('Enum value "' + value + '" is reserved by the C++ spec') + if value == "_empty": + raise SyntaxError('"_empty" is not an IDL enum value we support yet') + if value == "": + return "_empty" + return MakeNativeName(value) + +class CGEnum(CGThing): + def __init__(self, enum): + CGThing.__init__(self) + inner = """ +use dom::bindings::conversions::ToJSValConvertible; +use js::jsapi::JSContext; +use js::jsval::JSVal; + +#[repr(uint)] +#[deriving(Encodable, PartialEq)] +pub enum valuelist { + %s +} + +pub static strings: &'static [&'static str] = &[ + %s, +]; + +impl ToJSValConvertible for valuelist { + fn to_jsval(&self, cx: *mut JSContext) -> JSVal { + strings[*self as uint].to_string().to_jsval(cx) + } +} +""" % (",\n ".join(map(getEnumValueName, enum.values())), + ",\n ".join(['"%s"' % val for val in enum.values()])) + + self.cgRoot = CGList([ + CGNamespace.build([enum.identifier.name + "Values"], + CGIndenter(CGGeneric(inner)), public=True), + CGGeneric("pub type %s = self::%sValues::valuelist;\n" % + (enum.identifier.name, enum.identifier.name)), + ]) + + def define(self): + return self.cgRoot.define() + + +def convertConstIDLValueToRust(value): + tag = value.type.tag() + if tag in [IDLType.Tags.int8, IDLType.Tags.uint8, + IDLType.Tags.int16, IDLType.Tags.uint16, + IDLType.Tags.int32, IDLType.Tags.uint32, + IDLType.Tags.int64, IDLType.Tags.uint64, + IDLType.Tags.float, IDLType.Tags.double]: + return str(value.value) + + if tag == IDLType.Tags.bool: + return toStringBool(value.value) + + raise TypeError("Const value of unhandled type: " + value.type) + +class CGConstant(CGThing): + def __init__(self, constants): + CGThing.__init__(self) + self.constants = constants + + def define(self): + def stringDecl(const): + name = const.identifier.name + value = convertConstIDLValueToRust(const.value) + return CGGeneric("pub static %s: %s = %s;\n" % (name, builtinNames[const.value.type.tag()], value)) + + return CGIndenter(CGList(stringDecl(m) for m in self.constants)).define() + +def getUnionTypeTemplateVars(type, descriptorProvider): + # For dictionaries and sequences we need to pass None as the failureCode + # for getJSToNativeConversionTemplate. + # Also, for dictionaries we would need to handle conversion of + # null/undefined to the dictionary correctly. + if type.isDictionary() or type.isSequence(): + raise TypeError("Can't handle dictionaries or sequences in unions") + + if type.isGeckoInterface(): + name = type.inner.identifier.name + typeName = descriptorProvider.getDescriptor(name).nativeType + elif type.isEnum(): + name = type.inner.identifier.name + typeName = name + elif type.isArray() or type.isSequence(): + name = str(type) + #XXXjdm dunno about typeName here + typeName = "/*" + type.name + "*/" + elif type.isDOMString(): + name = type.name + typeName = "DOMString" + elif type.isPrimitive(): + name = type.name + typeName = builtinNames[type.tag()] + else: + name = type.name + typeName = "/*" + type.name + "*/" + + template, _, _, _ = getJSToNativeConversionTemplate( + type, descriptorProvider, failureCode="return Ok(None);", + exceptionCode='return Err(());', + isDefinitelyObject=True) + + assert not type.isObject() + jsConversion = string.Template(template).substitute({ + "val": "value", + }) + jsConversion = CGWrapper(CGGeneric(jsConversion), pre="Ok(Some(", post="))") + + return { + "name": name, + "typeName": typeName, + "jsConversion": jsConversion, + } + +class CGUnionStruct(CGThing): + def __init__(self, type, descriptorProvider): + assert not type.nullable() + assert not type.hasNullableType + + CGThing.__init__(self) + self.type = type + self.descriptorProvider = descriptorProvider + + def define(self): + templateVars = map(lambda t: getUnionTypeTemplateVars(t, self.descriptorProvider), + self.type.flatMemberTypes) + enumValues = [ + " e%s(%s)," % (v["name"], v["typeName"]) for v in templateVars + ] + enumConversions = [ + " e%s(ref inner) => inner.to_jsval(cx)," % v["name"] for v in templateVars + ] + return ("""pub enum %s { +%s +} + +impl ToJSValConvertible for %s { + fn to_jsval(&self, cx: *mut JSContext) -> JSVal { + match *self { +%s + } + } +} +""") % (self.type, "\n".join(enumValues), + self.type, "\n".join(enumConversions)) + + +class CGUnionConversionStruct(CGThing): + def __init__(self, type, descriptorProvider): + assert not type.nullable() + assert not type.hasNullableType + + CGThing.__init__(self) + self.type = type + self.descriptorProvider = descriptorProvider + + def from_jsval(self): + memberTypes = self.type.flatMemberTypes + names = [] + conversions = [] + + interfaceMemberTypes = filter(lambda t: t.isNonCallbackInterface(), memberTypes) + if len(interfaceMemberTypes) > 0: + def get_name(memberType): + if self.type.isGeckoInterface(): + return memberType.inner.identifier.name + + return memberType.name + + def get_match(name): + return ( + "match %s::TryConvertTo%s(cx, value) {\n" + " Err(_) => return Err(()),\n" + " Ok(Some(value)) => return Ok(e%s(value)),\n" + " Ok(None) => (),\n" + "}\n") % (self.type, name, name) + + typeNames = [get_name(memberType) for memberType in interfaceMemberTypes] + interfaceObject = CGList(CGGeneric(get_match(typeName)) for typeName in typeNames) + names.extend(typeNames) + else: + interfaceObject = None + + arrayObjectMemberTypes = filter(lambda t: t.isArray() or t.isSequence(), memberTypes) + if len(arrayObjectMemberTypes) > 0: + assert len(arrayObjectMemberTypes) == 1 + raise TypeError("Can't handle arrays or sequences in unions.") + else: + arrayObject = None + + dateObjectMemberTypes = filter(lambda t: t.isDate(), memberTypes) + if len(dateObjectMemberTypes) > 0: + assert len(dateObjectMemberTypes) == 1 + raise TypeError("Can't handle dates in unions.") + else: + dateObject = None + + callbackMemberTypes = filter(lambda t: t.isCallback() or t.isCallbackInterface(), memberTypes) + if len(callbackMemberTypes) > 0: + assert len(callbackMemberTypes) == 1 + raise TypeError("Can't handle callbacks in unions.") + else: + callbackObject = None + + dictionaryMemberTypes = filter(lambda t: t.isDictionary(), memberTypes) + if len(dictionaryMemberTypes) > 0: + raise TypeError("No support for unwrapping dictionaries as member " + "of a union") + else: + dictionaryObject = None + + if callbackObject or dictionaryObject: + assert False, "Not currently supported" + else: + nonPlatformObject = None + + objectMemberTypes = filter(lambda t: t.isObject(), memberTypes) + if len(objectMemberTypes) > 0: + raise TypeError("Can't handle objects in unions.") + else: + object = None + + hasObjectTypes = interfaceObject or arrayObject or dateObject or nonPlatformObject or object + if hasObjectTypes: + assert interfaceObject + templateBody = CGList([interfaceObject], "\n") + conversions.append(CGIfWrapper(templateBody, "value.is_object()")) + + otherMemberTypes = [ + t for t in memberTypes if t.isPrimitive() or t.isString() or t.isEnum() + ] + if len(otherMemberTypes) > 0: + assert len(otherMemberTypes) == 1 + memberType = otherMemberTypes[0] + if memberType.isEnum(): + name = memberType.inner.identifier.name + else: + name = memberType.name + match = ( + "match %s::TryConvertTo%s(cx, value) {\n" + " Err(_) => return Err(()),\n" + " Ok(Some(value)) => return Ok(e%s(value)),\n" + " Ok(None) => (),\n" + "}\n") % (self.type, name, name) + conversions.append(CGGeneric(match)) + names.append(name) + + conversions.append(CGGeneric( + "throw_not_in_union(cx, \"%s\");\n" + "Err(())" % ", ".join(names))) + method = CGWrapper( + CGIndenter(CGList(conversions, "\n\n")), + pre="fn from_jsval(cx: *mut JSContext, value: JSVal, _option: ()) -> Result<%s, ()> {\n" % self.type, + post="\n}") + return CGWrapper( + CGIndenter(method), + pre="impl FromJSValConvertible<()> for %s {\n" % self.type, + post="\n}") + + def try_method(self, t): + templateVars = getUnionTypeTemplateVars(t, self.descriptorProvider) + returnType = "Result<Option<%s>, ()>" % templateVars["typeName"] + jsConversion = templateVars["jsConversion"] + + return CGWrapper( + CGIndenter(jsConversion, 4), + pre="fn TryConvertTo%s(cx: *mut JSContext, value: JSVal) -> %s {\n" % (t.name, returnType), + post="\n}") + + def define(self): + from_jsval = self.from_jsval() + methods = CGIndenter(CGList([ + self.try_method(t) for t in self.type.flatMemberTypes + ], "\n\n")) + return """ +%s + +impl %s { +%s +} +""" % (from_jsval.define(), self.type, methods.define()) + + +class ClassItem: + """ Use with CGClass """ + def __init__(self, name, visibility): + self.name = name + self.visibility = visibility + def declare(self, cgClass): + assert False + def define(self, cgClass): + assert False + +class ClassBase(ClassItem): + def __init__(self, name, visibility='pub'): + ClassItem.__init__(self, name, visibility) + def declare(self, cgClass): + return '%s %s' % (self.visibility, self.name) + def define(self, cgClass): + # Only in the header + return '' + +class ClassMethod(ClassItem): + def __init__(self, name, returnType, args, inline=False, static=False, + virtual=False, const=False, bodyInHeader=False, + templateArgs=None, visibility='public', body=None, + breakAfterReturnDecl="\n", + breakAfterSelf="\n", override=False): + """ + override indicates whether to flag the method as MOZ_OVERRIDE + """ + assert not override or virtual + self.returnType = returnType + self.args = args + self.inline = False + self.static = static + self.virtual = virtual + self.const = const + self.bodyInHeader = True + self.templateArgs = templateArgs + self.body = body + self.breakAfterReturnDecl = breakAfterReturnDecl + self.breakAfterSelf = breakAfterSelf + self.override = override + ClassItem.__init__(self, name, visibility) + + def getDecorators(self, declaring): + decorators = [] + if self.inline: + decorators.append('inline') + if declaring: + if self.static: + decorators.append('static') + if self.virtual: + decorators.append('virtual') + if decorators: + return ' '.join(decorators) + ' ' + return '' + + def getBody(self): + # Override me or pass a string to constructor + assert self.body is not None + return self.body + + def declare(self, cgClass): + templateClause = '<%s>' % ', '.join(self.templateArgs) \ + if self.bodyInHeader and self.templateArgs else '' + args = ', '.join([a.declare() for a in self.args]) + if self.bodyInHeader: + body = CGIndenter(CGGeneric(self.getBody())).define() + body = ' {\n' + body + '\n}' + else: + body = ';' + + return string.Template("${decorators}%s" + "${visibility}fn ${name}${templateClause}(${args})${returnType}${const}${override}${body}%s" % + (self.breakAfterReturnDecl, self.breakAfterSelf) + ).substitute({ + 'templateClause': templateClause, + 'decorators': self.getDecorators(True), + 'returnType': (" -> %s" % self.returnType) if self.returnType else "", + 'name': self.name, + 'const': ' const' if self.const else '', + 'override': ' MOZ_OVERRIDE' if self.override else '', + 'args': args, + 'body': body, + 'visibility': self.visibility + ' ' if self.visibility is not 'priv' else '' + }) + + def define(self, cgClass): + pass + +class ClassUsingDeclaration(ClassItem): + """" + Used for importing a name from a base class into a CGClass + + baseClass is the name of the base class to import the name from + + name is the name to import + + visibility determines the visibility of the name (public, + protected, private), defaults to public. + """ + def __init__(self, baseClass, name, visibility='public'): + self.baseClass = baseClass + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return string.Template("""using ${baseClass}::${name}; +""").substitute({ 'baseClass': self.baseClass, + 'name': self.name }) + + def define(self, cgClass): + return '' + +class ClassConstructor(ClassItem): + """ + Used for adding a constructor to a CGClass. + + args is a list of Argument objects that are the arguments taken by the + constructor. + + inline should be True if the constructor should be marked inline. + + bodyInHeader should be True if the body should be placed in the class + declaration in the header. + + visibility determines the visibility of the constructor (public, + protected, private), defaults to private. + + explicit should be True if the constructor should be marked explicit. + + baseConstructors is a list of strings containing calls to base constructors, + defaults to None. + + body contains a string with the code for the constructor, defaults to empty. + """ + def __init__(self, args, inline=False, bodyInHeader=False, + visibility="priv", explicit=False, baseConstructors=None, + body=""): + self.args = args + self.inline = False + self.bodyInHeader = bodyInHeader + self.explicit = explicit + self.baseConstructors = baseConstructors or [] + self.body = body + ClassItem.__init__(self, None, visibility) + + def getDecorators(self, declaring): + decorators = [] + if self.explicit: + decorators.append('explicit') + if self.inline and declaring: + decorators.append('inline') + if decorators: + return ' '.join(decorators) + ' ' + return '' + + def getInitializationList(self, cgClass): + items = [str(c) for c in self.baseConstructors] + for m in cgClass.members: + if not m.static: + initialize = m.body + if initialize: + items.append(m.name + "(" + initialize + ")") + + if len(items) > 0: + return '\n : ' + ',\n '.join(items) + return '' + + def getBody(self, cgClass): + initializers = [" parent: %s" % str(self.baseConstructors[0])] + return (self.body + ( + "%s {\n" + "%s\n" + "}") % (cgClass.name, '\n'.join(initializers))) + + def declare(self, cgClass): + args = ', '.join([a.declare() for a in self.args]) + body = ' ' + self.getBody(cgClass); + body = stripTrailingWhitespace(body.replace('\n', '\n ')) + if len(body) > 0: + body += '\n' + body = ' {\n' + body + '}' + + return string.Template("""pub fn ${decorators}new(${args}) -> ${className}${body} +""").substitute({ 'decorators': self.getDecorators(True), + 'className': cgClass.getNameString(), + 'args': args, + 'body': body }) + + def define(self, cgClass): + if self.bodyInHeader: + return '' + + args = ', '.join([a.define() for a in self.args]) + + body = ' ' + self.getBody() + body = '\n' + stripTrailingWhitespace(body.replace('\n', '\n ')) + if len(body) > 0: + body += '\n' + + return string.Template("""${decorators} +${className}::${className}(${args})${initializationList} +{${body}} +""").substitute({ 'decorators': self.getDecorators(False), + 'className': cgClass.getNameString(), + 'args': args, + 'initializationList': self.getInitializationList(cgClass), + 'body': body }) + +class ClassDestructor(ClassItem): + """ + Used for adding a destructor to a CGClass. + + inline should be True if the destructor should be marked inline. + + bodyInHeader should be True if the body should be placed in the class + declaration in the header. + + visibility determines the visibility of the destructor (public, + protected, private), defaults to private. + + body contains a string with the code for the destructor, defaults to empty. + + virtual determines whether the destructor is virtual, defaults to False. + """ + def __init__(self, inline=False, bodyInHeader=False, + visibility="private", body='', virtual=False): + self.inline = inline or bodyInHeader + self.bodyInHeader = bodyInHeader + self.body = body + self.virtual = virtual + ClassItem.__init__(self, None, visibility) + + def getDecorators(self, declaring): + decorators = [] + if self.virtual and declaring: + decorators.append('virtual') + if self.inline and declaring: + decorators.append('inline') + if decorators: + return ' '.join(decorators) + ' ' + return '' + + def getBody(self): + return self.body + + def declare(self, cgClass): + if self.bodyInHeader: + body = ' ' + self.getBody(); + body = stripTrailingWhitespace(body.replace('\n', '\n ')) + if len(body) > 0: + body += '\n' + body = '\n{\n' + body + '}' + else: + body = ';' + + return string.Template("""${decorators}~${className}()${body} +""").substitute({ 'decorators': self.getDecorators(True), + 'className': cgClass.getNameString(), + 'body': body }) + + def define(self, cgClass): + if self.bodyInHeader: + return '' + + body = ' ' + self.getBody() + body = '\n' + stripTrailingWhitespace(body.replace('\n', '\n ')) + if len(body) > 0: + body += '\n' + + return string.Template("""${decorators} +${className}::~${className}() +{${body}} +""").substitute({ 'decorators': self.getDecorators(False), + 'className': cgClass.getNameString(), + 'body': body }) + +class ClassMember(ClassItem): + def __init__(self, name, type, visibility="priv", static=False, + body=None): + self.type = type; + self.static = static + self.body = body + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return '%s %s: %s,\n' % (self.visibility, self.name, self.type) + + def define(self, cgClass): + if not self.static: + return '' + if self.body: + body = " = " + self.body + else: + body = "" + return '%s %s::%s%s;\n' % (self.type, cgClass.getNameString(), + self.name, body) + +class ClassTypedef(ClassItem): + def __init__(self, name, type, visibility="public"): + self.type = type + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return 'typedef %s %s;\n' % (self.type, self.name) + + def define(self, cgClass): + # Only goes in the header + return '' + +class ClassEnum(ClassItem): + def __init__(self, name, entries, values=None, visibility="public"): + self.entries = entries + self.values = values + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + entries = [] + for i in range(0, len(self.entries)): + if not self.values or i >= len(self.values): + entry = '%s' % self.entries[i] + else: + entry = '%s = %s' % (self.entries[i], self.values[i]) + entries.append(entry) + name = '' if not self.name else ' ' + self.name + return 'enum%s\n{\n %s\n};\n' % (name, ',\n '.join(entries)) + + def define(self, cgClass): + # Only goes in the header + return '' + +class ClassUnion(ClassItem): + def __init__(self, name, entries, visibility="public"): + self.entries = [entry + ";" for entry in entries] + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return 'union %s\n{\n %s\n};\n' % (self.name, '\n '.join(self.entries)) + + def define(self, cgClass): + # Only goes in the header + return '' + +class CGClass(CGThing): + def __init__(self, name, bases=[], members=[], constructors=[], + destructor=None, methods=[], + typedefs = [], enums=[], unions=[], templateArgs=[], + templateSpecialization=[], isStruct=False, + disallowCopyConstruction=False, indent='', + decorators='', + extradeclarations='', + extradefinitions=''): + CGThing.__init__(self) + self.name = name + self.bases = bases + self.members = members + self.constructors = constructors + # We store our single destructor in a list, since all of our + # code wants lists of members. + self.destructors = [destructor] if destructor else [] + self.methods = methods + self.typedefs = typedefs + self.enums = enums + self.unions = unions + self.templateArgs = templateArgs + self.templateSpecialization = templateSpecialization + self.isStruct = isStruct + self.disallowCopyConstruction = disallowCopyConstruction + self.indent = indent + self.decorators = decorators + self.extradeclarations = extradeclarations + self.extradefinitions = extradefinitions + + def getNameString(self): + className = self.name + if self.templateSpecialization: + className = className + \ + '<%s>' % ', '.join([str(a) for a + in self.templateSpecialization]) + return className + + def define(self): + result = '' + if self.templateArgs: + templateArgs = [a.declare() for a in self.templateArgs] + templateArgs = templateArgs[len(self.templateSpecialization):] + result = result + self.indent + 'template <%s>\n' \ + % ','.join([str(a) for a in templateArgs]) + + if self.templateSpecialization: + specialization = \ + '<%s>' % ', '.join([str(a) for a in self.templateSpecialization]) + else: + specialization = '' + + myself = '' + if self.decorators != '': + myself += self.decorators + '\n' + myself += '%spub struct %s%s' % (self.indent, self.name, specialization) + result += myself + + assert len(self.bases) == 1 #XXjdm Can we support multiple inheritance? + + result += '{\n%s\n' % self.indent + + if self.bases: + self.members = [ClassMember("parent", self.bases[0].name, "pub")] + self.members + + result += CGIndenter(CGGeneric(self.extradeclarations), + len(self.indent)).define() + + def declareMembers(cgClass, memberList): + result = '' + + for member in memberList: + declaration = member.declare(cgClass) + declaration = CGIndenter(CGGeneric(declaration)).define() + result = result + declaration + return result + + if self.disallowCopyConstruction: + class DisallowedCopyConstructor(object): + def __init__(self): + self.visibility = "private" + def declare(self, cgClass): + name = cgClass.getNameString() + return ("%s(const %s&) MOZ_DELETE;\n" + "void operator=(const %s) MOZ_DELETE;\n" % (name, name, name)) + disallowedCopyConstructors = [DisallowedCopyConstructor()] + else: + disallowedCopyConstructors = [] + + order = [(self.enums, ''), (self.unions, ''), + (self.typedefs, ''), (self.members, '')] + + for (memberList, separator) in order: + memberString = declareMembers(self, memberList) + if self.indent: + memberString = CGIndenter(CGGeneric(memberString), + len(self.indent)).define() + result = result + memberString + + result += self.indent + '}\n\n' + result += 'impl %s {\n' % self.name + + order = [(self.constructors + disallowedCopyConstructors, '\n'), + (self.destructors, '\n'), (self.methods, '\n)')] + for (memberList, separator) in order: + memberString = declareMembers(self, memberList) + if self.indent: + memberString = CGIndenter(CGGeneric(memberString), + len(self.indent)).define() + result = result + memberString + + result += "}" + return result + +class CGProxySpecialOperation(CGPerSignatureCall): + """ + Base class for classes for calling an indexed or named special operation + (don't use this directly, use the derived classes below). + """ + def __init__(self, descriptor, operation): + nativeName = MakeNativeName(operation) + operation = descriptor.operations[operation] + assert len(operation.signatures()) == 1 + signature = operation.signatures()[0] + + (returnType, arguments) = signature + + # We pass len(arguments) as the final argument so that the + # CGPerSignatureCall won't do any argument conversion of its own. + CGPerSignatureCall.__init__(self, returnType, "", arguments, nativeName, + False, descriptor, operation, + len(arguments)) + + if operation.isSetter() or operation.isCreator(): + # arguments[0] is the index or name of the item that we're setting. + argument = arguments[1] + template, _, declType, needsRooting = getJSToNativeConversionTemplate( + argument.type, descriptor, treatNullAs=argument.treatNullAs) + templateValues = { + "val": "(*desc).value", + } + self.cgRoot.prepend(instantiateJSToNativeConversionTemplate( + template, templateValues, declType, argument.identifier.name, + needsRooting)) + elif operation.isGetter(): + self.cgRoot.prepend(CGGeneric("let mut found = false;")) + + def getArguments(self): + def process(arg): + argVal = arg.identifier.name + if arg.type.isGeckoInterface() and not arg.type.unroll().inner.isCallback(): + argVal += ".root_ref()" + return argVal + args = [(a, process(a)) for a in self.arguments] + if self.idlNode.isGetter(): + args.append((FakeArgument(BuiltinTypes[IDLBuiltinType.Types.boolean], + self.idlNode), + "&mut found")) + return args + + def wrap_return_value(self): + if not self.idlNode.isGetter() or self.templateValues is None: + return "" + + wrap = CGGeneric(wrapForType(**self.templateValues)) + wrap = CGIfWrapper(wrap, "found") + return "\n" + wrap.define() + +class CGProxyIndexedGetter(CGProxySpecialOperation): + """ + Class to generate a call to an indexed getter. If templateValues is not None + the returned value will be wrapped with wrapForType using templateValues. + """ + def __init__(self, descriptor, templateValues=None): + self.templateValues = templateValues + CGProxySpecialOperation.__init__(self, descriptor, 'IndexedGetter') + +class CGProxyIndexedSetter(CGProxySpecialOperation): + """ + Class to generate a call to an indexed setter. + """ + def __init__(self, descriptor): + CGProxySpecialOperation.__init__(self, descriptor, 'IndexedSetter') + +class CGProxyNamedGetter(CGProxySpecialOperation): + """ + Class to generate a call to an named getter. If templateValues is not None + the returned value will be wrapped with wrapForType using templateValues. + """ + def __init__(self, descriptor, templateValues=None): + self.templateValues = templateValues + CGProxySpecialOperation.__init__(self, descriptor, 'NamedGetter') + +class CGProxyNamedSetter(CGProxySpecialOperation): + """ + Class to generate a call to a named setter. + """ + def __init__(self, descriptor): + CGProxySpecialOperation.__init__(self, descriptor, 'NamedSetter') + +class CGProxyUnwrap(CGAbstractMethod): + def __init__(self, descriptor): + args = [Argument('*mut JSObject', 'obj')] + CGAbstractMethod.__init__(self, descriptor, "UnwrapProxy", '*const ' + descriptor.concreteType, args, alwaysInline=True) + + def definition_body(self): + return CGGeneric("""/*if (xpc::WrapperFactory::IsXrayWrapper(obj)) { + obj = js::UnwrapObject(obj); +}*/ +//MOZ_ASSERT(IsProxy(obj)); +let box_ = GetProxyPrivate(obj).to_private() as *const %s; +return box_;""" % self.descriptor.concreteType) + +class CGDOMJSProxyHandler_getOwnPropertyDescriptor(CGAbstractExternMethod): + def __init__(self, descriptor): + args = [Argument('*mut JSContext', 'cx'), Argument('*mut JSObject', 'proxy'), + Argument('jsid', 'id'), Argument('bool', 'set'), + Argument('*mut JSPropertyDescriptor', 'desc')] + CGAbstractExternMethod.__init__(self, descriptor, "getOwnPropertyDescriptor", + "bool", args) + self.descriptor = descriptor + def getBody(self): + indexedGetter = self.descriptor.operations['IndexedGetter'] + indexedSetter = self.descriptor.operations['IndexedSetter'] + + setOrIndexedGet = "" + if indexedGetter or indexedSetter: + setOrIndexedGet += "let index = GetArrayIndexFromId(cx, id);\n" + + if indexedGetter: + readonly = toStringBool(self.descriptor.operations['IndexedSetter'] is None) + fillDescriptor = "FillPropertyDescriptor(&mut *desc, proxy, %s);\nreturn true;" % readonly + templateValues = {'jsvalRef': '(*desc).value', 'successCode': fillDescriptor} + get = ("if index.is_some() {\n" + + " let index = index.unwrap();\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let this = this.root();\n" + + CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define() + "\n" + + "}\n") + + if indexedSetter or self.descriptor.operations['NamedSetter']: + setOrIndexedGet += "if set != 0 {\n" + if indexedSetter: + setOrIndexedGet += (" if index.is_some() {\n" + + " let index = index.unwrap();\n") + if not 'IndexedCreator' in self.descriptor.operations: + # FIXME need to check that this is a 'supported property index' + assert False + setOrIndexedGet += (" FillPropertyDescriptor(&mut *desc, proxy, false);\n" + + " return true;\n" + + " }\n") + if self.descriptor.operations['NamedSetter']: + setOrIndexedGet += " if RUST_JSID_IS_STRING(id) {\n" + if not 'NamedCreator' in self.descriptor.operations: + # FIXME need to check that this is a 'supported property name' + assert False + setOrIndexedGet += (" FillPropertyDescriptor(&mut *desc, proxy, false);\n" + + " return true;\n" + + " }\n") + setOrIndexedGet += "}" + if indexedGetter: + setOrIndexedGet += (" else {\n" + + CGIndenter(CGGeneric(get)).define() + + "}") + setOrIndexedGet += "\n\n" + elif indexedGetter: + setOrIndexedGet += ("if !set {\n" + + CGIndenter(CGGeneric(get)).define() + + "}\n\n") + + namedGetter = self.descriptor.operations['NamedGetter'] + if namedGetter: + readonly = toStringBool(self.descriptor.operations['NamedSetter'] is None) + fillDescriptor = "FillPropertyDescriptor(&mut *desc, proxy, %s);\nreturn true;" % readonly + templateValues = {'jsvalRef': '(*desc).value', 'successCode': fillDescriptor} + # Once we start supporting OverrideBuiltins we need to make + # ResolveOwnProperty or EnumerateOwnProperties filter out named + # properties that shadow prototype properties. + namedGet = ("\n" + + "if !set && RUST_JSID_IS_STRING(id) != 0 && !HasPropertyOnPrototype(cx, proxy, id) {\n" + + " let name = jsid_to_str(cx, id);\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let this = this.root();\n" + + CGIndenter(CGProxyNamedGetter(self.descriptor, templateValues)).define() + "\n" + + "}\n") + else: + namedGet = "" + + return setOrIndexedGet + """let expando: *mut JSObject = GetExpandoObject(proxy); +//if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = GetExpandoObject(proxy))) { +if expando.is_not_null() { + let flags = if set { JSRESOLVE_ASSIGNING } else { 0 } | JSRESOLVE_QUALIFIED; + if JS_GetPropertyDescriptorById(cx, expando, id, flags, desc) == 0 { + return false; + } + if (*desc).obj.is_not_null() { + // Pretend the property lives on the wrapper. + (*desc).obj = proxy; + return true; + } +} +""" + namedGet + """ +(*desc).obj = ptr::mut_null(); +return true;""" + + def definition_body(self): + return CGGeneric(self.getBody()) + +class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod): + def __init__(self, descriptor): + args = [Argument('*mut JSContext', 'cx'), Argument('*mut JSObject', 'proxy'), + Argument('jsid', 'id'), + Argument('*const JSPropertyDescriptor', 'desc')] + CGAbstractExternMethod.__init__(self, descriptor, "defineProperty", "bool", args) + self.descriptor = descriptor + def getBody(self): + set = "" + + indexedSetter = self.descriptor.operations['IndexedSetter'] + if indexedSetter: + if not (self.descriptor.operations['IndexedCreator'] is indexedSetter): + raise TypeError("Can't handle creator that's different from the setter") + set += ("let index = GetArrayIndexFromId(cx, id);\n" + + "if index.is_some() {\n" + + " let index = index.unwrap();\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let this = this.root();\n" + + CGIndenter(CGProxyIndexedSetter(self.descriptor)).define() + + " return true;\n" + + "}\n") + elif self.descriptor.operations['IndexedGetter']: + set += ("if GetArrayIndexFromId(cx, id).is_some() {\n" + + " return false;\n" + + " //return ThrowErrorMessage(cx, MSG_NO_PROPERTY_SETTER, \"%s\");\n" + + "}\n") % self.descriptor.name + + namedSetter = self.descriptor.operations['NamedSetter'] + if namedSetter: + if not self.descriptor.operations['NamedCreator'] is namedSetter: + raise TypeError("Can't handle creator that's different from the setter") + set += ("if RUST_JSID_IS_STRING(id) != 0 {\n" + + " let name = jsid_to_str(cx, id);\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let this = this.root();\n" + + CGIndenter(CGProxyNamedSetter(self.descriptor)).define() + "\n" + + "}\n") + elif self.descriptor.operations['NamedGetter']: + set += ("if RUST_JSID_IS_STRING(id) {\n" + + " let name = jsid_to_str(cx, id);\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let this = this.root();\n" + + CGIndenter(CGProxyNamedGetter(self.descriptor)).define() + + " if (found) {\n" + " return false;\n" + + " //return ThrowErrorMessage(cx, MSG_NO_PROPERTY_SETTER, \"%s\");\n" + + " }\n" + + " return true;\n" + "}\n") % (self.descriptor.name) + return set + """return proxyhandler::defineProperty_(%s);""" % ", ".join(a.name for a in self.args) + + def definition_body(self): + return CGGeneric(self.getBody()) + +class CGDOMJSProxyHandler_hasOwn(CGAbstractExternMethod): + def __init__(self, descriptor): + args = [Argument('*mut JSContext', 'cx'), Argument('*mut JSObject', 'proxy'), + Argument('jsid', 'id'), Argument('*mut bool', 'bp')] + CGAbstractExternMethod.__init__(self, descriptor, "hasOwn", "bool", args) + self.descriptor = descriptor + def getBody(self): + indexedGetter = self.descriptor.operations['IndexedGetter'] + if indexedGetter: + indexed = ("let index = GetArrayIndexFromId(cx, id);\n" + + "if index.is_some() {\n" + + " let index = index.unwrap();\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let this = this.root();\n" + + CGIndenter(CGProxyIndexedGetter(self.descriptor)).define() + "\n" + + " *bp = found;\n" + + " return true;\n" + + "}\n\n") + else: + indexed = "" + + namedGetter = self.descriptor.operations['NamedGetter'] + if namedGetter: + named = ("if RUST_JSID_IS_STRING(id) != 0 && !HasPropertyOnPrototype(cx, proxy, id) {\n" + + " let name = jsid_to_str(cx, id);\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let this = this.root();\n" + + CGIndenter(CGProxyNamedGetter(self.descriptor)).define() + "\n" + + " *bp = found;\n" + " return true;\n" + "}\n" + + "\n") + else: + named = "" + + return indexed + """let expando: *mut JSObject = GetExpandoObject(proxy); +if expando.is_not_null() { + let mut b: JSBool = 1; + let ok = JS_HasPropertyById(cx, expando, id, &mut b) != 0; + *bp = b != 0; + if !ok || *bp { + return ok; + } +} + +""" + named + """*bp = false; +return true;""" + + def definition_body(self): + return CGGeneric(self.getBody()) + +class CGDOMJSProxyHandler_get(CGAbstractExternMethod): + def __init__(self, descriptor): + args = [Argument('*mut JSContext', 'cx'), Argument('*mut JSObject', 'proxy'), + Argument('*mut JSObject', 'receiver'), Argument('jsid', 'id'), + Argument('*mut JSVal', 'vp')] + CGAbstractExternMethod.__init__(self, descriptor, "get", "bool", args) + self.descriptor = descriptor + def getBody(self): + getFromExpando = """let expando = GetExpandoObject(proxy); +if expando.is_not_null() { + let mut hasProp = 0; + if JS_HasPropertyById(cx, expando, id, &mut hasProp) == 0 { + return false; + } + + if hasProp != 0 { + return JS_GetPropertyById(cx, expando, id, vp) != 0; + } +}""" + + templateValues = { + 'jsvalRef': '*vp', + 'successCode': 'return true;', + } + + indexedGetter = self.descriptor.operations['IndexedGetter'] + if indexedGetter: + getIndexedOrExpando = ("let index = GetArrayIndexFromId(cx, id);\n" + + "if index.is_some() {\n" + + " let index = index.unwrap();\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let this = this.root();\n" + + CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define()) + getIndexedOrExpando += """ + // Even if we don't have this index, we don't forward the + // get on to our expando object. +} else { + %s +} +""" % (stripTrailingWhitespace(getFromExpando.replace('\n', '\n '))) + else: + getIndexedOrExpando = getFromExpando + "\n" + + namedGetter = self.descriptor.operations['NamedGetter'] + if namedGetter and False: #XXXjdm unfinished + getNamed = ("if (JSID_IS_STRING(id)) {\n" + + " let name = jsid_to_str(cx, id);\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let this = this.root();\n" + + CGIndenter(CGProxyNamedGetter(self.descriptor, templateValues)).define() + + "}\n") % (self.descriptor.concreteType) + else: + getNamed = "" + + return """//MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), + //"Should not have a XrayWrapper here"); + +%s +let mut found = false; +if !GetPropertyOnPrototype(cx, proxy, id, &mut found, vp) { + return false; +} + +if found { + return true; +} +%s +*vp = UndefinedValue(); +return true;""" % (getIndexedOrExpando, getNamed) + + def definition_body(self): + return CGGeneric(self.getBody()) + +class CGDOMJSProxyHandler_obj_toString(CGAbstractExternMethod): + def __init__(self, descriptor): + args = [Argument('*mut JSContext', 'cx'), Argument('*mut JSObject', 'proxy')] + CGAbstractExternMethod.__init__(self, descriptor, "obj_toString", "*mut JSString", args) + self.descriptor = descriptor + def getBody(self): + stringifier = self.descriptor.operations['Stringifier'] + if stringifier: + nativeName = MakeNativeName(stringifier.identifier.name) + signature = stringifier.signatures()[0] + returnType = signature[0] + extendedAttributes = self.descriptor.getExtendedAttributes(stringifier) + infallible = 'infallible' in extendedAttributes + if not infallible: + error = CGGeneric( + ('ThrowMethodFailedWithDetails(cx, rv, "%s", "toString");\n' + + "return NULL;") % self.descriptor.interface.identifier.name) + else: + error = None + call = CGCallGenerator(error, [], "", returnType, extendedAttributes, self.descriptor, nativeName, False, object="UnwrapProxy(proxy)") + return call.define() + """ + +JSString* jsresult; +return xpc_qsStringToJsstring(cx, result, &jsresult) ? jsresult : NULL;""" + + return """let s = "%s".to_c_str(); + _obj_toString(cx, s.as_ptr())""" % self.descriptor.name + + def definition_body(self): + return CGGeneric(self.getBody()) + +class CGAbstractClassHook(CGAbstractExternMethod): + """ + Meant for implementing JSClass hooks, like Finalize or Trace. Does very raw + 'this' unwrapping as it assumes that the unwrapped type is always known. + """ + def __init__(self, descriptor, name, returnType, args): + CGAbstractExternMethod.__init__(self, descriptor, name, returnType, + args) + + def definition_body_prologue(self): + return CGGeneric("""\ +let this: *const %s = unwrap::<%s>(obj); +""" % (self.descriptor.concreteType, self.descriptor.concreteType)) + + def definition_body(self): + return CGList([ + self.definition_body_prologue(), + self.generate_code(), + ]) + + def generate_code(self): + # Override me + assert(False) + +def finalizeHook(descriptor, hookName, context): + release = """let val = JS_GetReservedSlot(obj, dom_object_slot(obj)); +let _: Box<%s> = mem::transmute(val.to_private()); +debug!("%s finalize: {:p}", this); +""" % (descriptor.concreteType, descriptor.concreteType) + return release + +class CGClassTraceHook(CGAbstractClassHook): + """ + A hook to trace through our native object; used for GC and CC + """ + def __init__(self, descriptor): + args = [Argument('*mut JSTracer', 'trc'), Argument('*mut JSObject', 'obj')] + CGAbstractClassHook.__init__(self, descriptor, TRACE_HOOK_NAME, 'void', + args) + + def generate_code(self): + return CGGeneric("(*this).trace(%s);" % self.args[0].name) + +class CGClassConstructHook(CGAbstractExternMethod): + """ + JS-visible constructor for our objects + """ + def __init__(self, descriptor): + args = [Argument('*mut JSContext', 'cx'), Argument('u32', 'argc'), Argument('*mut JSVal', 'vp')] + CGAbstractExternMethod.__init__(self, descriptor, CONSTRUCT_HOOK_NAME, + 'JSBool', args) + self._ctor = self.descriptor.interface.ctor() + + def define(self): + if not self._ctor: + return "" + return CGAbstractExternMethod.define(self) + + def definition_body(self): + preamble = CGGeneric("""\ +let global = global_object_for_js_object(JS_CALLEE(cx, vp).to_object()); +let global = global.root(); +""") + nativeName = MakeNativeName(self._ctor.identifier.name) + callGenerator = CGMethodCall(["&global.root_ref()"], nativeName, True, + self.descriptor, self._ctor) + return CGList([preamble, callGenerator]) + +class CGClassFinalizeHook(CGAbstractClassHook): + """ + A hook for finalize, used to release our native object. + """ + def __init__(self, descriptor): + args = [Argument('*mut JSFreeOp', 'fop'), Argument('*mut JSObject', 'obj')] + CGAbstractClassHook.__init__(self, descriptor, FINALIZE_HOOK_NAME, + 'void', args) + + def generate_code(self): + return CGGeneric(finalizeHook(self.descriptor, self.name, self.args[0].name)) + +class CGDOMJSProxyHandlerDOMClass(CGThing): + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + + def define(self): + return """ +static Class: DOMClass = """ + DOMClass(self.descriptor) + """; + +""" + + +class CGInterfaceTrait(CGThing): + def __init__(self, descriptor): + CGThing.__init__(self) + + def argument_type(ty, optional=False, defaultValue=None, variadic=False): + _, _, declType, _ = getJSToNativeConversionTemplate( + ty, descriptor, isArgument=True) + + if variadic: + declType = CGWrapper(declType, pre="Vec<", post=">") + elif optional and not defaultValue: + declType = CGWrapper(declType, pre="Option<", post=">") + + if ty.isGeckoInterface() and not (ty.nullable() or optional): + declType = CGWrapper(declType, pre="&") + elif ty.isDictionary(): + declType = CGWrapper(declType, pre="&") + + return declType.define() + + def attribute_arguments(needCx, argument=None): + if needCx: + yield "cx", "*mut JSContext" + + if argument: + yield "value", argument_type(argument) + + def method_arguments(returnType, arguments, trailing=None): + if needCx(returnType, arguments, True): + yield "cx", "*mut JSContext" + + for argument in arguments: + ty = argument_type(argument.type, argument.optional, + argument.defaultValue, argument.variadic) + yield CGDictionary.makeMemberName(argument.identifier.name), ty + + if trailing: + yield trailing + + def return_type(rettype, infallible): + result = getRetvalDeclarationForType(rettype, descriptor) + if not infallible: + result = CGWrapper(result, pre="Fallible<", post=">") + return result.define() + + def members(): + for m in descriptor.interface.members: + if m.isMethod() and not m.isStatic(): + name = CGSpecializedMethod.makeNativeName(descriptor, m) + infallible = 'infallible' in descriptor.getExtendedAttributes(m) + for idx, (rettype, arguments) in enumerate(m.signatures()): + arguments = method_arguments(rettype, arguments) + rettype = return_type(rettype, infallible) + yield name + ('_' * idx), arguments, rettype + elif m.isAttr() and not m.isStatic(): + name = CGSpecializedGetter.makeNativeName(descriptor, m) + infallible = 'infallible' in descriptor.getExtendedAttributes(m, getter=True) + needCx = typeNeedsCx(m.type) + yield name, attribute_arguments(needCx), return_type(m.type, infallible) + + if not m.readonly: + name = CGSpecializedSetter.makeNativeName(descriptor, m) + infallible = 'infallible' in descriptor.getExtendedAttributes(m, setter=True) + if infallible: + rettype = "()" + else: + rettype = "ErrorResult" + yield name, attribute_arguments(needCx, m.type), rettype + + if descriptor.proxy: + for name, operation in descriptor.operations.iteritems(): + if not operation: + continue + + assert len(operation.signatures()) == 1 + rettype, arguments = operation.signatures()[0] + + infallible = 'infallible' in descriptor.getExtendedAttributes(operation) + arguments = method_arguments(rettype, arguments, ("found", "&mut bool")) + rettype = return_type(rettype, infallible) + yield name, arguments, rettype + + def fmt(arguments): + return "".join(", %s: %s" % argument for argument in arguments) + + methods = CGList([ + CGGeneric("fn %s(&self%s) -> %s;\n" % (name, fmt(arguments), rettype)) + for name, arguments, rettype in members() + ], "") + self.cgRoot = CGWrapper(CGIndenter(methods), + pre="pub trait %sMethods {\n" % descriptor.interface.identifier.name, + post="}") + + def define(self): + return self.cgRoot.define() + + +class CGDescriptor(CGThing): + def __init__(self, descriptor): + CGThing.__init__(self) + + assert not descriptor.interface.isCallback() + + cgThings = [] + cgThings.append(CGGetProtoObjectMethod(descriptor)) + if descriptor.interface.hasInterfaceObject(): + # https://github.com/mozilla/servo/issues/2665 + # cgThings.append(CGGetConstructorObjectMethod(descriptor)) + pass + + (hasMethod, hasGetter, hasLenientGetter, + hasSetter, hasLenientSetter) = False, False, False, False, False + for m in descriptor.interface.members: + if m.isMethod() and not m.isIdentifierLess(): + if m.isStatic(): + assert descriptor.interface.hasInterfaceObject() + cgThings.append(CGStaticMethod(descriptor, m)) + else: + cgThings.append(CGSpecializedMethod(descriptor, m)) + cgThings.append(CGMemberJITInfo(descriptor, m)) + hasMethod = True + elif m.isAttr(): + if m.isStatic(): + assert descriptor.interface.hasInterfaceObject() + cgThings.append(CGStaticGetter(descriptor, m)) + else: + cgThings.append(CGSpecializedGetter(descriptor, m)) + if m.hasLenientThis(): + hasLenientGetter = True + else: + hasGetter = True + + if not m.readonly: + if m.isStatic(): + assert descriptor.interface.hasInterfaceObject() + cgThings.append(CGStaticSetter(descriptor, m)) + else: + cgThings.append(CGSpecializedSetter(descriptor, m)) + if m.hasLenientThis(): + hasLenientSetter = True + else: + hasSetter = True + + if not m.isStatic(): + cgThings.append(CGMemberJITInfo(descriptor, m)) + if hasMethod: + cgThings.append(CGGenericMethod(descriptor)) + if hasGetter: + cgThings.append(CGGenericGetter(descriptor)) + if hasLenientGetter: + pass + if hasSetter: + cgThings.append(CGGenericSetter(descriptor)) + if hasLenientSetter: + pass + + if descriptor.concrete: + cgThings.append(CGClassFinalizeHook(descriptor)) + cgThings.append(CGClassTraceHook(descriptor)) + + if descriptor.interface.hasInterfaceObject(): + cgThings.append(CGClassConstructHook(descriptor)) + cgThings.append(CGInterfaceObjectJSClass(descriptor)) + + cgThings.append(CGPrototypeJSClass(descriptor)) + + properties = PropertyArrays(descriptor) + cgThings.append(CGGeneric(str(properties))) + cgThings.append(CGNativeProperties(descriptor, properties)) + cgThings.append(CGNativePropertyHooks(descriptor, properties)) + cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties)) + + cgThings.append(CGNamespace.build([descriptor.name + "Constants"], + CGConstant(m for m in descriptor.interface.members if m.isConst()), + public=True)) + + if descriptor.interface.hasInterfaceObject(): + cgThings.append(CGDefineDOMInterfaceMethod(descriptor)) + + if descriptor.proxy: + cgThings.append(CGDefineProxyHandler(descriptor)) + + if descriptor.concrete: + if descriptor.proxy: + #cgThings.append(CGProxyIsProxy(descriptor)) + cgThings.append(CGProxyUnwrap(descriptor)) + cgThings.append(CGDOMJSProxyHandlerDOMClass(descriptor)) + cgThings.append(CGDOMJSProxyHandler_getOwnPropertyDescriptor(descriptor)) + cgThings.append(CGDOMJSProxyHandler_obj_toString(descriptor)) + cgThings.append(CGDOMJSProxyHandler_get(descriptor)) + cgThings.append(CGDOMJSProxyHandler_hasOwn(descriptor)) + if descriptor.operations['IndexedSetter'] or descriptor.operations['NamedSetter']: + cgThings.append(CGDOMJSProxyHandler_defineProperty(descriptor)) + + #cgThings.append(CGDOMJSProxyHandler(descriptor)) + #cgThings.append(CGIsMethod(descriptor)) + pass + else: + cgThings.append(CGDOMJSClass(descriptor)) + pass + + cgThings.append(CGWrapMethod(descriptor)) + + cgThings.append(CGIDLInterface(descriptor)) + cgThings.append(CGInterfaceTrait(descriptor)) + + cgThings = CGList(cgThings, "\n") + cgThings = CGWrapper(cgThings, pre='\n', post='\n') + #self.cgRoot = CGWrapper(CGNamespace(toBindingNamespace(descriptor.name), + # cgThings), + # post='\n') + self.cgRoot = cgThings + + def define(self): + return self.cgRoot.define() + +class CGNamespacedEnum(CGThing): + def __init__(self, namespace, enumName, names, values, comment="", deriving=""): + + if not values: + values = [] + + # Account for explicit enum values. + entries = [] + for i in range(0, len(names)): + if len(values) > i and values[i] is not None: + entry = "%s = %s" % (names[i], values[i]) + else: + entry = names[i] + entries.append(entry) + + # Append a Count. + entries.append(enumName + 'Count = ' + str(len(entries))) + + # Indent. + entries = [' ' + e for e in entries] + + # Build the enum body. + enumstr = comment + 'pub enum %s {\n%s\n}\n' % (enumName, ',\n'.join(entries)) + if deriving: + enumstr = ('#[deriving(%s)]\n' % deriving) + enumstr + curr = CGGeneric(enumstr) + + # Add some whitespace padding. + curr = CGWrapper(curr, pre='\n',post='\n') + + # Add the namespace. + curr = CGNamespace(namespace, curr, public=True) + + # Add the typedef + #typedef = '\ntypedef %s::%s %s;\n\n' % (namespace, enumName, enumName) + #curr = CGList([curr, CGGeneric(typedef)]) + + # Save the result. + self.node = curr + + def define(self): + return self.node.define() + +class CGDictionary(CGThing): + def __init__(self, dictionary, descriptorProvider): + self.dictionary = dictionary; + if all(CGDictionary(d, descriptorProvider).generatable for + d in CGDictionary.getDictionaryDependencies(dictionary)): + self.generatable = True + else: + self.generatable = False + # Nothing else to do here + return + self.memberInfo = [ + (member, + getJSToNativeConversionTemplate(member.type, + descriptorProvider, + isMember="Dictionary", + defaultValue=member.defaultValue, + failureCode="return Err(());", + exceptionCode="return Err(());")) + for member in dictionary.members ] + + def define(self): + if not self.generatable: + return "" + return self.struct() + "\n" + self.impl() + + def struct(self): + d = self.dictionary + if d.parent: + inheritance = " pub parent: %s::%s<'a, 'b>,\n" % (self.makeModuleName(d.parent), + self.makeClassName(d.parent)) + else: + inheritance = "" + memberDecls = [" pub %s: %s," % + (self.makeMemberName(m[0].identifier.name), self.getMemberType(m)) + for m in self.memberInfo] + + return (string.Template( + "pub struct ${selfName}<'a, 'b> {\n" + + "${inheritance}" + + "\n".join(memberDecls) + "\n" + + "}").substitute( { "selfName": self.makeClassName(d), + "inheritance": inheritance })) + + def impl(self): + d = self.dictionary + if d.parent: + initParent = ("parent: match %s::%s::new(cx, val) {\n" + " Ok(parent) => parent,\n" + " Err(_) => return Err(()),\n" + "},\n") % (self.makeModuleName(d.parent), + self.makeClassName(d.parent)) + else: + initParent = "" + + def memberInit(memberInfo): + member, _ = memberInfo + name = self.makeMemberName(member.identifier.name) + conversion = self.getMemberConversion(memberInfo) + return CGGeneric("%s: %s,\n" % (name, conversion.define())) + + memberInits = CGList([memberInit(m) for m in self.memberInfo]) + + return string.Template( + "impl<'a, 'b> ${selfName}<'a, 'b> {\n" + " pub fn empty() -> ${selfName}<'a, 'b> {\n" + " ${selfName}::new(ptr::mut_null(), NullValue()).unwrap()\n" + " }\n" + " pub fn new(cx: *mut JSContext, val: JSVal) -> Result<${selfName}<'a, 'b>, ()> {\n" + " let object = if val.is_null_or_undefined() {\n" + " ptr::mut_null()\n" + " } else if val.is_object() {\n" + " val.to_object()\n" + " } else {\n" + " throw_type_error(cx, \"Value not an object.\");\n" + " return Err(());\n" + " };\n" + " Ok(${selfName} {\n" + "${initParent}" + "${initMembers}" + " })\n" + " }\n" + "}").substitute({ + "selfName": self.makeClassName(d), + "initParent": CGIndenter(CGGeneric(initParent), indentLevel=6).define(), + "initMembers": CGIndenter(memberInits, indentLevel=6).define(), + }) + + @staticmethod + def makeDictionaryName(dictionary): + return dictionary.identifier.name + + def makeClassName(self, dictionary): + return self.makeDictionaryName(dictionary) + + @staticmethod + def makeModuleName(dictionary): + name = dictionary.identifier.name + if name.endswith('Init'): + return toBindingNamespace(name.replace('Init', '')) + #XXXjdm This breaks on the test webidl files, sigh. + #raise TypeError("No idea how to find this dictionary's definition: " + name) + return "/* uh oh */ %s" % name + + def getMemberType(self, memberInfo): + member, (_, _, declType, _) = memberInfo + if not member.defaultValue: + declType = CGWrapper(declType, pre="Option<", post=">") + return declType.define() + + def getMemberConversion(self, memberInfo): + def indent(s): + return CGIndenter(CGGeneric(s), 8).define() + + member, (templateBody, default, declType, _) = memberInfo + replacements = { "val": "value" } + conversion = string.Template(templateBody).substitute(replacements) + + assert (member.defaultValue is None) == (default is None) + if not default: + default = "None" + conversion = "Some(%s)" % conversion + + conversion = ( + "match get_dictionary_property(cx, object, \"%s\") {\n" + " Err(()) => return Err(()),\n" + " Ok(Some(value)) => {\n" + "%s\n" + " },\n" + " Ok(None) => {\n" + "%s\n" + " },\n" + "}") % (member.identifier.name, indent(conversion), indent(default)) + + return CGGeneric(conversion) + + @staticmethod + def makeIdName(name): + return name + "_id" + + @staticmethod + def makeMemberName(name): + # Can't use Rust keywords as member names. + if name == "type": + return name + "_" + return name + + @staticmethod + def getDictionaryDependencies(dictionary): + deps = set(); + if dictionary.parent: + deps.add(dictionary.parent) + for member in dictionary.members: + if member.type.isDictionary(): + deps.add(member.type.unroll().inner) + return deps + +class CGRegisterProtos(CGAbstractMethod): + def __init__(self, config): + arguments = [ + Argument('*mut JSContext', 'cx'), + Argument('*mut JSObject', 'global'), + ] + CGAbstractMethod.__init__(self, None, 'Register', 'void', arguments, + unsafe=False, pub=True) + self.config = config + + def definition_body(self): + return CGList([ + CGGeneric("codegen::Bindings::%sBinding::DefineDOMInterface(cx, global);" % desc.name) + for desc in self.config.getDescriptors(hasInterfaceObject=True, register=True) + ], "\n") + + +class CGRegisterProxyHandlersMethod(CGAbstractMethod): + def __init__(self, descriptors): + CGAbstractMethod.__init__(self, None, 'RegisterProxyHandlers', 'void', [], + unsafe=True, pub=True) + self.descriptors = descriptors + + def definition_body(self): + return CGList([ + CGGeneric("proxy_handlers[proxies::%s as uint] = codegen::Bindings::%sBinding::DefineProxyHandler();" % (desc.name, desc.name)) + for desc in self.descriptors + ], "\n") + + +class CGRegisterProxyHandlers(CGThing): + def __init__(self, config): + descriptors = config.getDescriptors(proxy=True) + length = len(descriptors) + self.root = CGList([ + CGGeneric("pub static mut proxy_handlers: [*const libc::c_void, ..%d] = [0 as *const libc::c_void, ..%d];" % (length, length)), + CGRegisterProxyHandlersMethod(descriptors), + ], "\n") + + def define(self): + return self.root.define() + + +class CGBindingRoot(CGThing): + """ + Root codegen class for binding generation. Instantiate the class, and call + declare or define to generate header or cpp code (respectively). + """ + def __init__(self, config, prefix, webIDLFile): + descriptors = config.getDescriptors(webIDLFile=webIDLFile, + isCallback=False) + dictionaries = config.getDictionaries(webIDLFile=webIDLFile) + + cgthings = [] + + mainCallbacks = config.getCallbacks(webIDLFile=webIDLFile) + callbackDescriptors = config.getDescriptors(webIDLFile=webIDLFile, + isCallback=True) + + # Do codegen for all the enums + cgthings = [CGEnum(e) for e in config.getEnums(webIDLFile)] + + cgthings.extend([CGDictionary(d, config.getDescriptorProvider()) + for d in dictionaries]) + + # Do codegen for all the callbacks. + cgthings.extend(CGList([CGCallbackFunction(c, config.getDescriptorProvider()), + CGCallbackFunctionImpl(c)], "\n") + for c in mainCallbacks) + + # Do codegen for all the descriptors + cgthings.extend([CGDescriptor(x) for x in descriptors]) + + # Do codegen for all the callback interfaces. + cgthings.extend(CGList([CGCallbackInterface(x), + CGCallbackFunctionImpl(x)], "\n") + for x in callbackDescriptors) + + # And make sure we have the right number of newlines at the end + curr = CGWrapper(CGList(cgthings, "\n\n"), post="\n\n") + + # Wrap all of that in our namespaces. + #curr = CGNamespace.build(['dom'], + # CGWrapper(curr, pre="\n")) + + # Add imports + #XXXjdm This should only import the namespace for the current binding, + # not every binding ever. + curr = CGImports(curr, descriptors, [ + 'js', + 'js::{JS_ARGV, JS_CALLEE, JS_THIS_OBJECT}', + 'js::{JSCLASS_GLOBAL_SLOT_COUNT, JSCLASS_IS_DOMJSCLASS}', + 'js::{JSCLASS_IS_GLOBAL, JSCLASS_RESERVED_SLOTS_SHIFT}', + 'js::{JSCLASS_RESERVED_SLOTS_MASK, JSID_VOID, JSJitInfo}', + 'js::{JSPROP_ENUMERATE, JSPROP_NATIVE_ACCESSORS, JSPROP_SHARED}', + 'js::{JSRESOLVE_ASSIGNING, JSRESOLVE_QUALIFIED}', + 'js::jsapi::{JS_CallFunctionValue, JS_GetClass, JS_GetGlobalForObject}', + 'js::jsapi::{JS_GetObjectPrototype, JS_GetProperty, JS_GetPropertyById}', + 'js::jsapi::{JS_GetPropertyDescriptorById, JS_GetReservedSlot}', + 'js::jsapi::{JS_HasProperty, JS_HasPropertyById, JS_IsExceptionPending}', + 'js::jsapi::{JS_NewObject, JS_ObjectIsCallable, JS_SetPrototype}', + 'js::jsapi::{JS_SetReservedSlot, JS_WrapValue, JSBool, JSContext}', + 'js::jsapi::{JSClass, JSFreeOp, JSFunctionSpec, JSHandleObject, jsid}', + 'js::jsapi::{JSNativeWrapper, JSObject, JSPropertyDescriptor, JS_ArrayIterator}', + 'js::jsapi::{JSPropertyOpWrapper, JSPropertySpec, JS_PropertyStub}', + 'js::jsapi::{JSStrictPropertyOpWrapper, JSString, JSTracer, JS_ConvertStub}', + 'js::jsapi::{JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub}', + 'js::jsval::JSVal', + 'js::jsval::{ObjectValue, ObjectOrNullValue, PrivateValue}', + 'js::jsval::{NullValue, UndefinedValue}', + 'js::glue::{CallJitMethodOp, CallJitPropertyOp, CreateProxyHandler}', + 'js::glue::{GetProxyPrivate, NewProxyObject, ProxyTraps}', + 'js::glue::{RUST_FUNCTION_VALUE_TO_JITINFO}', + 'js::glue::{RUST_JS_NumberValue, RUST_JSID_IS_STRING}', + 'js::rust::with_compartment', + 'dom::types::*', + 'dom::bindings', + 'dom::bindings::global::GlobalRef', + 'dom::bindings::js::{JS, JSRef, Root, RootedReference, Temporary}', + 'dom::bindings::js::{OptionalRootable, OptionalRootedRootable, ResultRootable}', + 'dom::bindings::js::{OptionalRootedReference, OptionalOptionalRootedRootable}', + 'dom::bindings::utils::{CreateDOMGlobal, CreateInterfaceObjects2}', + 'dom::bindings::utils::{ConstantSpec, cx_for_dom_object}', + 'dom::bindings::utils::{dom_object_slot, DOM_OBJECT_SLOT, DOMClass}', + 'dom::bindings::utils::{DOMJSClass, JSCLASS_DOM_GLOBAL}', + 'dom::bindings::utils::{FindEnumStringIndex, GetArrayIndexFromId}', + 'dom::bindings::utils::{GetPropertyOnPrototype, GetProtoOrIfaceArray}', + 'dom::bindings::utils::{HasPropertyOnPrototype, IntVal}', + 'dom::bindings::utils::{jsid_to_str}', + 'dom::bindings::utils::global_object_for_js_object', + 'dom::bindings::utils::{Reflectable}', + 'dom::bindings::utils::{squirrel_away_unique}', + 'dom::bindings::utils::{ThrowingConstructor, unwrap, unwrap_jsmanaged}', + 'dom::bindings::utils::VoidVal', + 'dom::bindings::utils::get_dictionary_property', + 'dom::bindings::utils::{NativeProperties, NativePropertyHooks}', + 'dom::bindings::trace::JSTraceable', + 'dom::bindings::callback::{CallbackContainer,CallbackInterface,CallbackFunction}', + 'dom::bindings::callback::{CallSetup,ExceptionHandling}', + 'dom::bindings::callback::{WrapCallThisObject}', + 'dom::bindings::conversions::{FromJSValConvertible, ToJSValConvertible}', + 'dom::bindings::conversions::IDLInterface', + 'dom::bindings::conversions::{Default, Empty}', + 'dom::bindings::codegen::*', + 'dom::bindings::codegen::Bindings::*', + 'dom::bindings::codegen::RegisterBindings', + 'dom::bindings::codegen::UnionTypes::*', + 'dom::bindings::error::{FailureUnknown, Fallible, Error, ErrorResult}', + 'dom::bindings::error::throw_dom_exception', + 'dom::bindings::error::throw_type_error', + 'dom::bindings::proxyhandler', + 'dom::bindings::proxyhandler::{_obj_toString, defineProperty}', + 'dom::bindings::proxyhandler::{FillPropertyDescriptor, GetExpandoObject}', + 'dom::bindings::proxyhandler::{delete_, getPropertyDescriptor}', + 'dom::bindings::str::ByteString', + 'page::JSPageInfo', + 'libc', + 'servo_util::str::DOMString', + 'std::mem', + 'std::cmp', + 'std::ptr', + 'std::str', + 'std::num', + ]) + + # Add the auto-generated comment. + curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) + + # Store the final result. + self.root = curr + + def define(self): + return stripTrailingWhitespace(self.root.define()) + +class CGNativeMember(ClassMethod): + def __init__(self, descriptorProvider, member, name, signature, extendedAttrs, + breakAfter=True, passJSBitsAsNeeded=True, visibility="public", + jsObjectsArePtr=False, variadicIsSequence=False): + """ + If jsObjectsArePtr is true, typed arrays and "object" will be + passed as JSObject*. + + If passJSBitsAsNeeded is false, we don't automatically pass in a + JSContext* or a JSObject* based on the return and argument types. + """ + self.descriptorProvider = descriptorProvider + self.member = member + self.extendedAttrs = extendedAttrs + self.passJSBitsAsNeeded = passJSBitsAsNeeded + self.jsObjectsArePtr = jsObjectsArePtr + self.variadicIsSequence = variadicIsSequence + breakAfterSelf = "\n" if breakAfter else "" + ClassMethod.__init__(self, name, + self.getReturnType(signature[0], False), + self.getArgs(signature[0], signature[1]), + static=member.isStatic(), + # Mark our getters, which are attrs that + # have a non-void return type, as const. + const=(not member.isStatic() and member.isAttr() and + not signature[0].isVoid()), + breakAfterReturnDecl=" ", + breakAfterSelf=breakAfterSelf, + visibility=visibility) + + def getReturnType(self, type, isMember): + return self.getRetvalInfo(type, isMember)[0] + + def getRetvalInfo(self, type, isMember): + """ + Returns a tuple: + + The first element is the type declaration for the retval + + The second element is a template for actually returning a value stored in + "${declName}". This means actually returning it if + we're not outparam, else assigning to the "retval" outparam. If + isMember is true, this can be None, since in that case the caller will + never examine this value. + """ + if type.isVoid(): + typeDecl, template = "", "" + elif type.isPrimitive() and type.tag() in builtinNames: + result = CGGeneric(builtinNames[type.tag()]) + if type.nullable(): + raise TypeError("Nullable primitives are not supported here.") + + typeDecl, template = result.define(), "return Ok(${declName});" + elif type.isDOMString(): + if isMember: + # No need for a third element in the isMember case + typeDecl, template = "nsString", None + # Outparam + else: + typeDecl, template = "void", "retval = ${declName};" + elif type.isByteString(): + if isMember: + # No need for a third element in the isMember case + typeDecl, template = "nsCString", None + # Outparam + typeDecl, template = "void", "retval = ${declName};" + elif type.isEnum(): + enumName = type.unroll().inner.identifier.name + if type.nullable(): + enumName = CGTemplatedType("Nullable", + CGGeneric(enumName)).define() + typeDecl, template = enumName, "return ${declName};" + elif type.isGeckoInterface(): + iface = type.unroll().inner; + nativeType = self.descriptorProvider.getDescriptor( + iface.identifier.name).nativeType + # Now trim off unnecessary namespaces + nativeType = nativeType.split("::") + if nativeType[0] == "mozilla": + nativeType.pop(0) + if nativeType[0] == "dom": + nativeType.pop(0) + result = CGWrapper(CGGeneric("::".join(nativeType)), post="*") + # Since we always force an owning type for callback return values, + # our ${declName} is an OwningNonNull or nsRefPtr. So we can just + # .forget() to get our already_AddRefed. + typeDecl, template = result.define(), "return ${declName}.forget();" + elif type.isCallback(): + typeDecl, template = \ + ("already_AddRefed<%s>" % type.unroll().identifier.name, + "return ${declName}.forget();") + elif type.isAny(): + typeDecl, template = "JSVal", "return Ok(${declName});" + elif type.isObject(): + typeDecl, template = "JSObject*", "return ${declName};" + elif type.isSpiderMonkeyInterface(): + if type.nullable(): + returnCode = "return ${declName}.IsNull() ? nullptr : ${declName}.Value().Obj();" + else: + returnCode = "return ${declName}.Obj();" + typeDecl, template = "JSObject*", returnCode + elif type.isSequence(): + # If we want to handle sequence-of-sequences return values, we're + # going to need to fix example codegen to not produce nsTArray<void> + # for the relevant argument... + assert not isMember + # Outparam. + if type.nullable(): + returnCode = ("if (${declName}.IsNull()) {\n" + " retval.SetNull();\n" + "} else {\n" + " retval.SetValue().SwapElements(${declName}.Value());\n" + "}") + else: + returnCode = "retval.SwapElements(${declName});" + typeDecl, template = "void", returnCode + elif type.isDate(): + result = CGGeneric("Date") + if type.nullable(): + result = CGTemplatedType("Nullable", result) + typeDecl, template = result.define(), "return ${declName};" + else: + raise TypeError("Don't know how to declare return value for %s" % type) + + if not 'infallible' in self.extendedAttrs: + if typeDecl: + typeDecl = "Fallible<%s>" % typeDecl + else: + typeDecl = "ErrorResult" + if not template: + template = "return Ok(());" + return typeDecl, template + + def getArgs(self, returnType, argList): + args = [self.getArg(arg) for arg in argList] + # Now the outparams + if returnType.isDOMString(): + args.append(Argument("nsString&", "retval")) + if returnType.isByteString(): + args.append(Argument("nsCString&", "retval")) + elif returnType.isSequence(): + nullable = returnType.nullable() + if nullable: + returnType = returnType.inner + # And now the actual underlying type + elementDecl = self.getReturnType(returnType.inner, True) + type = CGTemplatedType("nsTArray", CGGeneric(elementDecl)) + if nullable: + type = CGTemplatedType("Nullable", type) + args.append(Argument("%s&" % type.define(), "retval")) + # The legacycaller thisval + if self.member.isMethod() and self.member.isLegacycaller(): + # If it has an identifier, we can't deal with it yet + assert self.member.isIdentifierLess() + args.insert(0, Argument("JS::Value", "aThisVal")) + # And jscontext bits. + if needCx(returnType, argList, self.passJSBitsAsNeeded): + args.insert(0, Argument("JSContext*", "cx")) + # And if we're static, a global + if self.member.isStatic(): + args.insert(0, Argument("const GlobalObject&", "global")) + return args + + def doGetArgType(self, type, optional, isMember): + """ + The main work of getArgType. Returns a string type decl, whether this + is a const ref, as well as whether the type should be wrapped in + Nullable as needed. + + isMember can be false or one of the strings "Sequence" or "Variadic" + """ + if type.isArray(): + raise TypeError("Can't handle array arguments yet") + + if type.isSequence(): + nullable = type.nullable() + if nullable: + type = type.inner + elementType = type.inner + argType = self.getArgType(elementType, False, "Sequence")[0] + decl = CGTemplatedType("Sequence", argType) + return decl.define(), True, True + + if type.isUnion(): + if type.nullable(): + type = type.inner + return str(type) + "::" + str(type), False, True + + if type.isGeckoInterface() and not type.isCallbackInterface(): + iface = type.unroll().inner + argIsPointer = type.nullable() + forceOwningType = iface.isCallback() or isMember + if argIsPointer: + if (optional or isMember) and forceOwningType: + typeDecl = "nsRefPtr<%s>" + else: + typeDecl = "*%s" + else: + if optional or isMember: + if forceOwningType: + typeDecl = "OwningNonNull<%s>" + else: + typeDecl = "NonNull<%s>" + else: + typeDecl = "%s" + descriptor = self.descriptorProvider.getDescriptor(iface.identifier.name) + return (typeDecl % descriptor.argumentType, + False, False) + + if type.isSpiderMonkeyInterface(): + if self.jsObjectsArePtr: + return "JSObject*", False, False + + return type.name, True, True + + if type.isDOMString(): + declType = "DOMString" + return declType, True, False + + if type.isByteString(): + declType = "nsCString" + return declType, True, False + + if type.isEnum(): + return type.unroll().inner.identifier.name, False, True + + if type.isCallback() or type.isCallbackInterface(): + forceOwningType = optional or isMember + if type.nullable(): + if forceOwningType: + declType = "nsRefPtr<%s>" + else: + declType = "%s*" + else: + if forceOwningType: + declType = "OwningNonNull<%s>" + else: + declType = "%s&" + if type.isCallback(): + name = type.unroll().identifier.name + else: + name = type.unroll().inner.identifier.name + return declType % name, False, False + + if type.isAny(): + # Don't do the rooting stuff for variadics for now + if isMember: + declType = "JS::Value" + else: + declType = "JSVal" + return declType, False, False + + if type.isObject(): + if isMember: + declType = "JSObject*" + else: + declType = "JS::Handle<JSObject*>" + return declType, False, False + + if type.isDictionary(): + typeName = CGDictionary.makeDictionaryName(type.inner) + return typeName, True, True + + if type.isDate(): + return "Date", False, True + + assert type.isPrimitive() + + return builtinNames[type.tag()], False, True + + def getArgType(self, type, optional, isMember): + """ + Get the type of an argument declaration. Returns the type CGThing, and + whether this should be a const ref. + + isMember can be False, "Sequence", or "Variadic" + """ + (decl, ref, handleNullable) = self.doGetArgType(type, optional, + isMember) + decl = CGGeneric(decl) + if handleNullable and type.nullable(): + decl = CGTemplatedType("Nullable", decl) + ref = True + if isMember == "Variadic": + arrayType = "Sequence" if self.variadicIsSequence else "nsTArray" + decl = CGTemplatedType(arrayType, decl) + ref = True + elif optional: + # Note: All variadic args claim to be optional, but we can just use + # empty arrays to represent them not being present. + decl = CGTemplatedType("Option", decl) + ref = False + return (decl, ref) + + def getArg(self, arg): + """ + Get the full argument declaration for an argument + """ + (decl, ref) = self.getArgType(arg.type, + arg.optional and not arg.defaultValue, + "Variadic" if arg.variadic else False) + if ref: + decl = CGWrapper(decl, pre="&") + + return Argument(decl.define(), arg.identifier.name) + +class CGCallback(CGClass): + def __init__(self, idlObject, descriptorProvider, baseName, methods, + getters=[], setters=[]): + self.baseName = baseName + self._deps = idlObject.getDeps() + name = idlObject.identifier.name + # For our public methods that needThisHandling we want most of the + # same args and the same return type as what CallbackMember + # generates. So we want to take advantage of all its + # CGNativeMember infrastructure, but that infrastructure can't deal + # with templates and most especially template arguments. So just + # cheat and have CallbackMember compute all those things for us. + realMethods = [] + for method in methods: + if not method.needThisHandling: + realMethods.append(method) + else: + realMethods.extend(self.getMethodImpls(method)) + CGClass.__init__(self, name, + bases=[ClassBase(baseName)], + constructors=self.getConstructors(), + methods=realMethods+getters+setters, + decorators="#[deriving(PartialEq,Clone,Encodable)]") + + def getConstructors(self): + return [ClassConstructor( + [Argument("*mut JSObject", "aCallback")], + bodyInHeader=True, + visibility="pub", + explicit=False, + baseConstructors=[ + "%s::new(aCallback)" % self.baseName + ])] + + def getMethodImpls(self, method): + assert method.needThisHandling + args = list(method.args) + # Strip out the JSContext*/JSObject* args + # that got added. + assert args[0].name == "cx" and args[0].argType == "*mut JSContext" + assert args[1].name == "aThisObj" and args[1].argType == "*mut JSObject" + args = args[2:] + # Record the names of all the arguments, so we can use them when we call + # the private method. + argnames = [arg.name for arg in args] + argnamesWithThis = ["s.GetContext()", "thisObjJS"] + argnames + argnamesWithoutThis = ["s.GetContext()", "ptr::mut_null()"] + argnames + # Now that we've recorded the argnames for our call to our private + # method, insert our optional argument for deciding whether the + # CallSetup should re-throw exceptions on aRv. + args.append(Argument("ExceptionHandling", "aExceptionHandling", + "ReportExceptions")) + + args[0] = Argument('&' + args[0].argType, args[0].name, args[0].default) + method.args[2] = args[0] + + # And now insert our template argument. + argsWithoutThis = list(args) + args.insert(0, Argument("&JSRef<T>", "thisObj")) + + # And the self argument + method.args.insert(0, Argument(None, "&self")) + args.insert(0, Argument(None, "&self")) + argsWithoutThis.insert(0, Argument(None, "&self")) + + setupCall = ("let s = CallSetup::new(self, aExceptionHandling);\n" + "if s.GetContext().is_null() {\n" + " return Err(FailureUnknown);\n" + "}\n") + + bodyWithThis = string.Template( + setupCall+ + "let thisObjJS = WrapCallThisObject(s.GetContext(), thisObj);\n" + "if thisObjJS.is_null() {\n" + " return Err(FailureUnknown);\n" + "}\n" + "return ${methodName}(${callArgs});").substitute({ + "callArgs" : ", ".join(argnamesWithThis), + "methodName": 'self.' + method.name, + }) + bodyWithoutThis = string.Template( + setupCall + + "return ${methodName}(${callArgs});").substitute({ + "callArgs" : ", ".join(argnamesWithoutThis), + "methodName": 'self.' + method.name, + }) + return [ClassMethod(method.name+'_', method.returnType, args, + bodyInHeader=True, + templateArgs=["T: Reflectable"], + body=bodyWithThis, + visibility='pub'), + ClassMethod(method.name+'__', method.returnType, argsWithoutThis, + bodyInHeader=True, + body=bodyWithoutThis, + visibility='pub'), + method] + + def deps(self): + return self._deps + +# We're always fallible +def callbackGetterName(attr): + return "Get" + MakeNativeName(attr.identifier.name) + +def callbackSetterName(attr): + return "Set" + MakeNativeName(attr.identifier.name) + +class CGCallbackFunction(CGCallback): + def __init__(self, callback, descriptorProvider): + CGCallback.__init__(self, callback, descriptorProvider, + "CallbackFunction", + methods=[CallCallback(callback, descriptorProvider)]) + + def getConstructors(self): + return CGCallback.getConstructors(self) + +class CGCallbackFunctionImpl(CGGeneric): + def __init__(self, callback): + impl = string.Template("""impl CallbackContainer for ${type} { + fn new(callback: *mut JSObject) -> ${type} { + ${type}::new(callback) + } + + fn callback(&self) -> *mut JSObject { + self.parent.callback() + } +} + +impl ToJSValConvertible for ${type} { + fn to_jsval(&self, cx: *mut JSContext) -> JSVal { + self.callback().to_jsval(cx) + } +} +""").substitute({"type": callback.name}) + CGGeneric.__init__(self, impl) + +class CGCallbackInterface(CGCallback): + def __init__(self, descriptor): + iface = descriptor.interface + attrs = [m for m in iface.members if m.isAttr() and not m.isStatic()] + getters = [CallbackGetter(a, descriptor) for a in attrs] + setters = [CallbackSetter(a, descriptor) for a in attrs + if not a.readonly] + methods = [m for m in iface.members + if m.isMethod() and not m.isStatic() and not m.isIdentifierLess()] + methods = [CallbackOperation(m, sig, descriptor) for m in methods + for sig in m.signatures()] + assert not iface.isJSImplemented() or not iface.ctor() + CGCallback.__init__(self, iface, descriptor, "CallbackInterface", + methods, getters=getters, setters=setters) + +class FakeMember(): + def __init__(self): + self.treatNullAs = "Default" + def isStatic(self): + return False + def isAttr(self): + return False + def isMethod(self): + return False + def getExtendedAttribute(self, name): + return None + +class CallbackMember(CGNativeMember): + def __init__(self, sig, name, descriptorProvider, needThisHandling, rethrowContentException=False): + """ + needThisHandling is True if we need to be able to accept a specified + thisObj, False otherwise. + """ + assert not rethrowContentException or not needThisHandling + + self.retvalType = sig[0] + self.originalSig = sig + args = sig[1] + self.argCount = len(args) + if self.argCount > 0: + # Check for variadic arguments + lastArg = args[self.argCount-1] + if lastArg.variadic: + self.argCountStr = ( + "(%d - 1) + %s.Length()" % (self.argCount, + lastArg.identifier.name)) + else: + self.argCountStr = "%d" % self.argCount + self.needThisHandling = needThisHandling + # If needThisHandling, we generate ourselves as private and the caller + # will handle generating public versions that handle the "this" stuff. + visibility = "priv" if needThisHandling else "pub" + self.rethrowContentException = rethrowContentException + # We don't care, for callback codegen, whether our original member was + # a method or attribute or whatnot. Just always pass FakeMember() + # here. + CGNativeMember.__init__(self, descriptorProvider, FakeMember(), + name, (self.retvalType, args), + extendedAttrs={}, + passJSBitsAsNeeded=False, + visibility=visibility, + jsObjectsArePtr=True) + # We have to do all the generation of our body now, because + # the caller relies on us throwing if we can't manage it. + self.exceptionCode= "return Err(FailureUnknown);\n" + self.body = self.getImpl() + + def getImpl(self): + replacements = { + "declRval": self.getRvalDecl(), + "returnResult": self.getResultConversion(), + "convertArgs": self.getArgConversions(), + "doCall": self.getCall(), + "setupCall": self.getCallSetup(), + } + if self.argCount > 0: + replacements["argCount"] = self.argCountStr + replacements["argvDecl"] = string.Template( + "let mut argv = Vec::from_elem(${argCount}, UndefinedValue());\n" + ).substitute(replacements) + else: + # Avoid weird 0-sized arrays + replacements["argvDecl"] = "" + + # Newlines and semicolons are in the values + pre = string.Template( + "${setupCall}" + "${declRval}" + "${argvDecl}").substitute(replacements) + body = string.Template( + "${convertArgs}" + "${doCall}" + "${returnResult}").substitute(replacements) + return CGList([ + CGGeneric(pre), + CGWrapper(CGIndenter(CGGeneric(body)), + pre="with_compartment(cx, self.parent.callback(), || {\n", + post="})") + ], "\n").define() + + def getResultConversion(self): + replacements = { + "val": "rval", + "declName": "rvalDecl", + } + + template, _, declType, needsRooting = getJSToNativeConversionTemplate( + self.retvalType, + self.descriptorProvider, + exceptionCode=self.exceptionCode, + isCallbackReturnValue="Callback", + # XXXbz we should try to do better here + sourceDescription="return value") + + convertType = instantiateJSToNativeConversionTemplate( + template, replacements, declType, "rvalDecl", needsRooting) + + assignRetval = string.Template( + self.getRetvalInfo(self.retvalType, + False)[1]).substitute(replacements) + return convertType.define() + "\n" + assignRetval + "\n" + + def getArgConversions(self): + # Just reget the arglist from self.originalSig, because our superclasses + # just have way to many members they like to clobber, so I can't find a + # safe member name to store it in. + argConversions = [self.getArgConversion(i, arg) for (i, arg) + in enumerate(self.originalSig[1])] + # Do them back to front, so our argc modifications will work + # correctly, because we examine trailing arguments first. + argConversions.reverse(); + # Wrap each one in a scope so that any locals it has don't leak out, and + # also so that we can just "break;" for our successCode. + argConversions = [CGWrapper(CGIndenter(CGGeneric(c)), + pre="loop {\n", + post="\nbreak;}\n") + for c in argConversions] + if self.argCount > 0: + argConversions.insert(0, self.getArgcDecl()) + # And slap them together. + return CGList(argConversions, "\n\n").define() + "\n\n" + + def getArgConversion(self, i, arg): + argval = arg.identifier.name + + if arg.variadic: + argval = argval + "[idx]" + jsvalIndex = "%d + idx" % i + else: + jsvalIndex = "%d" % i + if arg.optional and not arg.defaultValue: + argval += ".clone().unwrap()" + + conversion = wrapForType("*argv.get_mut(%s)" % jsvalIndex, + result=argval, + successCode="continue;" if arg.variadic else "break;") + if arg.variadic: + conversion = string.Template( + "for (uint32_t idx = 0; idx < ${arg}.Length(); ++idx) {\n" + + CGIndenter(CGGeneric(conversion)).define() + "\n" + "}\n" + "break;").substitute({ "arg": arg.identifier.name }) + elif arg.optional and not arg.defaultValue: + conversion = ( + CGIfWrapper(CGGeneric(conversion), + "%s.is_some()" % arg.identifier.name).define() + + " else if (argc == %d) {\n" + " // This is our current trailing argument; reduce argc\n" + " argc -= 1;\n" + "} else {\n" + " *argv.get_mut(%d) = UndefinedValue();\n" + "}" % (i+1, i)) + return conversion + + def getArgs(self, returnType, argList): + args = CGNativeMember.getArgs(self, returnType, argList) + if not self.needThisHandling: + # Since we don't need this handling, we're the actual method that + # will be called, so we need an aRethrowExceptions argument. + if self.rethrowContentException: + args.append(Argument("JSCompartment*", "aCompartment", "nullptr")) + else: + args.append(Argument("ExceptionHandling", "aExceptionHandling", + "ReportExceptions")) + return args + # We want to allow the caller to pass in a "this" object, as + # well as a JSContext. + return [Argument("*mut JSContext", "cx"), + Argument("*mut JSObject", "aThisObj")] + args + + def getCallSetup(self): + if self.needThisHandling: + # It's been done for us already + return "" + callSetup = "CallSetup s(CallbackPreserveColor(), aRv" + if self.rethrowContentException: + # getArgs doesn't add the aExceptionHandling argument but does add + # aCompartment for us. + callSetup += ", RethrowContentExceptions, aCompartment" + else: + callSetup += ", aExceptionHandling" + callSetup += ");" + return string.Template( + "${callSetup}\n" + "JSContext* cx = s.GetContext();\n" + "if (!cx) {\n" + " return Err(FailureUnknown);\n" + "}\n").substitute({ + "callSetup": callSetup, + }) + + def getArgcDecl(self): + return CGGeneric("let mut argc = %su32;" % self.argCountStr); + + @staticmethod + def ensureASCIIName(idlObject): + type = "attribute" if idlObject.isAttr() else "operation" + if re.match("[^\x20-\x7E]", idlObject.identifier.name): + raise SyntaxError('Callback %s name "%s" contains non-ASCII ' + "characters. We can't handle that. %s" % + (type, idlObject.identifier.name, + idlObject.location)) + if re.match('"', idlObject.identifier.name): + raise SyntaxError("Callback %s name '%s' contains " + "double-quote character. We can't handle " + "that. %s" % + (type, idlObject.identifier.name, + idlObject.location)) + +class CallbackMethod(CallbackMember): + def __init__(self, sig, name, descriptorProvider, needThisHandling, rethrowContentException=False): + CallbackMember.__init__(self, sig, name, descriptorProvider, + needThisHandling, rethrowContentException) + def getRvalDecl(self): + return "let mut rval = UndefinedValue();\n" + + def getCall(self): + replacements = { + "thisObj": self.getThisObj(), + "getCallable": self.getCallableDecl() + } + if self.argCount > 0: + replacements["argv"] = "argv.as_mut_ptr()" + replacements["argc"] = "argc" + else: + replacements["argv"] = "nullptr" + replacements["argc"] = "0" + return string.Template("${getCallable}" + "let ok = unsafe {\n" + " JS_CallFunctionValue(cx, ${thisObj}, callable,\n" + " ${argc}, ${argv}, &mut rval)\n" + "};\n" + "if ok == 0 {\n" + " return Err(FailureUnknown);\n" + "}\n").substitute(replacements) + +class CallCallback(CallbackMethod): + def __init__(self, callback, descriptorProvider): + CallbackMethod.__init__(self, callback.signatures()[0], "Call", + descriptorProvider, needThisHandling=True) + + def getThisObj(self): + return "aThisObj" + + def getCallableDecl(self): + return "let callable = ObjectValue(unsafe {&*self.parent.callback()});\n"; + +class CallbackOperationBase(CallbackMethod): + """ + Common class for implementing various callback operations. + """ + def __init__(self, signature, jsName, nativeName, descriptor, singleOperation, rethrowContentException=False): + self.singleOperation = singleOperation + self.methodName = jsName + CallbackMethod.__init__(self, signature, nativeName, descriptor, singleOperation, rethrowContentException) + + def getThisObj(self): + if not self.singleOperation: + return "self.parent.callback()" + # This relies on getCallableDecl declaring a boolean + # isCallable in the case when we're a single-operation + # interface. + return "if isCallable { aThisObj } else { self.parent.callback() }" + + def getCallableDecl(self): + replacements = { + "methodName": self.methodName + } + getCallableFromProp = string.Template( + 'match self.parent.GetCallableProperty(cx, "${methodName}") {\n' + ' Err(_) => return Err(FailureUnknown),\n' + ' Ok(callable) => callable,\n' + '}').substitute(replacements) + if not self.singleOperation: + return 'JS::Rooted<JS::Value> callable(cx);\n' + getCallableFromProp + return ( + 'let isCallable = unsafe { JS_ObjectIsCallable(cx, self.parent.callback()) != 0 };\n' + 'let callable =\n' + + CGIndenter( + CGIfElseWrapper('isCallable', + CGGeneric('unsafe { ObjectValue(&*self.parent.callback()) }'), + CGGeneric(getCallableFromProp))).define() + ';\n') + +class CallbackOperation(CallbackOperationBase): + """ + Codegen actual WebIDL operations on callback interfaces. + """ + def __init__(self, method, signature, descriptor): + self.ensureASCIIName(method) + jsName = method.identifier.name + CallbackOperationBase.__init__(self, signature, + jsName, MakeNativeName(jsName), + descriptor, descriptor.interface.isSingleOperationInterface(), + rethrowContentException=descriptor.interface.isJSImplemented()) + +class CallbackGetter(CallbackMember): + def __init__(self, attr, descriptor): + self.ensureASCIIName(attr) + self.attrName = attr.identifier.name + CallbackMember.__init__(self, + (attr.type, []), + callbackGetterName(attr), + descriptor, + needThisHandling=False, + rethrowContentException=descriptor.interface.isJSImplemented()) + + def getRvalDecl(self): + return "JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());\n" + + def getCall(self): + replacements = { + "attrName": self.attrName + } + return string.Template( + 'if (!JS_GetProperty(cx, mCallback, "${attrName}", &rval)) {\n' + ' return Err(FailureUnknown);\n' + '}\n').substitute(replacements); + +class CallbackSetter(CallbackMember): + def __init__(self, attr, descriptor): + self.ensureASCIIName(attr) + self.attrName = attr.identifier.name + CallbackMember.__init__(self, + (BuiltinTypes[IDLBuiltinType.Types.void], + [FakeArgument(attr.type, attr)]), + callbackSetterName(attr), + descriptor, + needThisHandling=False, + rethrowContentException=descriptor.interface.isJSImplemented()) + + def getRvalDecl(self): + # We don't need an rval + return "" + + def getCall(self): + replacements = { + "attrName": self.attrName, + "argv": "argv.handleAt(0)", + } + return string.Template( + 'MOZ_ASSERT(argv.length() == 1);\n' + 'if (!JS_SetProperty(cx, mCallback, "${attrName}", ${argv})) {\n' + ' return Err(FailureUnknown);\n' + '}\n').substitute(replacements) + + def getArgcDecl(self): + return None + +class GlobalGenRoots(): + """ + Roots for global codegen. + + To generate code, call the method associated with the target, and then + call the appropriate define/declare method. + """ + + @staticmethod + def PrototypeList(config): + # Prototype ID enum. + protos = [d.name for d in config.getDescriptors(isCallback=False)] + proxies = [d.name for d in config.getDescriptors(proxy=True)] + + return CGList([ + CGGeneric(AUTOGENERATED_WARNING_COMMENT), + CGGeneric("pub static MAX_PROTO_CHAIN_LENGTH: uint = %d;\n\n" % config.maxProtoChainLength), + CGNamespacedEnum('id', 'ID', protos, [0], deriving="PartialEq"), + CGNamespacedEnum('proxies', 'Proxy', proxies, [0], deriving="PartialEq"), + ]) + + + @staticmethod + def RegisterBindings(config): + # TODO - Generate the methods we want + code = CGList([ + CGRegisterProtos(config), + CGRegisterProxyHandlers(config), + ], "\n") + + return CGImports(code, [], [ + 'dom::bindings::codegen', + 'dom::bindings::codegen::PrototypeList::proxies', + 'js::jsapi::JSContext', + 'js::jsapi::JSObject', + 'libc', + ]) + + @staticmethod + def InterfaceTypes(config): + descriptors = [d.name for d in config.getDescriptors(register=True, isCallback=False)] + curr = CGList([CGGeneric("pub use dom::%s::%s;\n" % (name.lower(), name)) for name in descriptors]) + curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) + return curr + + @staticmethod + def Bindings(config): + + descriptors = (set(d.name + "Binding" for d in config.getDescriptors(register=True)) | + set(d.unroll().module() for d in config.callbacks)) + curr = CGList([CGGeneric("pub mod %s;\n" % name) for name in sorted(descriptors)]) + curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) + return curr + + @staticmethod + def InheritTypes(config): + + descriptors = config.getDescriptors(register=True, isCallback=False) + allprotos = [CGGeneric("#![allow(unused_imports)]\n"), + CGGeneric("use dom::types::*;\n"), + CGGeneric("use dom::bindings::js::{JS, JSRef, Temporary};\n"), + CGGeneric("use dom::bindings::trace::JSTraceable;\n"), + CGGeneric("use dom::bindings::utils::Reflectable;\n"), + CGGeneric("use serialize::{Encodable, Encoder};\n"), + CGGeneric("use js::jsapi::JSTracer;\n\n")] + for descriptor in descriptors: + name = descriptor.name + protos = [CGGeneric('pub trait %s {}\n' % (name + 'Base'))] + for proto in descriptor.prototypeChain: + protos += [CGGeneric('impl %s for %s {}\n' % (proto + 'Base', + descriptor.concreteType))] + derived = [CGGeneric('pub trait %s { fn %s(&self) -> bool; }\n' % + (name + 'Derived', 'is_' + name.lower()))] + for protoName in descriptor.prototypeChain[1:-1]: + protoDescriptor = config.getDescriptor(protoName) + delegate = string.Template('''impl ${selfName} for ${baseName} { + fn ${fname}(&self) -> bool { + self.${parentName}.${fname}() + } +} +''').substitute({'fname': 'is_' + name.lower(), + 'selfName': name + 'Derived', + 'baseName': protoDescriptor.concreteType, + 'parentName': protoDescriptor.prototypeChain[-2].lower()}) + derived += [CGGeneric(delegate)] + derived += [CGGeneric('\n')] + + cast = [CGGeneric(string.Template('''pub trait ${castTraitName} { + #[inline(always)] + fn to_ref<'a, 'b, T: ${toBound}+Reflectable>(base: &'a JSRef<'b, T>) -> Option<&'a JSRef<'b, Self>> { + match base.deref().${checkFn}() { + true => unsafe { Some(base.transmute()) }, + false => None + } + } + + #[inline(always)] + fn to_mut_ref<'a, 'b, T: ${toBound}+Reflectable>(base: &'a mut JSRef<'b, T>) -> Option<&'a mut JSRef<'b, Self>> { + match base.deref().${checkFn}() { + true => unsafe { Some(base.transmute_mut()) }, + false => None + } + } + + #[inline(always)] + fn from_ref<'a, 'b, T: ${fromBound}>(derived: &'a JSRef<'b, T>) -> &'a JSRef<'b, Self> { + unsafe { derived.transmute() } + } + + #[inline(always)] + fn from_mut_ref<'a, 'b, T: ${fromBound}>(derived: &'a mut JSRef<'b, T>) -> &'a mut JSRef<'b, Self> { + unsafe { derived.transmute_mut() } + } + + #[inline(always)] + fn from_temporary<T: ${fromBound}+Reflectable>(derived: Temporary<T>) -> Temporary<Self> { + unsafe { derived.transmute() } + } +} +''').substitute({'checkFn': 'is_' + name.lower(), + 'castTraitName': name + 'Cast', + 'fromBound': name + 'Base', + 'toBound': name + 'Derived'})), + CGGeneric("impl %s for %s {}\n\n" % (name + 'Cast', name))] + + trace = [CGGeneric(string.Template('''impl JSTraceable for ${name} { + fn trace(&self, tracer: *mut JSTracer) { + unsafe { + self.encode(&mut *tracer).ok().expect("failed to encode"); + } + } +} +''').substitute({'name': name}))] + + allprotos += protos + derived + cast + trace + + curr = CGList(allprotos) + curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) + return curr + + @staticmethod + def UnionTypes(config): + + curr = UnionTypes(config.getDescriptors(), + config.getDictionaries(), + config.getCallbacks(), + config) + + # Add the auto-generated comment. + curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) + + # Done. + return curr diff --git a/components/script/dom/bindings/codegen/Configuration.py b/components/script/dom/bindings/codegen/Configuration.py new file mode 100644 index 00000000000..d9be43fd2e6 --- /dev/null +++ b/components/script/dom/bindings/codegen/Configuration.py @@ -0,0 +1,341 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.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 IDLInterface + +autogenerated_comment = "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n" + +class Configuration: + """ + Represents global configuration state based on IDL parse data and + the configuration file. + """ + def __init__(self, filename, parseData): + # 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.maxProtoChainLength = 0; + for thing in parseData: + # Some toplevel things are sadly types, and those have an + # isInterface that doesn't mean the same thing as IDLObject's + # isInterface()... + if not isinstance(thing, IDLInterface): + 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(): + continue + entry = {} + else: + entry = config[iface.identifier.name] + if not isinstance(entry, list): + assert isinstance(entry, dict) + entry = [entry] + self.descriptors.extend([Descriptor(self, iface, x) for x in entry]) + + # Mark the descriptors for which only a single nativeType implements + # an interface. + for descriptor in self.descriptors: + intefaceName = descriptor.interface.identifier.name + otherDescriptors = [d for d in self.descriptors + if d.interface.identifier.name == intefaceName] + descriptor.uniqueImplementation = len(otherDescriptors) == 1 + + 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()] + + # Keep the descriptor list sorted for determinism. + self.descriptors.sort(lambda x,y: cmp(x.name, y.name)) + + def getInterface(self, ifname): + return self.interfaces[ifname] + def getDescriptors(self, **filters): + """Gets the descriptors that match the given filters.""" + curr = self.descriptors + for key, val in filters.iteritems(): + if key == 'webIDLFile': + getter = lambda x: x.interface.filename() + elif key == 'hasInterfaceObject': + getter = lambda x: x.interface.hasInterfaceObject() + elif key == 'isCallback': + getter = lambda x: x.interface.isCallback() + elif key == 'isJSImplemented': + getter = lambda x: x.interface.isJSImplemented() + else: + getter = lambda x: getattr(x, key) + curr = filter(lambda x: getter(x) == val, curr) + return curr + def getEnums(self, webIDLFile): + return filter(lambda e: e.filename() == webIDLFile, self.enums) + + @staticmethod + def _filterForFile(items, webIDLFile=""): + """Gets the items that match the given filters.""" + if not webIDLFile: + return items + + return filter(lambda x: x.filename() == webIDLFile, items) + + def getDictionaries(self, webIDLFile=""): + return self._filterForFile(self.dictionaries, webIDLFile=webIDLFile) + def getCallbacks(self, webIDLFile=""): + return self._filterForFile(self.callbacks, webIDLFile=webIDLFile) + + def getDescriptor(self, interfaceName): + """ + Gets the appropriate descriptor for the given interface name. + """ + iface = self.getInterface(interfaceName) + descriptors = self.getDescriptors(interface=iface) + + # We should have exactly one result. + if len(descriptors) is not 1: + raise NoSuchDescriptorError("For " + interfaceName + " found " + + str(len(matches)) + " matches"); + return descriptors[0] + def getDescriptorProvider(self): + """ + Gets a descriptor provider that can provide descriptors as needed. + """ + return DescriptorProvider(self) + +class NoSuchDescriptorError(TypeError): + def __init__(self, str): + TypeError.__init__(self, str) + +class DescriptorProvider: + """ + A way of getting descriptors for interface names + """ + def __init__(self, config): + self.config = config + + def getDescriptor(self, interfaceName): + """ + Gets the appropriate descriptor for the given interface name given the + context of the current descriptor. + """ + return self.config.getDescriptor(interfaceName) + +class Descriptor(DescriptorProvider): + """ + Represents a single descriptor for an interface. See Bindings.conf. + """ + def __init__(self, config, interface, desc): + DescriptorProvider.__init__(self, config) + self.interface = interface + + # Read the desc, and fill in the relevant defaults. + ifaceName = self.interface.identifier.name + + # Callback types do not use JS smart pointers, so we should not use the + # built-in rooting mechanisms for them. + if self.interface.isCallback(): + self.needsRooting = False + else: + self.needsRooting = True + + self.returnType = desc.get('returnType', "Temporary<%s>" % ifaceName) + self.argumentType = "JSRef<%s>" % ifaceName + self.memberType = "Root<'a, 'b, %s>" % ifaceName + self.nativeType = desc.get('nativeType', 'JS<%s>' % ifaceName) + self.concreteType = desc.get('concreteType', ifaceName) + self.register = desc.get('register', True) + self.outerObjectHook = desc.get('outerObjectHook', 'None') + + # If we're concrete, we need to crawl our ancestor interfaces and mark + # them as having a concrete descendant. + self.concrete = desc.get('concrete', True) + if self.concrete: + self.proxy = False + operations = { + 'IndexedGetter': None, + 'IndexedSetter': None, + 'IndexedCreator': None, + 'IndexedDeleter': None, + 'NamedGetter': None, + 'NamedSetter': None, + 'NamedCreator': None, + 'NamedDeleter': None, + 'Stringifier': None + } + iface = self.interface + while iface: + for m in iface.members: + if not m.isMethod(): + continue + + def addOperation(operation, m): + if not operations[operation]: + operations[operation] = m + def addIndexedOrNamedOperation(operation, m): + self.proxy = True + if m.isIndexed(): + operation = 'Indexed' + operation + else: + assert m.isNamed() + operation = 'Named' + operation + addOperation(operation, m) + + if m.isStringifier(): + addOperation('Stringifier', m) + else: + if m.isGetter(): + addIndexedOrNamedOperation('Getter', m) + if m.isSetter(): + addIndexedOrNamedOperation('Setter', m) + if m.isCreator(): + addIndexedOrNamedOperation('Creator', m) + if m.isDeleter(): + addIndexedOrNamedOperation('Deleter', m) + raise TypeError("deleter specified on %s but we " + "don't support deleters yet" % + self.interface.identifier.name) + + iface.setUserData('hasConcreteDescendant', True) + iface = iface.parent + + if self.proxy: + self.operations = operations + iface = self.interface + while iface: + iface.setUserData('hasProxyDescendant', True) + iface = iface.parent + + 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) + + # 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 getExtendedAttributes(self, member, getter=False, setter=False): + def maybeAppendInfallibleToAttrs(attrs, throws): + if throws is None: + attrs.append("infallible") + elif throws is True: + pass + else: + raise TypeError("Unknown value for 'Throws'") + + name = member.identifier.name + if member.isMethod(): + attrs = self.extendedAttributes['all'].get(name, []) + throws = member.getExtendedAttribute("Throws") + 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, []) + throws = member.getExtendedAttribute("Throws") + if throws is None: + throwsAttr = "GetterThrows" if getter else "SetterThrows" + throws = member.getExtendedAttribute(throwsAttr) + maybeAppendInfallibleToAttrs(attrs, throws) + return attrs + + 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")) + + +# 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()) + 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 diff --git a/components/script/dom/bindings/codegen/DOMJSClass.h b/components/script/dom/bindings/codegen/DOMJSClass.h new file mode 100644 index 00000000000..151960b5901 --- /dev/null +++ b/components/script/dom/bindings/codegen/DOMJSClass.h @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_DOMJSClass_h +#define mozilla_dom_DOMJSClass_h + +#include "jsapi.h" +#include "jsfriendapi.h" + +#include "mozilla/dom/PrototypeList.h" // auto-generated + +// We use slot 0 for holding the raw object. This is safe for both +// globals and non-globals. +#define DOM_OBJECT_SLOT 0 + +// All DOM globals must have a slot at DOM_PROTOTYPE_SLOT. We have to +// start at 1 past JSCLASS_GLOBAL_SLOT_COUNT because XPConnect uses +// that one. +#define DOM_PROTOTYPE_SLOT (JSCLASS_GLOBAL_SLOT_COUNT + 1) + +// We use these flag bits for the new bindings. +#define JSCLASS_DOM_GLOBAL JSCLASS_USERBIT1 + +// 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_PROTO_INSTANCE_CLASS_SLOT 0 + +namespace mozilla { +namespace dom { + +typedef bool +(* ResolveProperty)(JSContext* cx, JSObject* wrapper, jsid id, bool set, + JSPropertyDescriptor* desc); +typedef bool +(* EnumerateProperties)(JSContext* cx, JSObject* wrapper, + JS::AutoIdVector& props); + +struct NativePropertyHooks +{ + ResolveProperty mResolveOwnProperty; + ResolveProperty mResolveProperty; + EnumerateProperties mEnumerateOwnProperties; + EnumerateProperties mEnumerateProperties; + + const NativePropertyHooks *mProtoHooks; +}; + +struct DOMClass +{ + // A list of interfaces that this object implements, in order of decreasing + // derivedness. + const prototypes::ID mInterfaceChain[prototypes::id::_ID_Count]; + + // 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; +}; + +// 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. + JSClass mBase; + + DOMClass mClass; + + static DOMJSClass* FromJSClass(JSClass* base) { + MOZ_ASSERT(base->flags & JSCLASS_IS_DOMJSCLASS); + return reinterpret_cast<DOMJSClass*>(base); + } + static const DOMJSClass* FromJSClass(const JSClass* base) { + MOZ_ASSERT(base->flags & JSCLASS_IS_DOMJSCLASS); + return reinterpret_cast<const DOMJSClass*>(base); + } + + static DOMJSClass* FromJSClass(js::Class* base) { + return FromJSClass(Jsvalify(base)); + } + static const DOMJSClass* FromJSClass(const js::Class* base) { + return FromJSClass(Jsvalify(base)); + } + + JSClass* ToJSClass() { return &mBase; } +}; + +inline bool +HasProtoOrIfaceArray(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 JSObject** +GetProtoOrIfaceArray(JSObject* global) +{ + MOZ_ASSERT(js::GetObjectClass(global)->flags & JSCLASS_DOM_GLOBAL); + return static_cast<JSObject**>( + js::GetReservedSlot(global, DOM_PROTOTYPE_SLOT).toPrivate()); +} + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_DOMJSClass_h */ diff --git a/components/script/dom/bindings/codegen/DOMJSProxyHandler.cpp b/components/script/dom/bindings/codegen/DOMJSProxyHandler.cpp new file mode 100644 index 00000000000..af45cc6ed1a --- /dev/null +++ b/components/script/dom/bindings/codegen/DOMJSProxyHandler.cpp @@ -0,0 +1,247 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=2 sw=2 et tw=99 ft=cpp: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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/Util.h" + +#include "DOMJSProxyHandler.h" +#include "xpcpublic.h" +#include "xpcprivate.h" +#include "XPCQuickStubs.h" +#include "XPCWrapper.h" +#include "WrapperFactory.h" +#include "nsDOMClassInfo.h" +#include "nsGlobalWindow.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) +{ + JSAutoRequest ar(cx); + + return InternJSString(cx, s_length_id, "length"); +} + + +int HandlerFamily; + +// Store the information for the specialized ICs. +struct SetListBaseInformation +{ + SetListBaseInformation() { + js::SetListBaseInformation((void*) &HandlerFamily, js::JSSLOT_PROXY_EXTRA + JSPROXYSLOT_EXPANDO); + } +}; + +SetListBaseInformation gSetListBaseInformation; + + +bool +DefineConstructor(JSContext* cx, JSObject* obj, DefineInterface aDefine, nsresult* aResult) +{ + bool enabled; + bool defined = aDefine(cx, obj, &enabled); + MOZ_ASSERT(!defined || enabled, + "We defined a constructor but the new bindings are disabled?"); + *aResult = defined ? NS_OK : NS_ERROR_FAILURE; + return enabled; +} + +// static +JSObject* +DOMProxyHandler::EnsureExpandoObject(JSContext* cx, JSObject* obj) +{ + NS_ASSERTION(IsDOMProxy(obj), "expected a DOM proxy object"); + JSObject* expando = GetExpandoObject(obj); + if (!expando) { + expando = JS_NewObjectWithGivenProto(cx, nullptr, nullptr, + js::GetObjectParent(obj)); + if (!expando) { + return NULL; + } + + xpc::CompartmentPrivate* priv = xpc::GetCompartmentPrivate(obj); + if (!priv->RegisterDOMExpandoObject(obj)) { + return NULL; + } + + nsWrapperCache* cache; + CallQueryInterface(UnwrapDOMObject<nsISupports>(obj, eProxyDOMObject), &cache); + cache->SetPreservingWrapper(true); + + js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, ObjectValue(*expando)); + } + return expando; +} + +bool +DOMProxyHandler::getPropertyDescriptor(JSContext* cx, JSObject* proxy, jsid id, bool set, + JSPropertyDescriptor* desc) +{ + if (!getOwnPropertyDescriptor(cx, proxy, id, set, desc)) { + return false; + } + if (desc->obj) { + return true; + } + + JSObject* proto; + if (!js::GetObjectProto(cx, proxy, &proto)) { + return false; + } + if (!proto) { + desc->obj = NULL; + return true; + } + + return JS_GetPropertyDescriptorById(cx, proto, id, JSRESOLVE_QUALIFIED, desc); +} + +bool +DOMProxyHandler::defineProperty(JSContext* cx, JSObject* proxy, jsid id, + JSPropertyDescriptor* desc) +{ + if ((desc->attrs & JSPROP_GETTER) && desc->setter == JS_StrictPropertyStub) { + return JS_ReportErrorFlagsAndNumber(cx, + JSREPORT_WARNING | JSREPORT_STRICT | + JSREPORT_STRICT_MODE_ERROR, + js_GetErrorMessage, NULL, + JSMSG_GETTER_ONLY); + } + + if (xpc::WrapperFactory::IsXrayWrapper(proxy)) { + return true; + } + + JSObject* expando = EnsureExpandoObject(cx, proxy); + if (!expando) { + return false; + } + + return JS_DefinePropertyById(cx, expando, id, desc->value, desc->getter, desc->setter, + desc->attrs); +} + +bool +DOMProxyHandler::delete_(JSContext* cx, JSObject* proxy, jsid id, bool* bp) +{ + JSBool b = true; + + JSObject* expando; + if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = GetExpandoObject(proxy))) { + Value v; + if (!JS_DeletePropertyById2(cx, expando, id, &v) || !JS_ValueToBoolean(cx, v, &b)) { + return false; + } + } + + *bp = !!b; + return true; +} + +bool +DOMProxyHandler::enumerate(JSContext* cx, JSObject* proxy, AutoIdVector& props) +{ + JSObject* proto; + if (!JS_GetPrototype(cx, proxy, &proto)) { + return false; + } + return getOwnPropertyNames(cx, proxy, props) && + (!proto || js::GetPropertyNames(cx, proto, 0, &props)); +} + +bool +DOMProxyHandler::fix(JSContext* cx, JSObject* proxy, Value* vp) +{ + vp->setUndefined(); + return true; +} + +bool +DOMProxyHandler::has(JSContext* cx, JSObject* proxy, jsid id, bool* bp) +{ + if (!hasOwn(cx, proxy, id, bp)) { + return false; + } + + if (*bp) { + // We have the property ourselves; no need to worry about our prototype + // chain. + return true; + } + + // OK, now we have to look at the proto + JSObject *proto; + if (!js::GetObjectProto(cx, proxy, &proto)) { + return false; + } + if (!proto) { + return true; + } + JSBool protoHasProp; + bool ok = JS_HasPropertyById(cx, proto, id, &protoHasProp); + if (ok) { + *bp = protoHasProp; + } + return ok; +} + +// static +JSString* +DOMProxyHandler::obj_toString(JSContext* cx, const char* className) +{ + size_t nchars = sizeof("[object ]") - 1 + strlen(className); + jschar* chars = static_cast<jschar*>(JS_malloc(cx, (nchars + 1) * sizeof(jschar))); + if (!chars) { + return NULL; + } + + const char* prefix = "[object "; + nchars = 0; + while ((chars[nchars] = (jschar)*prefix) != 0) { + nchars++, prefix++; + } + while ((chars[nchars] = (jschar)*className) != 0) { + nchars++, className++; + } + chars[nchars++] = ']'; + chars[nchars] = 0; + + JSString* str = JS_NewUCString(cx, chars, nchars); + if (!str) { + JS_free(cx, chars); + } + return str; +} + +int32_t +IdToInt32(JSContext* cx, jsid id) +{ + JSAutoRequest ar(cx); + + jsval idval; + double array_index; + int32_t i; + if (!::JS_IdToValue(cx, id, &idval) || + !::JS_ValueToNumber(cx, idval, &array_index) || + !::JS_DoubleIsInt32(array_index, &i)) { + return -1; + } + + return i; +} + +} // namespace dom +} // namespace mozilla diff --git a/components/script/dom/bindings/codegen/DOMJSProxyHandler.h b/components/script/dom/bindings/codegen/DOMJSProxyHandler.h new file mode 100644 index 00000000000..394e2dc4d2f --- /dev/null +++ b/components/script/dom/bindings/codegen/DOMJSProxyHandler.h @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_DOMJSProxyHandler_h +#define mozilla_dom_DOMJSProxyHandler_h + +#include "jsapi.h" +#include "jsfriendapi.h" +#include "jsproxy.h" +#include "xpcpublic.h" +#include "nsString.h" +#include "mozilla/Likely.h" + +#define DOM_PROXY_OBJECT_SLOT js::JSSLOT_PROXY_PRIVATE + +namespace mozilla { +namespace dom { + +enum { + JSPROXYSLOT_EXPANDO = 0 +}; + +template<typename T> struct Prefable; + +class DOMProxyHandler : public DOMBaseProxyHandler +{ +public: + DOMProxyHandler(const DOMClass& aClass) + : DOMBaseProxyHandler(true), + mClass(aClass) + { + } + + bool getPropertyDescriptor(JSContext* cx, JSObject* proxy, jsid id, bool set, + JSPropertyDescriptor* desc); + bool defineProperty(JSContext* cx, JSObject* proxy, jsid id, + JSPropertyDescriptor* desc); + bool delete_(JSContext* cx, JSObject* proxy, jsid id, bool* bp); + bool enumerate(JSContext* cx, JSObject* proxy, JS::AutoIdVector& props); + bool fix(JSContext* cx, JSObject* proxy, JS::Value* vp); + bool has(JSContext* cx, JSObject* proxy, jsid id, bool* bp); + using js::BaseProxyHandler::obj_toString; + + static JSObject* GetExpandoObject(JSObject* obj) + { + MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object"); + JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO); + return v.isUndefined() ? NULL : v.toObjectOrNull(); + } + static JSObject* EnsureExpandoObject(JSContext* cx, JSObject* obj); + + const DOMClass& mClass; + +protected: + static JSString* obj_toString(JSContext* cx, const char* className); +}; + +extern jsid s_length_id; + +int32_t IdToInt32(JSContext* cx, jsid id); + +inline int32_t +GetArrayIndexFromId(JSContext* cx, jsid id) +{ + if (MOZ_LIKELY(JSID_IS_INT(id))) { + return JSID_TO_INT(id); + } + if (MOZ_LIKELY(id == s_length_id)) { + return -1; + } + if (MOZ_LIKELY(JSID_IS_ATOM(id))) { + JSAtom* atom = JSID_TO_ATOM(id); + jschar s = *js::GetAtomChars(atom); + if (MOZ_LIKELY((unsigned)s >= 'a' && (unsigned)s <= 'z')) + return -1; + + uint32_t i; + JSLinearString* str = js::AtomToLinearString(JSID_TO_ATOM(id)); + return js::StringIsArrayIndex(str, &i) ? i : -1; + } + return IdToInt32(cx, id); +} + +inline void +FillPropertyDescriptor(JSPropertyDescriptor* desc, JSObject* obj, bool readonly) +{ + desc->obj = obj; + desc->attrs = (readonly ? JSPROP_READONLY : 0) | JSPROP_ENUMERATE; + desc->getter = NULL; + desc->setter = NULL; + desc->shortid = 0; +} + +inline void +FillPropertyDescriptor(JSPropertyDescriptor* desc, JSObject* obj, jsval v, bool readonly) +{ + desc->value = v; + FillPropertyDescriptor(desc, obj, readonly); +} + +JSObject* +EnsureExpandoObject(JSContext* cx, JSObject* obj); + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_DOMProxyHandler_h */ diff --git a/components/script/dom/bindings/codegen/ErrorResult.h b/components/script/dom/bindings/codegen/ErrorResult.h new file mode 100644 index 00000000000..bbd9404a865 --- /dev/null +++ b/components/script/dom/bindings/codegen/ErrorResult.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ +/* vim: set ts=2 sw=2 et tw=79: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * A struct for tracking exceptions that need to be thrown to JS. + */ + +#ifndef mozilla_ErrorResult_h +#define mozilla_ErrorResult_h + +#include "nscore.h" +#include "mozilla/Assertions.h" + +namespace mozilla { + +class ErrorResult { +public: + ErrorResult() { + mResult = NS_OK; + } + + void Throw(nsresult rv) { + MOZ_ASSERT(NS_FAILED(rv), "Please don't try throwing success"); + mResult = rv; + } + + // 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) { + mResult = rv; + } + + bool Failed() const { + return NS_FAILED(mResult); + } + + nsresult ErrorCode() const { + return mResult; + } + +private: + nsresult mResult; + + // Not to be implemented, to make sure people always pass this by + // reference, not by value. + ErrorResult(const ErrorResult&) MOZ_DELETE; +}; + +} // namespace mozilla + +#endif /* mozilla_ErrorResult_h */ diff --git a/components/script/dom/bindings/codegen/Errors.msg b/components/script/dom/bindings/codegen/Errors.msg new file mode 100644 index 00000000000..81d6624cec8 --- /dev/null +++ b/components/script/dom/bindings/codegen/Errors.msg @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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>, <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. + * + * <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, 2, "Value '{0}' is not a valid value for enumeration {1}.") +MSG_DEF(MSG_MISSING_ARGUMENTS, 1, "Not enough arguments to {0}.") +MSG_DEF(MSG_NOT_OBJECT, 0, "Value not an object.") +MSG_DEF(MSG_DOES_NOT_IMPLEMENT_INTERFACE, 1, "Value does not implement interface {0}.") +MSG_DEF(MSG_NOT_IN_UNION, 1, "Value could not be converted to any of: {0}.") +MSG_DEF(MSG_ILLEGAL_CONSTRUCTOR, 0, "Illegal constructor.") +MSG_DEF(MSG_NO_PROPERTY_SETTER, 1, "{0} doesn't have an indexed property setter.") +MSG_DEF(MSG_ENFORCE_RANGE_NON_FINITE, 1, "Non-finite value is out of range for {0}.") +MSG_DEF(MSG_ENFORCE_RANGE_OUT_OF_RANGE, 1, "Value is out of range for {0}.") diff --git a/components/script/dom/bindings/codegen/GenerateCSS2PropertiesWebIDL.py b/components/script/dom/bindings/codegen/GenerateCSS2PropertiesWebIDL.py new file mode 100644 index 00000000000..1bb50afaee7 --- /dev/null +++ b/components/script/dom/bindings/codegen/GenerateCSS2PropertiesWebIDL.py @@ -0,0 +1,26 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.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 + +propList = eval(sys.stdin.read()) +props = "" +for [prop, pref] in propList: + extendedAttrs = ["Throws", "TreatNullAs=EmptyString"] + if pref is not "": + extendedAttrs.append("Pref=%s" % pref) + if not prop.startswith("Moz"): + prop = prop[0].lower() + prop[1:] + # Unfortunately, even some of the getters here are fallible + # (e.g. on nsComputedDOMStyle). + props += " [%s] attribute DOMString %s;\n" % (", ".join(extendedAttrs), + prop) + +idlFile = open(sys.argv[1], "r"); +idlTemplate = idlFile.read(); +idlFile.close(); + +print ("/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */\n\n" + + string.Template(idlTemplate).substitute({ "props": props })) diff --git a/components/script/dom/bindings/codegen/GlobalGen.py b/components/script/dom/bindings/codegen/GlobalGen.py new file mode 100644 index 00000000000..cdca464e029 --- /dev/null +++ b/components/script/dom/bindings/codegen/GlobalGen.py @@ -0,0 +1,83 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# We do one global pass over all the WebIDL to generate our prototype enum +# and generate information for subsequent phases. + +import sys +sys.path.append("./parser/") +sys.path.append("./ply/") +import os +import cStringIO +import WebIDL +import cPickle +from Configuration import * +from CodegenRust import GlobalGenRoots, replaceFileIfChanged +# import Codegen in general, so we can set a variable on it +import Codegen + +def generate_file(config, name, filename): + root = getattr(GlobalGenRoots, name)(config) + code = root.define() + + if replaceFileIfChanged(filename, code): + print "Generating %s" % (filename) + else: + print "%s hasn't changed - not touching it" % (filename) + +def main(): + # Parse arguments. + from optparse import OptionParser + usageString = "usage: %prog [options] webidldir [files]" + o = OptionParser(usage=usageString) + o.add_option("--cachedir", dest='cachedir', default=None, + help="Directory in which to cache lex/parse tables.") + o.add_option("--verbose-errors", action='store_true', default=False, + help="When an error happens, display the Python traceback.") + (options, args) = o.parse_args() + + if len(args) < 2: + o.error(usageString) + + configFile = args[0] + baseDir = args[1] + fileList = args[2:] + + # Parse the WebIDL. + parser = WebIDL.Parser(options.cachedir) + for filename in fileList: + fullPath = os.path.normpath(os.path.join(baseDir, filename)) + f = open(fullPath, 'rb') + lines = f.readlines() + f.close() + parser.parse(''.join(lines), fullPath) + parserResults = parser.finish() + + # Write the parser results out to a pickle. + resultsFile = open('ParserResults.pkl', 'wb') + cPickle.dump(parserResults, resultsFile, -1) + resultsFile.close() + + # Load the configuration. + config = Configuration(configFile, parserResults) + + # Generate the prototype list. + generate_file(config, 'PrototypeList', 'PrototypeList.rs') + + # Generate the common code. + generate_file(config, 'RegisterBindings', 'RegisterBindings.rs') + + # Generate the type list. + generate_file(config, 'InterfaceTypes', 'InterfaceTypes.rs') + + # Generate the type list. + generate_file(config, 'InheritTypes', 'InheritTypes.rs') + + # Generate the module declarations. + generate_file(config, 'Bindings', 'Bindings/mod.rs') + + generate_file(config, 'UnionTypes', 'UnionTypes.rs') + +if __name__ == '__main__': + main() diff --git a/components/script/dom/bindings/codegen/Makefile.in b/components/script/dom/bindings/codegen/Makefile.in new file mode 100644 index 00000000000..5fef1e77218 --- /dev/null +++ b/components/script/dom/bindings/codegen/Makefile.in @@ -0,0 +1,165 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +DEPTH = @DEPTH@ +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ +FAIL_ON_WARNINGS := 1 + +MODULE = dom +LIBRARY_NAME = dombindings_s +LIBXUL_LIBRARY = 1 +FORCE_STATIC_LIB = 1 +EXPORT_LIBRARY = 1 + +include $(topsrcdir)/config/config.mk + +# Need this to find all our DOM source files. +include $(topsrcdir)/dom/dom-config.mk + +include $(topsrcdir)/dom/webidl/WebIDL.mk + +binding_include_path := mozilla/dom +all_webidl_files = $(webidl_files) $(generated_webidl_files) +# Set exported_binding_headers before adding the test IDL to the mix +exported_binding_headers := $(subst .webidl,Binding.h,$(all_webidl_files)) +# Set linked_binding_cpp_files before adding the test IDL to the mix +linked_binding_cpp_files := $(subst .webidl,Binding.cpp,$(all_webidl_files)) + +all_webidl_files += $(test_webidl_files) + +binding_header_files := $(subst .webidl,Binding.h,$(all_webidl_files)) +binding_cpp_files := $(subst .webidl,Binding.cpp,$(all_webidl_files)) + +globalgen_targets := \ + PrototypeList.h \ + RegisterBindings.h \ + RegisterBindings.cpp \ + UnionTypes.h \ + UnionConversions.h \ + $(NULL) + +CPPSRCS = \ + $(linked_binding_cpp_files) \ + $(filter %.cpp, $(globalgen_targets)) \ + BindingUtils.cpp \ + DOMJSProxyHandler.cpp \ + $(NULL) + +EXPORTS_NAMESPACES = $(binding_include_path) mozilla + +EXPORTS_mozilla = \ + ErrorResult.h \ + $(NULL) + +EXPORTS_$(binding_include_path) = \ + BindingUtils.h \ + DOMJSClass.h \ + DOMJSProxyHandler.h \ + Errors.msg \ + Nullable.h \ + PrimitiveConversions.h \ + PrototypeList.h \ + RegisterBindings.h \ + TypedArray.h \ + UnionConversions.h \ + UnionTypes.h \ + $(exported_binding_headers) \ + $(NULL) + +LOCAL_INCLUDES += -I$(topsrcdir)/js/xpconnect/src \ + -I$(topsrcdir)/js/xpconnect/wrappers \ + -I$(topsrcdir)/content/canvas/src \ + -I$(topsrcdir)/content/html/content/src + +include $(topsrcdir)/config/rules.mk + +# If you change bindinggen_dependencies here, change it in +# dom/bindings/test/Makefile.in too. +bindinggen_dependencies := \ + BindingGen.py \ + Bindings.conf \ + Configuration.py \ + Codegen.py \ + parser/WebIDL.py \ + ParserResults.pkl \ + $(GLOBAL_DEPS) \ + $(NULL) + +CSS2Properties.webidl: $(topsrcdir)/layout/style/nsCSSPropList.h \ + $(topsrcdir)/layout/style/nsCSSPropAliasList.h \ + $(webidl_base)/CSS2Properties.webidl.in \ + $(webidl_base)/CSS2PropertiesProps.h \ + $(srcdir)/GenerateCSS2PropertiesWebIDL.py \ + $(GLOBAL_DEPS) + $(CPP) $(DEFINES) $(ACDEFINES) -I$(topsrcdir)/layout/style $(webidl_base)/CSS2PropertiesProps.h | \ + $(PYTHON) \ + $(srcdir)/GenerateCSS2PropertiesWebIDL.py $(webidl_base)/CSS2Properties.webidl.in > CSS2Properties.webidl + +$(webidl_files): %: $(webidl_base)/% + $(INSTALL) $(IFLAGS1) $(webidl_base)/$* . + +$(test_webidl_files): %: $(srcdir)/test/% + $(INSTALL) $(IFLAGS1) $(srcdir)/test/$* . + +$(binding_header_files): %Binding.h: $(bindinggen_dependencies) \ + %.webidl \ + $(NULL) + $(PYTHON) $(topsrcdir)/config/pythonpath.py \ + $(PLY_INCLUDE) -I$(srcdir)/parser \ + $(srcdir)/BindingGen.py header \ + $(srcdir)/Bindings.conf $*Binding \ + $*.webidl + +$(binding_cpp_files): %Binding.cpp: $(bindinggen_dependencies) \ + %.webidl \ + $(NULL) + $(PYTHON) $(topsrcdir)/config/pythonpath.py \ + $(PLY_INCLUDE) -I$(srcdir)/parser \ + $(srcdir)/BindingGen.py cpp \ + $(srcdir)/Bindings.conf $*Binding \ + $*.webidl + +$(globalgen_targets): ParserResults.pkl + +CACHE_DIR = _cache + +globalgen_dependencies := \ + GlobalGen.py \ + Bindings.conf \ + Configuration.py \ + Codegen.py \ + parser/WebIDL.py \ + $(CACHE_DIR)/.done \ + $(GLOBAL_DEPS) \ + $(NULL) + +$(CACHE_DIR)/.done: + $(MKDIR) -p $(CACHE_DIR) + @$(TOUCH) $@ + +ParserResults.pkl: $(globalgen_dependencies) \ + $(all_webidl_files) + $(PYTHON) $(topsrcdir)/config/pythonpath.py \ + $(PLY_INCLUDE) -I$(srcdir)/parser \ + $(srcdir)/GlobalGen.py $(srcdir)/Bindings.conf . \ + --cachedir=$(CACHE_DIR) \ + $(all_webidl_files) + +GARBAGE += \ + $(binding_header_files) \ + $(binding_cpp_files) \ + $(all_webidl_files) \ + $(globalgen_targets) \ + ParserResults.pkl \ + webidlyacc.py \ + parser.out \ + $(NULL) + +# Make sure all binding header files are created during the export stage, so we +# don't have issues with .cpp files being compiled before we've generated the +# headers they depend on. This is really only needed for the test files, since +# the non-test headers are all exported above anyway. +export:: $(binding_header_files) diff --git a/components/script/dom/bindings/codegen/Nullable.h b/components/script/dom/bindings/codegen/Nullable.h new file mode 100644 index 00000000000..8b2cc08642b --- /dev/null +++ b/components/script/dom/bindings/codegen/Nullable.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ +/* vim: set ts=2 sw=2 et tw=79: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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" + +namespace mozilla { +namespace dom { + +// Support for nullable types +template <typename T> +struct Nullable +{ +private: + T mValue; + bool mIsNull; + +public: + Nullable() + : mIsNull(true) + {} + + Nullable(T aValue) + : mValue(aValue) + , mIsNull(false) + {} + + void SetValue(T aValue) { + mValue = aValue; + mIsNull = false; + } + + // 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() { + mIsNull = false; + return mValue; + } + + void SetNull() { + mIsNull = true; + } + + const T& Value() const { + MOZ_ASSERT(!mIsNull); + return mValue; + } + + T& Value() { + MOZ_ASSERT(!mIsNull); + return mValue; + } + + bool IsNull() const { + return mIsNull; + } +}; + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_Nullable_h */ diff --git a/components/script/dom/bindings/codegen/PrimitiveConversions.h b/components/script/dom/bindings/codegen/PrimitiveConversions.h new file mode 100644 index 00000000000..40c27425772 --- /dev/null +++ b/components/script/dom/bindings/codegen/PrimitiveConversions.h @@ -0,0 +1,350 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ +/* vim: set ts=2 sw=2 et tw=79: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "mozilla/Assertions.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/FloatingPoint.h" +#include "xpcpublic.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::Value v, jstype* retval) { + MOZ_NOT_REACHED("This should never be instantiated!"); + return false; + } +}; + +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::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::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::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::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) +{ + MOZ_STATIC_ASSERT(std::numeric_limits<T>::is_integer, + "This can only be applied to integers!"); + + if (!MOZ_DOUBLE_IS_FINITE(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) +{ + MOZ_STATIC_ASSERT(std::numeric_limits<T>::is_integer, + "This can only be applied to integers!"); + + if (MOZ_DOUBLE_IS_NaN(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(MOZ_DOUBLE_IS_FINITE(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(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 JSBool jstype; + typedef bool intermediateType; + static inline bool converter(JSContext* /* unused */, 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::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::Value v, T* retval) +{ + typename PrimitiveConversionTraits<T, B>::jstype t; + if (!PrimitiveConversionTraits<T, B>::converter(cx, v, &t)) + return false; + + *retval = + static_cast<typename PrimitiveConversionTraits<T, B>::intermediateType>(t); + return true; +} + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_PrimitiveConversions_h */ diff --git a/components/script/dom/bindings/codegen/RegisterBindings.h b/components/script/dom/bindings/codegen/RegisterBindings.h new file mode 100644 index 00000000000..7d83a747cc3 --- /dev/null +++ b/components/script/dom/bindings/codegen/RegisterBindings.h @@ -0,0 +1,14 @@ +#ifndef mozilla_dom_RegisterBindings_h__ +#define mozilla_dom_RegisterBindings_h__ + + +namespace mozilla { +namespace dom { +void +Register(nsScriptNameSpaceManager* aNameSpaceManager); + +} // namespace dom +} // namespace mozilla + + +#endif // mozilla_dom_RegisterBindings_h__ diff --git a/components/script/dom/bindings/codegen/TypedArray.h b/components/script/dom/bindings/codegen/TypedArray.h new file mode 100644 index 00000000000..2a6f17bcb96 --- /dev/null +++ b/components/script/dom/bindings/codegen/TypedArray.h @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ +/* vim: set ts=2 sw=2 et tw=79: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "jsfriendapi.h" + +namespace mozilla { +namespace dom { + +/* + * Various typed array classes for argument conversion. We have a base class + * that has a way of initializing a TypedArray from an existing typed array, and + * a subclass of the base class that supports creation of a relevant typed array + * or array buffer object. + */ +template<typename T, + JSObject* UnboxArray(JSContext*, JSObject*, uint32_t*, T**)> +struct TypedArray_base { + TypedArray_base(JSContext* cx, JSObject* obj) + { + mObj = UnboxArray(cx, obj, &mLength, &mData); + } + +private: + T* mData; + uint32_t mLength; + JSObject* mObj; + +public: + inline bool inited() const { + return !!mObj; + } + + inline T *Data() const { + MOZ_ASSERT(inited()); + return mData; + } + + inline uint32_t Length() const { + MOZ_ASSERT(inited()); + return mLength; + } + + inline JSObject *Obj() const { + MOZ_ASSERT(inited()); + return mObj; + } +}; + + +template<typename T, + T* GetData(JSObject*, JSContext*), + JSObject* UnboxArray(JSContext*, JSObject*, uint32_t*, T**), + JSObject* CreateNew(JSContext*, uint32_t)> +struct TypedArray : public TypedArray_base<T,UnboxArray> { + TypedArray(JSContext* cx, JSObject* obj) : + TypedArray_base<T,UnboxArray>(cx, obj) + {} + + static inline JSObject* + Create(JSContext* cx, nsWrapperCache* creator, uint32_t length, + const T* data = NULL) { + JSObject* creatorWrapper; + Maybe<JSAutoCompartment> ac; + if (creator && (creatorWrapper = creator->GetWrapperPreserveColor())) { + ac.construct(cx, creatorWrapper); + } + JSObject* obj = CreateNew(cx, length); + if (!obj) { + return NULL; + } + if (data) { + T* buf = static_cast<T*>(GetData(obj, cx)); + memcpy(buf, data, length*sizeof(T)); + } + return obj; + } +}; + +typedef TypedArray<int8_t, JS_GetInt8ArrayData, JS_GetObjectAsInt8Array, + JS_NewInt8Array> + Int8Array; +typedef TypedArray<uint8_t, JS_GetUint8ArrayData, + JS_GetObjectAsUint8Array, JS_NewUint8Array> + Uint8Array; +typedef TypedArray<uint8_t, JS_GetUint8ClampedArrayData, + JS_GetObjectAsUint8ClampedArray, JS_NewUint8ClampedArray> + Uint8ClampedArray; +typedef TypedArray<int16_t, JS_GetInt16ArrayData, + JS_GetObjectAsInt16Array, JS_NewInt16Array> + Int16Array; +typedef TypedArray<uint16_t, JS_GetUint16ArrayData, + JS_GetObjectAsUint16Array, JS_NewUint16Array> + Uint16Array; +typedef TypedArray<int32_t, JS_GetInt32ArrayData, + JS_GetObjectAsInt32Array, JS_NewInt32Array> + Int32Array; +typedef TypedArray<uint32_t, JS_GetUint32ArrayData, + JS_GetObjectAsUint32Array, JS_NewUint32Array> + Uint32Array; +typedef TypedArray<float, JS_GetFloat32ArrayData, + JS_GetObjectAsFloat32Array, JS_NewFloat32Array> + Float32Array; +typedef TypedArray<double, JS_GetFloat64ArrayData, + JS_GetObjectAsFloat64Array, JS_NewFloat64Array> + Float64Array; +typedef TypedArray_base<uint8_t, JS_GetObjectAsArrayBufferView> + ArrayBufferView; +typedef TypedArray<uint8_t, JS_GetArrayBufferData, + JS_GetObjectAsArrayBuffer, JS_NewArrayBuffer> + ArrayBuffer; + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_TypedArray_h */ diff --git a/components/script/dom/bindings/codegen/crashtests/769464.html b/components/script/dom/bindings/codegen/crashtests/769464.html new file mode 100644 index 00000000000..84d6dbc08b4 --- /dev/null +++ b/components/script/dom/bindings/codegen/crashtests/769464.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<script> + +function boom() +{ + window.getComputedStyle(new Worker("404.js")); +} + +window.addEventListener("load", boom, false); + +</script> diff --git a/components/script/dom/bindings/codegen/crashtests/crashtests.list b/components/script/dom/bindings/codegen/crashtests/crashtests.list new file mode 100644 index 00000000000..cb954bd91fc --- /dev/null +++ b/components/script/dom/bindings/codegen/crashtests/crashtests.list @@ -0,0 +1 @@ +asserts-if(cocoaWidget,0-1) load 769464.html diff --git a/components/script/dom/bindings/codegen/parser/README b/components/script/dom/bindings/codegen/parser/README new file mode 100644 index 00000000000..94b64b88459 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/README @@ -0,0 +1 @@ +A WebIDL parser written in Python to be used in Mozilla.
\ No newline at end of file diff --git a/components/script/dom/bindings/codegen/parser/UPSTREAM b/components/script/dom/bindings/codegen/parser/UPSTREAM new file mode 100644 index 00000000000..7ac5899379e --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/UPSTREAM @@ -0,0 +1 @@ +http://dev.w3.org/cvsweb/~checkout~/2006/webapi/WebIDL/Overview.html?rev=1.409;content-type=text%2Fhtml%3b+charset=utf-8
\ No newline at end of file diff --git a/components/script/dom/bindings/codegen/parser/WebIDL.py b/components/script/dom/bindings/codegen/parser/WebIDL.py new file mode 100644 index 00000000000..32f80e82c56 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/WebIDL.py @@ -0,0 +1,5583 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +""" A WebIDL parser. """ + +from ply import lex, yacc +import re +import os +import traceback +import math +from collections import defaultdict + +# Machinery + +def parseInt(literal): + string = literal + sign = 0 + base = 0 + + if string[0] == '-': + sign = -1 + string = string[1:] + else: + sign = 1 + + if string[0] == '0' and len(string) > 1: + if string[1] == 'x' or string[1] == 'X': + base = 16 + string = string[2:] + else: + base = 8 + string = string[1:] + else: + base = 10 + + value = int(string, base) + return value * sign + +# Magic for creating enums +def M_add_class_attribs(attribs, start): + def foo(name, bases, dict_): + for v, k in enumerate(attribs): + dict_[k] = start + v + assert 'length' not in dict_ + dict_['length'] = start + len(attribs) + return type(name, bases, dict_) + return foo + +def enum(*names, **kw): + if len(kw) == 1: + base = kw['base'].__class__ + start = base.length + else: + assert len(kw) == 0 + base = object + start = 0 + class Foo(base): + __metaclass__ = M_add_class_attribs(names, start) + def __setattr__(self, name, value): # this makes it read-only + raise NotImplementedError + return Foo() + +class WebIDLError(Exception): + def __init__(self, message, locations, warning=False): + self.message = message + self.locations = [str(loc) for loc in locations] + self.warning = warning + + def __str__(self): + return "%s: %s%s%s" % (self.warning and 'warning' or 'error', + self.message, + ", " if len(self.locations) != 0 else "", + "\n".join(self.locations)) + +class Location(object): + def __init__(self, lexer, lineno, lexpos, filename): + self._line = None + self._lineno = lineno + self._lexpos = lexpos + self._lexdata = lexer.lexdata + self._file = filename if filename else "<unknown>" + + def __eq__(self, other): + return self._lexpos == other._lexpos and \ + self._file == other._file + + def filename(self): + return self._file + + def resolve(self): + if self._line: + return + + startofline = self._lexdata.rfind('\n', 0, self._lexpos) + 1 + endofline = self._lexdata.find('\n', self._lexpos, self._lexpos + 80) + if endofline != -1: + self._line = self._lexdata[startofline:endofline] + else: + self._line = self._lexdata[startofline:] + self._colno = self._lexpos - startofline + + # Our line number seems to point to the start of self._lexdata + self._lineno += self._lexdata.count('\n', 0, startofline) + + def get(self): + self.resolve() + return "%s line %s:%s" % (self._file, self._lineno, self._colno) + + def _pointerline(self): + return " " * self._colno + "^" + + def __str__(self): + self.resolve() + return "%s line %s:%s\n%s\n%s" % (self._file, self._lineno, self._colno, + self._line, self._pointerline()) + +class BuiltinLocation(object): + def __init__(self, text): + self.msg = text + "\n" + + def __eq__(self, other): + return isinstance(other, BuiltinLocation) and \ + self.msg == other.msg + + def filename(self): + return '<builtin>' + + def resolve(self): + pass + + def get(self): + return self.msg + + def __str__(self): + return self.get() + + +# Data Model + +class IDLObject(object): + def __init__(self, location): + self.location = location + self.userData = dict() + + def filename(self): + return self.location.filename() + + def isInterface(self): + return False + + def isEnum(self): + return False + + def isCallback(self): + return False + + def isType(self): + return False + + def isDictionary(self): + return False; + + def isUnion(self): + return False + + def getUserData(self, key, default): + return self.userData.get(key, default) + + def setUserData(self, key, value): + self.userData[key] = value + + def addExtendedAttributes(self, attrs): + assert False # Override me! + + def handleExtendedAttribute(self, attr): + assert False # Override me! + + def _getDependentObjects(self): + assert False # Override me! + + def getDeps(self, visited=None): + """ Return a set of files that this object depends on. If any of + these files are changed the parser needs to be rerun to regenerate + a new IDLObject. + + The visited argument is a set of all the objects already visited. + We must test to see if we are in it, and if so, do nothing. This + prevents infinite recursion.""" + + # NB: We can't use visited=set() above because the default value is + # evaluated when the def statement is evaluated, not when the function + # is executed, so there would be one set for all invocations. + if visited == None: + visited = set() + + if self in visited: + return set() + + visited.add(self) + + deps = set() + if self.filename() != "<builtin>": + deps.add(self.filename()) + + for d in self._getDependentObjects(): + deps = deps.union(d.getDeps(visited)) + + return deps + +class IDLScope(IDLObject): + def __init__(self, location, parentScope, identifier): + IDLObject.__init__(self, location) + + self.parentScope = parentScope + if identifier: + assert isinstance(identifier, IDLIdentifier) + self._name = identifier + else: + self._name = None + + self._dict = {} + self.globalNames = set() + # A mapping from global name to the set of global interfaces + # that have that global name. + self.globalNameMapping = defaultdict(set) + self.primaryGlobalAttr = None + self.primaryGlobalName = None + + def __str__(self): + return self.QName() + + def QName(self): + if self._name: + return self._name.QName() + "::" + return "::" + + def ensureUnique(self, identifier, object): + """ + Ensure that there is at most one 'identifier' in scope ('self'). + Note that object can be None. This occurs if we end up here for an + interface type we haven't seen yet. + """ + assert isinstance(identifier, IDLUnresolvedIdentifier) + assert not object or isinstance(object, IDLObjectWithIdentifier) + assert not object or object.identifier == identifier + + if identifier.name in self._dict: + if not object: + return + + # ensureUnique twice with the same object is not allowed + assert id(object) != id(self._dict[identifier.name]) + + replacement = self.resolveIdentifierConflict(self, identifier, + self._dict[identifier.name], + object) + self._dict[identifier.name] = replacement + return + + assert object + + self._dict[identifier.name] = object + + def resolveIdentifierConflict(self, scope, identifier, originalObject, newObject): + if isinstance(originalObject, IDLExternalInterface) and \ + isinstance(newObject, IDLExternalInterface) and \ + originalObject.identifier.name == newObject.identifier.name: + return originalObject + + if (isinstance(originalObject, IDLExternalInterface) or + isinstance(newObject, IDLExternalInterface)): + raise WebIDLError( + "Name collision between " + "interface declarations for identifier '%s' at '%s' and '%s'" + % (identifier.name, + originalObject.location, newObject.location), []) + + if (isinstance(originalObject, IDLDictionary) or + isinstance(newObject, IDLDictionary)): + raise WebIDLError( + "Name collision between dictionary declarations for " + "identifier '%s'.\n%s\n%s" + % (identifier.name, + originalObject.location, newObject.location), []) + + # We do the merging of overloads here as opposed to in IDLInterface + # because we need to merge overloads of NamedConstructors and we need to + # detect conflicts in those across interfaces. See also the comment in + # IDLInterface.addExtendedAttributes for "NamedConstructor". + if originalObject.tag == IDLInterfaceMember.Tags.Method and \ + newObject.tag == IDLInterfaceMember.Tags.Method: + return originalObject.addOverload(newObject) + + # Default to throwing, derived classes can override. + conflictdesc = "\n\t%s at %s\n\t%s at %s" % \ + (originalObject, originalObject.location, newObject, newObject.location) + + raise WebIDLError( + "Multiple unresolvable definitions of identifier '%s' in scope '%s%s" + % (identifier.name, str(self), conflictdesc), []) + + def _lookupIdentifier(self, identifier): + return self._dict[identifier.name] + + def lookupIdentifier(self, identifier): + assert isinstance(identifier, IDLIdentifier) + assert identifier.scope == self + return self._lookupIdentifier(identifier) + +class IDLIdentifier(IDLObject): + def __init__(self, location, scope, name): + IDLObject.__init__(self, location) + + self.name = name + assert isinstance(scope, IDLScope) + self.scope = scope + + def __str__(self): + return self.QName() + + def QName(self): + return self.scope.QName() + self.name + + def __hash__(self): + return self.QName().__hash__() + + def __eq__(self, other): + return self.QName() == other.QName() + + def object(self): + return self.scope.lookupIdentifier(self) + +class IDLUnresolvedIdentifier(IDLObject): + def __init__(self, location, name, allowDoubleUnderscore = False, + allowForbidden = False): + IDLObject.__init__(self, location) + + assert len(name) > 0 + + if name[:2] == "__" and name != "__content" and name != "___noSuchMethod__" and not allowDoubleUnderscore: + raise WebIDLError("Identifiers beginning with __ are reserved", + [location]) + if name[0] == '_' and not allowDoubleUnderscore: + name = name[1:] + # TODO: Bug 872377, Restore "toJSON" to below list. + # We sometimes need custom serialization, so allow toJSON for now. + if (name in ["constructor", "toString"] and + not allowForbidden): + raise WebIDLError("Cannot use reserved identifier '%s'" % (name), + [location]) + + self.name = name + + def __str__(self): + return self.QName() + + def QName(self): + return "<unresolved scope>::" + self.name + + def resolve(self, scope, object): + assert isinstance(scope, IDLScope) + assert not object or isinstance(object, IDLObjectWithIdentifier) + assert not object or object.identifier == self + + scope.ensureUnique(self, object) + + identifier = IDLIdentifier(self.location, scope, self.name) + if object: + object.identifier = identifier + return identifier + + def finish(self): + assert False # Should replace with a resolved identifier first. + +class IDLObjectWithIdentifier(IDLObject): + def __init__(self, location, parentScope, identifier): + IDLObject.__init__(self, location) + + assert isinstance(identifier, IDLUnresolvedIdentifier) + + self.identifier = identifier + + if parentScope: + self.resolve(parentScope) + + self.treatNullAs = "Default" + + def resolve(self, parentScope): + assert isinstance(parentScope, IDLScope) + assert isinstance(self.identifier, IDLUnresolvedIdentifier) + self.identifier.resolve(parentScope, self) + + def checkForStringHandlingExtendedAttributes(self, attrs, + isDictionaryMember=False, + isOptional=False): + """ + A helper function to deal with TreatNullAs. Returns the list + of attrs it didn't handle itself. + """ + assert isinstance(self, IDLArgument) or isinstance(self, IDLAttribute) + unhandledAttrs = list() + for attr in attrs: + if not attr.hasValue(): + unhandledAttrs.append(attr) + continue + + identifier = attr.identifier() + value = attr.value() + if identifier == "TreatNullAs": + if not self.type.isDOMString() or self.type.nullable(): + raise WebIDLError("[TreatNullAs] is only allowed on " + "arguments or attributes whose type is " + "DOMString", + [self.location]) + if isDictionaryMember: + raise WebIDLError("[TreatNullAs] is not allowed for " + "dictionary members", [self.location]) + if value != 'EmptyString': + raise WebIDLError("[TreatNullAs] must take the identifier " + "'EmptyString', not '%s'" % value, + [self.location]) + self.treatNullAs = value + else: + unhandledAttrs.append(attr) + + return unhandledAttrs + +class IDLObjectWithScope(IDLObjectWithIdentifier, IDLScope): + def __init__(self, location, parentScope, identifier): + assert isinstance(identifier, IDLUnresolvedIdentifier) + + IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier) + IDLScope.__init__(self, location, parentScope, self.identifier) + +class IDLIdentifierPlaceholder(IDLObjectWithIdentifier): + def __init__(self, location, identifier): + assert isinstance(identifier, IDLUnresolvedIdentifier) + IDLObjectWithIdentifier.__init__(self, location, None, identifier) + + def finish(self, scope): + try: + scope._lookupIdentifier(self.identifier) + except: + raise WebIDLError("Unresolved type '%s'." % self.identifier, + [self.location]) + + obj = self.identifier.resolve(scope, None) + return scope.lookupIdentifier(obj) + +class IDLExternalInterface(IDLObjectWithIdentifier): + def __init__(self, location, parentScope, identifier): + raise WebIDLError("Servo does not support external interfaces.", + [self.location]) + +class IDLPartialInterface(IDLObject): + def __init__(self, location, name, members, nonPartialInterface): + assert isinstance(name, IDLUnresolvedIdentifier) + + IDLObject.__init__(self, location) + self.identifier = name + self.members = members + # propagatedExtendedAttrs are the ones that should get + # propagated to our non-partial interface. + self.propagatedExtendedAttrs = [] + self._nonPartialInterface = nonPartialInterface + self._finished = False + nonPartialInterface.addPartialInterface(self) + + def addExtendedAttributes(self, attrs): + for attr in attrs: + identifier = attr.identifier() + + if identifier in ["Constructor", "NamedConstructor"]: + self.propagatedExtendedAttrs.append(attr) + elif identifier == "Exposed": + # This just gets propagated to all our members. + for member in self.members: + if len(member._exposureGlobalNames) != 0: + raise WebIDLError("[Exposed] specified on both a " + "partial interface member and on the " + "partial interface itself", + [member.location, attr.location]) + member.addExtendedAttributes([attr]) + else: + raise WebIDLError("Unknown extended attribute %s on partial " + "interface" % identifier, + [attr.location]) + + def finish(self, scope): + if self._finished: + return + self._finished = True + # Need to make sure our non-partial interface gets finished so it can + # report cases when we only have partial interfaces. + self._nonPartialInterface.finish(scope) + + def validate(self): + pass + + +def convertExposedAttrToGlobalNameSet(exposedAttr, targetSet): + assert len(targetSet) == 0 + if exposedAttr.hasValue(): + targetSet.add(exposedAttr.value()) + else: + assert exposedAttr.hasArgs() + targetSet.update(exposedAttr.args()) + +def globalNameSetToExposureSet(globalScope, nameSet, exposureSet): + for name in nameSet: + exposureSet.update(globalScope.globalNameMapping[name]) + +class IDLInterface(IDLObjectWithScope): + def __init__(self, location, parentScope, name, parent, members, + isKnownNonPartial): + assert isinstance(parentScope, IDLScope) + assert isinstance(name, IDLUnresolvedIdentifier) + assert isKnownNonPartial or not parent + assert isKnownNonPartial or len(members) == 0 + + self.parent = None + self._callback = False + self._finished = False + self.members = [] + self._partialInterfaces = [] + self._extendedAttrDict = {} + # namedConstructors needs deterministic ordering because bindings code + # outputs the constructs in the order that namedConstructors enumerates + # them. + self.namedConstructors = list() + self.implementedInterfaces = set() + self._consequential = False + self._isKnownNonPartial = False + # self.interfacesBasedOnSelf is the set of interfaces that inherit from + # self or have self as a consequential interface, including self itself. + # Used for distinguishability checking. + self.interfacesBasedOnSelf = set([self]) + # self.interfacesImplementingSelf is the set of interfaces that directly + # have self as a consequential interface + self.interfacesImplementingSelf = set() + self._hasChildInterfaces = False + self._isOnGlobalProtoChain = False + # Tracking of the number of reserved slots we need for our + # members and those of ancestor interfaces. + self.totalMembersInSlots = 0 + # Tracking of the number of own own members we have in slots + self._ownMembersInSlots = 0 + # _exposureGlobalNames are the global names listed in our [Exposed] + # extended attribute. exposureSet is the exposure set as defined in the + # Web IDL spec: it contains interface names. + self._exposureGlobalNames = set() + self.exposureSet = set() + + IDLObjectWithScope.__init__(self, location, parentScope, name) + + if isKnownNonPartial: + self.setNonPartial(location, parent, members) + + def __str__(self): + return "Interface '%s'" % self.identifier.name + + def ctor(self): + identifier = IDLUnresolvedIdentifier(self.location, "constructor", + allowForbidden=True) + try: + return self._lookupIdentifier(identifier) + except: + return None + + def resolveIdentifierConflict(self, scope, identifier, originalObject, newObject): + assert isinstance(scope, IDLScope) + assert isinstance(originalObject, IDLInterfaceMember) + assert isinstance(newObject, IDLInterfaceMember) + + retval = IDLScope.resolveIdentifierConflict(self, scope, identifier, + originalObject, newObject) + + # Might be a ctor, which isn't in self.members + if newObject in self.members: + self.members.remove(newObject) + return retval + + def finish(self, scope): + if self._finished: + return + + self._finished = True + + if not self._isKnownNonPartial: + raise WebIDLError("Interface %s does not have a non-partial " + "declaration" % self.identifier.name, + [self.location]) + + # Verify that our [Exposed] value, if any, makes sense. + for globalName in self._exposureGlobalNames: + if globalName not in scope.globalNames: + raise WebIDLError("Unknown [Exposed] value %s" % globalName, + [self.location]) + + if len(self._exposureGlobalNames) == 0: + self._exposureGlobalNames.add(scope.primaryGlobalName) + + globalNameSetToExposureSet(scope, self._exposureGlobalNames, + self.exposureSet) + + # Now go ahead and merge in our partial interfaces. + for partial in self._partialInterfaces: + partial.finish(scope) + self.addExtendedAttributes(partial.propagatedExtendedAttrs) + self.members.extend(partial.members) + + # Now that we've merged in our partial interfaces, set the + # _exposureGlobalNames on any members that don't have it set yet. Note + # that any partial interfaces that had [Exposed] set have already set up + # _exposureGlobalNames on all the members coming from them, so this is + # just implementing the "members default to interface that defined them" + # and "partial interfaces default to interface they're a partial for" + # rules from the spec. + for m in self.members: + # If m, or the partial interface m came from, had [Exposed] + # specified, it already has a nonempty exposure global names set. + if len(m._exposureGlobalNames) == 0: + m._exposureGlobalNames.update(self._exposureGlobalNames) + + assert not self.parent or isinstance(self.parent, IDLIdentifierPlaceholder) + parent = self.parent.finish(scope) if self.parent else None + if parent and isinstance(parent, IDLExternalInterface): + raise WebIDLError("%s inherits from %s which does not have " + "a definition" % + (self.identifier.name, + self.parent.identifier.name), + [self.location]) + assert not parent or isinstance(parent, IDLInterface) + + self.parent = parent + + assert iter(self.members) + + if self.parent: + self.parent.finish(scope) + + self.parent._hasChildInterfaces = True + + self.totalMembersInSlots = self.parent.totalMembersInSlots + + # Interfaces with [Global] or [PrimaryGlobal] must not + # have anything inherit from them + if (self.parent.getExtendedAttribute("Global") or + self.parent.getExtendedAttribute("PrimaryGlobal")): + # Note: This is not a self.parent.isOnGlobalProtoChain() check + # because ancestors of a [Global] interface can have other + # descendants. + raise WebIDLError("[Global] interface has another interface " + "inheriting from it", + [self.location, self.parent.location]) + + # Make sure that we're not exposed in places where our parent is not + if not self.exposureSet.issubset(self.parent.exposureSet): + raise WebIDLError("Interface %s is exposed in globals where its " + "parent interface %s is not exposed." % + (self.identifier.name, + self.parent.identifier.name), + [self.location, self.parent.location]) + + # Callbacks must not inherit from non-callbacks or inherit from + # anything that has consequential interfaces. + # XXXbz Can non-callbacks inherit from callbacks? Spec issue pending. + # XXXbz Can callbacks have consequential interfaces? Spec issue pending + if self.isCallback(): + if not self.parent.isCallback(): + raise WebIDLError("Callback interface %s inheriting from " + "non-callback interface %s" % + (self.identifier.name, + self.parent.identifier.name), + [self.location, self.parent.location]) + elif self.parent.isCallback(): + raise WebIDLError("Non-callback interface %s inheriting from " + "callback interface %s" % + (self.identifier.name, + self.parent.identifier.name), + [self.location, self.parent.location]) + + for iface in self.implementedInterfaces: + iface.finish(scope) + + cycleInGraph = self.findInterfaceLoopPoint(self) + if cycleInGraph: + raise WebIDLError("Interface %s has itself as ancestor or " + "implemented interface" % self.identifier.name, + [self.location, cycleInGraph.location]) + + if self.isCallback(): + # "implements" should have made sure we have no + # consequential interfaces. + assert len(self.getConsequentialInterfaces()) == 0 + # And that we're not consequential. + assert not self.isConsequential() + + # Now resolve() and finish() our members before importing the + # ones from our implemented interfaces. + + # resolve() will modify self.members, so we need to iterate + # over a copy of the member list here. + for member in list(self.members): + member.resolve(self) + + for member in self.members: + member.finish(scope) + + # Now that we've finished our members, which has updated their exposure + # sets, make sure they aren't exposed in places where we are not. + for member in self.members: + if not member.exposureSet.issubset(self.exposureSet): + raise WebIDLError("Interface member has larger exposure set " + "than the interface itself", + [member.location, self.location]) + + ctor = self.ctor() + if ctor is not None: + ctor.finish(scope) + + for ctor in self.namedConstructors: + ctor.finish(scope) + + # Make a copy of our member list, so things that implement us + # can get those without all the stuff we implement ourselves + # admixed. + self.originalMembers = list(self.members) + + # Import everything from our consequential interfaces into + # self.members. Sort our consequential interfaces by name + # just so we have a consistent order. + for iface in sorted(self.getConsequentialInterfaces(), + cmp=cmp, + key=lambda x: x.identifier.name): + # Flag the interface as being someone's consequential interface + iface.setIsConsequentialInterfaceOf(self) + # Verify that we're not exposed somewhere where iface is not exposed + if not self.exposureSet.issubset(iface.exposureSet): + raise WebIDLError("Interface %s is exposed in globals where its " + "consequential interface %s is not exposed." % + (self.identifier.name, iface.identifier.name), + [self.location, iface.location]) + additionalMembers = iface.originalMembers; + for additionalMember in additionalMembers: + for member in self.members: + if additionalMember.identifier.name == member.identifier.name: + raise WebIDLError( + "Multiple definitions of %s on %s coming from 'implements' statements" % + (member.identifier.name, self), + [additionalMember.location, member.location]) + self.members.extend(additionalMembers) + iface.interfacesImplementingSelf.add(self) + + for ancestor in self.getInheritedInterfaces(): + ancestor.interfacesBasedOnSelf.add(self) + for ancestorConsequential in ancestor.getConsequentialInterfaces(): + ancestorConsequential.interfacesBasedOnSelf.add(self) + + # Deal with interfaces marked [Unforgeable], now that we have our full + # member list, except unforgeables pulled in from parents. We want to + # do this before we set "originatingInterface" on our unforgeable + # members. + if self.getExtendedAttribute("Unforgeable"): + # Check that the interface already has all the things the + # spec would otherwise require us to synthesize and is + # missing the ones we plan to synthesize. + if not any(m.isMethod() and m.isStringifier() for m in self.members): + raise WebIDLError("Unforgeable interface %s does not have a " + "stringifier" % self.identifier.name, + [self.location]) + + for m in self.members: + if ((m.isMethod() and m.isJsonifier()) or + m.identifier.name == "toJSON"): + raise WebIDLError("Unforgeable interface %s has a " + "jsonifier so we won't be able to add " + "one ourselves" % self.identifier.name, + [self.location, m.location]) + + if m.identifier.name == "valueOf" and not m.isStatic(): + raise WebIDLError("Unforgeable interface %s has a valueOf " + "member so we won't be able to add one " + "ourselves" % self.identifier.name, + [self.location, m.location]) + + for member in self.members: + if ((member.isAttr() or member.isMethod()) and + member.isUnforgeable() and + not hasattr(member, "originatingInterface")): + member.originatingInterface = self + + # Compute slot indices for our members before we pull in + # unforgeable members from our parent. + for member in self.members: + if (member.isAttr() and + (member.getExtendedAttribute("StoreInSlot") or + member.getExtendedAttribute("Cached"))): + member.slotIndex = self.totalMembersInSlots + self.totalMembersInSlots += 1 + if member.getExtendedAttribute("StoreInSlot"): + self._ownMembersInSlots += 1 + + if self.parent: + # Make sure we don't shadow any of the [Unforgeable] attributes on + # our ancestor interfaces. We don't have to worry about + # consequential interfaces here, because those have already been + # imported into the relevant .members lists. And we don't have to + # worry about anything other than our parent, because it has already + # imported its ancestors unforgeable attributes into its member + # list. + for unforgeableMember in (member for member in self.parent.members if + (member.isAttr() or member.isMethod()) and + member.isUnforgeable()): + shadows = [ m for m in self.members if + (m.isAttr() or m.isMethod()) and + not m.isStatic() and + m.identifier.name == unforgeableMember.identifier.name ] + if len(shadows) != 0: + locs = [unforgeableMember.location] + [ s.location for s + in shadows ] + raise WebIDLError("Interface %s shadows [Unforgeable] " + "members of %s" % + (self.identifier.name, + ancestor.identifier.name), + locs) + # And now just stick it in our members, since we won't be + # inheriting this down the proto chain. If we really cared we + # could try to do something where we set up the unforgeable + # attributes/methods of ancestor interfaces, with their + # corresponding getters, on our interface, but that gets pretty + # complicated and seems unnecessary. + self.members.append(unforgeableMember) + + # Ensure that there's at most one of each {named,indexed} + # {getter,setter,creator,deleter}, at most one stringifier, + # and at most one legacycaller. Note that this last is not + # quite per spec, but in practice no one overloads + # legacycallers. + specialMembersSeen = {} + for member in self.members: + if not member.isMethod(): + continue + + if member.isGetter(): + memberType = "getters" + elif member.isSetter(): + memberType = "setters" + elif member.isCreator(): + memberType = "creators" + elif member.isDeleter(): + memberType = "deleters" + elif member.isStringifier(): + memberType = "stringifiers" + elif member.isJsonifier(): + memberType = "jsonifiers" + elif member.isLegacycaller(): + memberType = "legacycallers" + else: + continue + + if (memberType != "stringifiers" and memberType != "legacycallers" and + memberType != "jsonifiers"): + if member.isNamed(): + memberType = "named " + memberType + else: + assert member.isIndexed() + memberType = "indexed " + memberType + + if memberType in specialMembersSeen: + raise WebIDLError("Multiple " + memberType + " on %s" % (self), + [self.location, + specialMembersSeen[memberType].location, + member.location]) + + specialMembersSeen[memberType] = member + + if self._isOnGlobalProtoChain: + # Make sure we have no named setters, creators, or deleters + for memberType in ["setter", "creator", "deleter"]: + memberId = "named " + memberType + "s" + if memberId in specialMembersSeen: + raise WebIDLError("Interface with [Global] has a named %s" % + memberType, + [self.location, + specialMembersSeen[memberId].location]) + # Make sure we're not [OverrideBuiltins] + if self.getExtendedAttribute("OverrideBuiltins"): + raise WebIDLError("Interface with [Global] also has " + "[OverrideBuiltins]", + [self.location]) + # Mark all of our ancestors as being on the global's proto chain too + parent = self.parent + while parent: + # Must not inherit from an interface with [OverrideBuiltins] + if parent.getExtendedAttribute("OverrideBuiltins"): + raise WebIDLError("Interface with [Global] inherits from " + "interface with [OverrideBuiltins]", + [self.location, parent.location]) + parent._isOnGlobalProtoChain = True + parent = parent.parent + + def validate(self): + # We don't support consequential unforgeable interfaces. Need to check + # this here, becaue in finish() an interface might not know yet that + # it's consequential. + if self.getExtendedAttribute("Unforgeable") and self.isConsequential(): + raise WebIDLError( + "%s is an unforgeable consequential interface" % + self.identifier.name, + [self.location] + + list(i.location for i in + (self.interfacesBasedOnSelf - { self }) )) + + # We also don't support inheriting from unforgeable interfaces. + if self.getExtendedAttribute("Unforgeable") and self.hasChildInterfaces(): + raise WebIDLError("%s is an unforgeable ancestor interface" % + self.identifier.name, + [self.location] + + list(i.location for i in + self.interfacesBasedOnSelf if i.parent == self)) + + + for member in self.members: + member.validate() + + # Check that PutForwards refers to another attribute and that no + # cycles exist in forwarded assignments. + if member.isAttr(): + iface = self + attr = member + putForwards = attr.getExtendedAttribute("PutForwards") + if putForwards and self.isCallback(): + raise WebIDLError("[PutForwards] used on an attribute " + "on interface %s which is a callback " + "interface" % self.identifier.name, + [self.location, member.location]) + + while putForwards is not None: + forwardIface = attr.type.unroll().inner + fowardAttr = None + + for forwardedMember in forwardIface.members: + if (not forwardedMember.isAttr() or + forwardedMember.identifier.name != putForwards[0]): + continue + if forwardedMember == member: + raise WebIDLError("Cycle detected in forwarded " + "assignments for attribute %s on " + "%s" % + (member.identifier.name, self), + [member.location]) + fowardAttr = forwardedMember + break + + if fowardAttr is None: + raise WebIDLError("Attribute %s on %s forwards to " + "missing attribute %s" % + (attr.identifier.name, iface, putForwards), + [attr.location]) + + iface = forwardIface + attr = fowardAttr + putForwards = attr.getExtendedAttribute("PutForwards") + + if (self.getExtendedAttribute("Pref") and + self._exposureGlobalNames != set([self.parentScope.primaryGlobalName])): + raise WebIDLError("[Pref] used on an member that is not %s-only" % + self.parentScope.primaryGlobalName, + [self.location]) + + + def isInterface(self): + return True + + def isExternal(self): + return False + + def setIsConsequentialInterfaceOf(self, other): + self._consequential = True + self.interfacesBasedOnSelf.add(other) + + def isConsequential(self): + return self._consequential + + def setCallback(self, value): + self._callback = value + + def isCallback(self): + return self._callback + + def isSingleOperationInterface(self): + assert self.isCallback() or self.isJSImplemented() + return ( + # JS-implemented things should never need the + # this-handling weirdness of single-operation interfaces. + not self.isJSImplemented() and + # Not inheriting from another interface + not self.parent and + # No consequential interfaces + len(self.getConsequentialInterfaces()) == 0 and + # No attributes of any kinds + not any(m.isAttr() for m in self.members) and + # There is at least one regular operation, and all regular + # operations have the same identifier + len(set(m.identifier.name for m in self.members if + m.isMethod() and not m.isStatic())) == 1) + + def inheritanceDepth(self): + depth = 0 + parent = self.parent + while parent: + depth = depth + 1 + parent = parent.parent + return depth + + def hasConstants(self): + return any(m.isConst() for m in self.members) + + def hasInterfaceObject(self): + if self.isCallback(): + return self.hasConstants() + return not hasattr(self, "_noInterfaceObject") + + def hasInterfacePrototypeObject(self): + return not self.isCallback() and self.getUserData('hasConcreteDescendant', False) + + def addExtendedAttributes(self, attrs): + for attr in attrs: + identifier = attr.identifier() + + # Special cased attrs + if identifier == "TreatNonCallableAsNull": + raise WebIDLError("TreatNonCallableAsNull cannot be specified on interfaces", + [attr.location, self.location]) + if identifier == "TreatNonObjectAsNull": + raise WebIDLError("TreatNonObjectAsNull cannot be specified on interfaces", + [attr.location, self.location]) + elif identifier == "NoInterfaceObject": + if not attr.noArguments(): + raise WebIDLError("[NoInterfaceObject] must take no arguments", + [attr.location]) + + if self.ctor(): + raise WebIDLError("Constructor and NoInterfaceObject are incompatible", + [self.location]) + + self._noInterfaceObject = True + elif identifier == "Constructor" or identifier == "NamedConstructor" or identifier == "ChromeConstructor": + if identifier == "Constructor" and not self.hasInterfaceObject(): + raise WebIDLError(str(identifier) + " and NoInterfaceObject are incompatible", + [self.location]) + + if identifier == "NamedConstructor" and not attr.hasValue(): + raise WebIDLError("NamedConstructor must either take an identifier or take a named argument list", + [attr.location]) + + if identifier == "ChromeConstructor" and not self.hasInterfaceObject(): + raise WebIDLError(str(identifier) + " and NoInterfaceObject are incompatible", + [self.location]) + + args = attr.args() if attr.hasArgs() else [] + + retType = IDLWrapperType(self.location, self) + + if identifier == "Constructor" or identifier == "ChromeConstructor": + name = "constructor" + allowForbidden = True + else: + name = attr.value() + allowForbidden = False + + methodIdentifier = IDLUnresolvedIdentifier(self.location, name, + allowForbidden=allowForbidden) + + method = IDLMethod(self.location, methodIdentifier, retType, + args, static=True) + # Constructors are always NewObject and are always + # assumed to be able to throw (since there's no way to + # indicate otherwise) and never have any other + # extended attributes. + method.addExtendedAttributes( + [IDLExtendedAttribute(self.location, ("NewObject",)), + IDLExtendedAttribute(self.location, ("Throws",))]) + if identifier == "ChromeConstructor": + method.addExtendedAttributes( + [IDLExtendedAttribute(self.location, ("ChromeOnly",))]) + + if identifier == "Constructor" or identifier == "ChromeConstructor": + method.resolve(self) + else: + # We need to detect conflicts for NamedConstructors across + # interfaces. We first call resolve on the parentScope, + # which will merge all NamedConstructors with the same + # identifier accross interfaces as overloads. + method.resolve(self.parentScope) + + # Then we look up the identifier on the parentScope. If the + # result is the same as the method we're adding then it + # hasn't been added as an overload and it's the first time + # we've encountered a NamedConstructor with that identifier. + # If the result is not the same as the method we're adding + # then it has been added as an overload and we need to check + # whether the result is actually one of our existing + # NamedConstructors. + newMethod = self.parentScope.lookupIdentifier(method.identifier) + if newMethod == method: + self.namedConstructors.append(method) + elif not newMethod in self.namedConstructors: + raise WebIDLError("NamedConstructor conflicts with a NamedConstructor of a different interface", + [method.location, newMethod.location]) + elif (identifier == "ArrayClass"): + if not attr.noArguments(): + raise WebIDLError("[ArrayClass] must take no arguments", + [attr.location]) + if self.parent: + raise WebIDLError("[ArrayClass] must not be specified on " + "an interface with inherited interfaces", + [attr.location, self.location]) + elif (identifier == "ExceptionClass"): + if not attr.noArguments(): + raise WebIDLError("[ExceptionClass] must take no arguments", + [attr.location]) + if self.parent: + raise WebIDLError("[ExceptionClass] must not be specified on " + "an interface with inherited interfaces", + [attr.location, self.location]) + elif identifier == "Global": + if attr.hasValue(): + self.globalNames = [ attr.value() ] + elif attr.hasArgs(): + self.globalNames = attr.args() + else: + self.globalNames = [ self.identifier.name ] + self.parentScope.globalNames.update(self.globalNames) + for globalName in self.globalNames: + self.parentScope.globalNameMapping[globalName].add(self.identifier.name) + self._isOnGlobalProtoChain = True + elif identifier == "PrimaryGlobal": + if not attr.noArguments(): + raise WebIDLError("[PrimaryGlobal] must take no arguments", + [attr.location]) + if self.parentScope.primaryGlobalAttr is not None: + raise WebIDLError( + "[PrimaryGlobal] specified twice", + [attr.location, + self.parentScope.primaryGlobalAttr.location]) + self.parentScope.primaryGlobalAttr = attr + self.parentScope.primaryGlobalName = self.identifier.name + self.parentScope.globalNames.add(self.identifier.name) + self.parentScope.globalNameMapping[self.identifier.name].add(self.identifier.name) + self._isOnGlobalProtoChain = True + elif (identifier == "NeedNewResolve" or + identifier == "OverrideBuiltins" or + identifier == "ChromeOnly" or + identifier == "Unforgeable" or + identifier == "LegacyEventInit"): + # Known extended attributes that do not take values + if not attr.noArguments(): + raise WebIDLError("[%s] must take no arguments" % identifier, + [attr.location]) + elif identifier == "Exposed": + convertExposedAttrToGlobalNameSet(attr, + self._exposureGlobalNames) + elif (identifier == "Pref" or + identifier == "JSImplementation" or + identifier == "HeaderFile" or + identifier == "NavigatorProperty" or + identifier == "AvailableIn" or + identifier == "Func" or + identifier == "CheckPermissions"): + # Known extended attributes that take a string value + if not attr.hasValue(): + raise WebIDLError("[%s] must have a value" % identifier, + [attr.location]) + else: + raise WebIDLError("Unknown extended attribute %s on interface" % identifier, + [attr.location]) + + attrlist = attr.listValue() + self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True + + def addImplementedInterface(self, implementedInterface): + assert(isinstance(implementedInterface, IDLInterface)) + self.implementedInterfaces.add(implementedInterface) + + def getInheritedInterfaces(self): + """ + Returns a list of the interfaces this interface inherits from + (not including this interface itself). The list is in order + from most derived to least derived. + """ + assert(self._finished) + if not self.parent: + return [] + parentInterfaces = self.parent.getInheritedInterfaces() + parentInterfaces.insert(0, self.parent) + return parentInterfaces + + def getConsequentialInterfaces(self): + assert(self._finished) + # The interfaces we implement directly + consequentialInterfaces = set(self.implementedInterfaces) + + # And their inherited interfaces + for iface in self.implementedInterfaces: + consequentialInterfaces |= set(iface.getInheritedInterfaces()) + + # And now collect up the consequential interfaces of all of those + temp = set() + for iface in consequentialInterfaces: + temp |= iface.getConsequentialInterfaces() + + return consequentialInterfaces | temp + + def findInterfaceLoopPoint(self, otherInterface): + """ + Finds an interface, amongst our ancestors and consequential interfaces, + that inherits from otherInterface or implements otherInterface + directly. If there is no such interface, returns None. + """ + if self.parent: + if self.parent == otherInterface: + return self + loopPoint = self.parent.findInterfaceLoopPoint(otherInterface) + if loopPoint: + return loopPoint + if otherInterface in self.implementedInterfaces: + return self + for iface in self.implementedInterfaces: + loopPoint = iface.findInterfaceLoopPoint(otherInterface) + if loopPoint: + return loopPoint + return None + + def getExtendedAttribute(self, name): + return self._extendedAttrDict.get(name, None) + + def setNonPartial(self, location, parent, members): + assert not parent or isinstance(parent, IDLIdentifierPlaceholder) + if self._isKnownNonPartial: + raise WebIDLError("Two non-partial definitions for the " + "same interface", + [location, self.location]) + self._isKnownNonPartial = True + # Now make it look like we were parsed at this new location, since + # that's the place where the interface is "really" defined + self.location = location + assert not self.parent + self.parent = parent + # Put the new members at the beginning + self.members = members + self.members + + def addPartialInterface(self, partial): + assert self.identifier.name == partial.identifier.name + self._partialInterfaces.append(partial) + + def getJSImplementation(self): + classId = self.getExtendedAttribute("JSImplementation") + if not classId: + return classId + assert isinstance(classId, list) + assert len(classId) == 1 + return classId[0] + + def isJSImplemented(self): + return bool(self.getJSImplementation()) + + def getNavigatorProperty(self): + naviProp = self.getExtendedAttribute("NavigatorProperty") + if not naviProp: + return None + assert len(naviProp) == 1 + assert isinstance(naviProp, list) + assert len(naviProp[0]) != 0 + return naviProp[0] + + def hasChildInterfaces(self): + return self._hasChildInterfaces + + def isOnGlobalProtoChain(self): + return self._isOnGlobalProtoChain + + def _getDependentObjects(self): + deps = set(self.members) + deps.union(self.implementedInterfaces) + if self.parent: + deps.add(self.parent) + return deps + + def hasMembersInSlots(self): + return self._ownMembersInSlots != 0 + +class IDLDictionary(IDLObjectWithScope): + def __init__(self, location, parentScope, name, parent, members): + assert isinstance(parentScope, IDLScope) + assert isinstance(name, IDLUnresolvedIdentifier) + assert not parent or isinstance(parent, IDLIdentifierPlaceholder) + + self.parent = parent + self._finished = False + self.members = list(members) + + IDLObjectWithScope.__init__(self, location, parentScope, name) + + def __str__(self): + return "Dictionary '%s'" % self.identifier.name + + def isDictionary(self): + return True; + + def finish(self, scope): + if self._finished: + return + + self._finished = True + + if self.parent: + assert isinstance(self.parent, IDLIdentifierPlaceholder) + oldParent = self.parent + self.parent = self.parent.finish(scope) + if not isinstance(self.parent, IDLDictionary): + raise WebIDLError("Dictionary %s has parent that is not a dictionary" % + self.identifier.name, + [oldParent.location, self.parent.location]) + + # Make sure the parent resolves all its members before we start + # looking at them. + self.parent.finish(scope) + + for member in self.members: + member.resolve(self) + if not member.isComplete(): + member.complete(scope) + assert member.type.isComplete() + + # Members of a dictionary are sorted in lexicographic order + self.members.sort(cmp=cmp, key=lambda x: x.identifier.name) + + inheritedMembers = [] + ancestor = self.parent + while ancestor: + if ancestor == self: + raise WebIDLError("Dictionary %s has itself as an ancestor" % + self.identifier.name, + [self.identifier.location]) + inheritedMembers.extend(ancestor.members) + ancestor = ancestor.parent + + # Catch name duplication + for inheritedMember in inheritedMembers: + for member in self.members: + if member.identifier.name == inheritedMember.identifier.name: + raise WebIDLError("Dictionary %s has two members with name %s" % + (self.identifier.name, member.identifier.name), + [member.location, inheritedMember.location]) + + def validate(self): + def typeContainsDictionary(memberType, dictionary): + """ + Returns a tuple whose: + + - First element is a Boolean value indicating whether + memberType contains dictionary. + + - Second element is: + A list of locations that leads from the type that was passed in + the memberType argument, to the dictionary being validated, + if the boolean value in the first element is True. + + None, if the boolean value in the first element is False. + """ + + if (memberType.nullable() or + memberType.isArray() or + memberType.isSequence() or + memberType.isMozMap()): + return typeContainsDictionary(memberType.inner, dictionary) + + if memberType.isDictionary(): + if memberType.inner == dictionary: + return (True, [memberType.location]) + + (contains, locations) = dictionaryContainsDictionary(memberType.inner, \ + dictionary) + if contains: + return (True, [memberType.location] + locations) + + if memberType.isUnion(): + for member in memberType.flatMemberTypes: + (contains, locations) = typeContainsDictionary(member, dictionary) + if contains: + return (True, locations) + + return (False, None) + + def dictionaryContainsDictionary(dictMember, dictionary): + for member in dictMember.members: + (contains, locations) = typeContainsDictionary(member.type, dictionary) + if contains: + return (True, [member.location] + locations) + + if dictMember.parent: + if dictMember.parent == dictionary: + return (True, [dictMember.location]) + else: + (contains, locations) = dictionaryContainsDictionary(dictMember.parent, dictionary) + if contains: + return (True, [dictMember.location] + locations) + + return (False, None) + + for member in self.members: + if member.type.isDictionary() and member.type.nullable(): + raise WebIDLError("Dictionary %s has member with nullable " + "dictionary type" % self.identifier.name, + [member.location]) + (contains, locations) = typeContainsDictionary(member.type, self) + if contains: + raise WebIDLError("Dictionary %s has member with itself as type." % + self.identifier.name, + [member.location] + locations) + + def addExtendedAttributes(self, attrs): + assert len(attrs) == 0 + + def _getDependentObjects(self): + deps = set(self.members) + if (self.parent): + deps.add(self.parent) + return deps + +class IDLEnum(IDLObjectWithIdentifier): + def __init__(self, location, parentScope, name, values): + assert isinstance(parentScope, IDLScope) + assert isinstance(name, IDLUnresolvedIdentifier) + + if len(values) != len(set(values)): + raise WebIDLError("Enum %s has multiple identical strings" % name.name, + [location]) + + IDLObjectWithIdentifier.__init__(self, location, parentScope, name) + self._values = values + + def values(self): + return self._values + + def finish(self, scope): + pass + + def validate(self): + pass + + def isEnum(self): + return True + + def addExtendedAttributes(self, attrs): + assert len(attrs) == 0 + + def _getDependentObjects(self): + return set() + +class IDLType(IDLObject): + Tags = enum( + # The integer types + 'int8', + 'uint8', + 'int16', + 'uint16', + 'int32', + 'uint32', + 'int64', + 'uint64', + # Additional primitive types + 'bool', + 'unrestricted_float', + 'float', + 'unrestricted_double', + # "double" last primitive type to match IDLBuiltinType + 'double', + # Other types + 'any', + 'domstring', + 'bytestring', + 'scalarvaluestring', + 'object', + 'date', + 'void', + # Funny stuff + 'interface', + 'dictionary', + 'enum', + 'callback', + 'union', + 'sequence', + 'mozmap', + 'array' + ) + + def __init__(self, location, name): + IDLObject.__init__(self, location) + self.name = name + self.builtin = False + + def __eq__(self, other): + return other and self.builtin == other.builtin and self.name == other.name + + def __ne__(self, other): + return not self == other + + def __str__(self): + return str(self.name) + + def isType(self): + return True + + def nullable(self): + return False + + def isPrimitive(self): + return False + + def isBoolean(self): + return False + + def isNumeric(self): + return False + + def isString(self): + return False + + def isByteString(self): + return False + + def isDOMString(self): + return False + + def isScalarValueString(self): + return False + + def isVoid(self): + return self.name == "Void" + + def isSequence(self): + return False + + def isMozMap(self): + return False + + def isArray(self): + return False + + def isArrayBuffer(self): + return False + + def isArrayBufferView(self): + return False + + def isTypedArray(self): + return False + + def isCallbackInterface(self): + return False + + def isNonCallbackInterface(self): + return False + + def isGeckoInterface(self): + """ Returns a boolean indicating whether this type is an 'interface' + type that is implemented in Gecko. At the moment, this returns + true for all interface types that are not types from the TypedArray + spec.""" + return self.isInterface() and not self.isSpiderMonkeyInterface() + + def isSpiderMonkeyInterface(self): + """ Returns a boolean indicating whether this type is an 'interface' + type that is implemented in Spidermonkey. At the moment, this + only returns true for the types from the TypedArray spec. """ + return self.isInterface() and (self.isArrayBuffer() or \ + self.isArrayBufferView() or \ + self.isTypedArray()) + + def isDictionary(self): + return False + + def isInterface(self): + return False + + def isAny(self): + return self.tag() == IDLType.Tags.any + + def isDate(self): + return self.tag() == IDLType.Tags.date + + def isObject(self): + return self.tag() == IDLType.Tags.object + + def isPromise(self): + return False + + def isComplete(self): + return True + + def includesRestrictedFloat(self): + return False + + def isFloat(self): + return False + + def isUnrestricted(self): + # Should only call this on float types + assert self.isFloat() + + def isSerializable(self): + return False + + def tag(self): + assert False # Override me! + + def treatNonCallableAsNull(self): + assert self.tag() == IDLType.Tags.callback + return self.nullable() and self.inner._treatNonCallableAsNull + + def treatNonObjectAsNull(self): + assert self.tag() == IDLType.Tags.callback + return self.nullable() and self.inner._treatNonObjectAsNull + + def addExtendedAttributes(self, attrs): + assert len(attrs) == 0 + + def resolveType(self, parentScope): + pass + + def unroll(self): + return self + + def isDistinguishableFrom(self, other): + raise TypeError("Can't tell whether a generic type is or is not " + "distinguishable from other things") + + def isExposedInAllOf(self, exposureSet): + return True + +class IDLUnresolvedType(IDLType): + """ + Unresolved types are interface types + """ + + def __init__(self, location, name, promiseInnerType=None): + IDLType.__init__(self, location, name) + self._promiseInnerType = promiseInnerType + + def isComplete(self): + return False + + def complete(self, scope): + obj = None + try: + obj = scope._lookupIdentifier(self.name) + except: + raise WebIDLError("Unresolved type '%s'." % self.name, + [self.location]) + + assert obj + if obj.isType(): + # obj itself might not be complete; deal with that. + assert obj != self + if not obj.isComplete(): + obj = obj.complete(scope) + return obj + + if self._promiseInnerType and not self._promiseInnerType.isComplete(): + self._promiseInnerType = self._promiseInnerType.complete(scope) + + name = self.name.resolve(scope, None) + return IDLWrapperType(self.location, obj, self._promiseInnerType) + + def isDistinguishableFrom(self, other): + raise TypeError("Can't tell whether an unresolved type is or is not " + "distinguishable from other things") + +class IDLNullableType(IDLType): + def __init__(self, location, innerType): + assert not innerType.isVoid() + assert not innerType == BuiltinTypes[IDLBuiltinType.Types.any] + + IDLType.__init__(self, location, innerType.name) + self.inner = innerType + self.builtin = False + + def __eq__(self, other): + return isinstance(other, IDLNullableType) and self.inner == other.inner + + def __str__(self): + return self.inner.__str__() + "OrNull" + + def nullable(self): + return True + + def isCallback(self): + return self.inner.isCallback() + + def isPrimitive(self): + return self.inner.isPrimitive() + + def isBoolean(self): + return self.inner.isBoolean() + + def isNumeric(self): + return self.inner.isNumeric() + + def isString(self): + return self.inner.isString() + + def isByteString(self): + return self.inner.isByteString() + + def isDOMString(self): + return self.inner.isDOMString() + + def isScalarValueString(self): + return self.inner.isScalarValueString() + + def isFloat(self): + return self.inner.isFloat() + + def isUnrestricted(self): + return self.inner.isUnrestricted() + + def includesRestrictedFloat(self): + return self.inner.includesRestrictedFloat() + + def isInteger(self): + return self.inner.isInteger() + + def isVoid(self): + return False + + def isSequence(self): + return self.inner.isSequence() + + def isMozMap(self): + return self.inner.isMozMap() + + def isArray(self): + return self.inner.isArray() + + def isArrayBuffer(self): + return self.inner.isArrayBuffer() + + def isArrayBufferView(self): + return self.inner.isArrayBufferView() + + def isTypedArray(self): + return self.inner.isTypedArray() + + def isDictionary(self): + return self.inner.isDictionary() + + def isInterface(self): + return self.inner.isInterface() + + def isCallbackInterface(self): + return self.inner.isCallbackInterface() + + def isNonCallbackInterface(self): + return self.inner.isNonCallbackInterface() + + def isEnum(self): + return self.inner.isEnum() + + def isUnion(self): + return self.inner.isUnion() + + def isSerializable(self): + return self.inner.isSerializable() + + def tag(self): + return self.inner.tag() + + def resolveType(self, parentScope): + assert isinstance(parentScope, IDLScope) + self.inner.resolveType(parentScope) + + def isComplete(self): + return self.inner.isComplete() + + def complete(self, scope): + self.inner = self.inner.complete(scope) + if self.inner.nullable(): + raise WebIDLError("The inner type of a nullable type must not be " + "a nullable type", + [self.location, self.inner.location]) + if self.inner.isUnion(): + if self.inner.hasNullableType: + raise WebIDLError("The inner type of a nullable type must not " + "be a union type that itself has a nullable " + "type as a member type", [self.location]) + + self.name = self.inner.name + return self + + def unroll(self): + return self.inner.unroll() + + def isDistinguishableFrom(self, other): + if (other.nullable() or (other.isUnion() and other.hasNullableType) or + other.isDictionary()): + # Can't tell which type null should become + return False + return self.inner.isDistinguishableFrom(other) + + def _getDependentObjects(self): + return self.inner._getDependentObjects() + +class IDLSequenceType(IDLType): + def __init__(self, location, parameterType): + assert not parameterType.isVoid() + + IDLType.__init__(self, location, parameterType.name) + self.inner = parameterType + self.builtin = False + + def __eq__(self, other): + return isinstance(other, IDLSequenceType) and self.inner == other.inner + + def __str__(self): + return self.inner.__str__() + "Sequence" + + def nullable(self): + return False + + def isPrimitive(self): + return False; + + def isString(self): + return False; + + def isByteString(self): + return False + + def isDOMString(self): + return False + + def isScalarValueString(self): + return False + + def isVoid(self): + return False + + def isSequence(self): + return True + + def isArray(self): + return False + + def isDictionary(self): + return False + + def isInterface(self): + return False + + def isEnum(self): + return False + + def isSerializable(self): + return self.inner.isSerializable() + + def includesRestrictedFloat(self): + return self.inner.includesRestrictedFloat() + + def tag(self): + return IDLType.Tags.sequence + + def resolveType(self, parentScope): + assert isinstance(parentScope, IDLScope) + self.inner.resolveType(parentScope) + + def isComplete(self): + return self.inner.isComplete() + + def complete(self, scope): + self.inner = self.inner.complete(scope) + self.name = self.inner.name + return self + + def unroll(self): + return self.inner.unroll() + + def isDistinguishableFrom(self, other): + if other.isUnion(): + # Just forward to the union; it'll deal + return other.isDistinguishableFrom(self) + return (other.isPrimitive() or other.isString() or other.isEnum() or + other.isDate() or other.isNonCallbackInterface() or other.isMozMap()) + + def _getDependentObjects(self): + return self.inner._getDependentObjects() + +class IDLMozMapType(IDLType): + # XXXbz This is pretty similar to IDLSequenceType in various ways. + # And maybe to IDLNullableType. Should we have a superclass for + # "type containing this other type"? Bug 1015318. + def __init__(self, location, parameterType): + assert not parameterType.isVoid() + + IDLType.__init__(self, location, parameterType.name) + self.inner = parameterType + self.builtin = False + + def __eq__(self, other): + return isinstance(other, IDLMozMapType) and self.inner == other.inner + + def __str__(self): + return self.inner.__str__() + "MozMap" + + def isMozMap(self): + return True + + def includesRestrictedFloat(self): + return self.inner.includesRestrictedFloat() + + def tag(self): + return IDLType.Tags.mozmap + + def resolveType(self, parentScope): + assert isinstance(parentScope, IDLScope) + self.inner.resolveType(parentScope) + + def isComplete(self): + return self.inner.isComplete() + + def complete(self, scope): + self.inner = self.inner.complete(scope) + self.name = self.inner.name + return self + + def unroll(self): + # We do not unroll our inner. Just stop at ourselves. That + # lets us add headers for both ourselves and our inner as + # needed. + return self + + def isDistinguishableFrom(self, other): + if other.isUnion(): + # Just forward to the union; it'll deal + return other.isDistinguishableFrom(self) + return (other.isPrimitive() or other.isString() or other.isEnum() or + other.isDate() or other.isNonCallbackInterface() or other.isSequence()) + + def isExposedInAllOf(self, exposureSet): + return self.inner.unroll().isExposedInAllOf(exposureSet) + + def _getDependentObjects(self): + return self.inner._getDependentObjects() + +class IDLUnionType(IDLType): + def __init__(self, location, memberTypes): + IDLType.__init__(self, location, "") + self.memberTypes = memberTypes + self.hasNullableType = False + self.hasDictionaryType = False + self.flatMemberTypes = None + self.builtin = False + + def __eq__(self, other): + return isinstance(other, IDLUnionType) and self.memberTypes == other.memberTypes + + def isVoid(self): + return False + + def isUnion(self): + return True + + def isSerializable(self): + return all(m.isSerializable() for m in self.memberTypes) + + def includesRestrictedFloat(self): + return any(t.includesRestrictedFloat() for t in self.memberTypes) + + def tag(self): + return IDLType.Tags.union + + def resolveType(self, parentScope): + assert isinstance(parentScope, IDLScope) + for t in self.memberTypes: + t.resolveType(parentScope) + + def isComplete(self): + return self.flatMemberTypes is not None + + def complete(self, scope): + def typeName(type): + if isinstance(type, IDLNullableType): + return typeName(type.inner) + "OrNull" + if isinstance(type, IDLWrapperType): + return typeName(type._identifier.object()) + if isinstance(type, IDLObjectWithIdentifier): + return typeName(type.identifier) + if (isinstance(type, IDLType) and + (type.isArray() or type.isSequence() or type.isMozMap)): + return str(type) + return type.name + + for (i, type) in enumerate(self.memberTypes): + if not type.isComplete(): + self.memberTypes[i] = type.complete(scope) + + self.name = "Or".join(typeName(type) for type in self.memberTypes) + self.flatMemberTypes = list(self.memberTypes) + i = 0 + while i < len(self.flatMemberTypes): + if self.flatMemberTypes[i].nullable(): + if self.hasNullableType: + raise WebIDLError("Can't have more than one nullable types in a union", + [nullableType.location, self.flatMemberTypes[i].location]) + if self.hasDictionaryType: + raise WebIDLError("Can't have a nullable type and a " + "dictionary type in a union", + [dictionaryType.location, + self.flatMemberTypes[i].location]) + self.hasNullableType = True + nullableType = self.flatMemberTypes[i] + self.flatMemberTypes[i] = self.flatMemberTypes[i].inner + continue + if self.flatMemberTypes[i].isDictionary(): + if self.hasNullableType: + raise WebIDLError("Can't have a nullable type and a " + "dictionary type in a union", + [nullableType.location, + self.flatMemberTypes[i].location]) + self.hasDictionaryType = True + dictionaryType = self.flatMemberTypes[i] + elif self.flatMemberTypes[i].isUnion(): + self.flatMemberTypes[i:i + 1] = self.flatMemberTypes[i].memberTypes + continue + i += 1 + + for (i, t) in enumerate(self.flatMemberTypes[:-1]): + for u in self.flatMemberTypes[i + 1:]: + if not t.isDistinguishableFrom(u): + raise WebIDLError("Flat member types of a union should be " + "distinguishable, " + str(t) + " is not " + "distinguishable from " + str(u), + [self.location, t.location, u.location]) + + return self + + def isDistinguishableFrom(self, other): + if self.hasNullableType and other.nullable(): + # Can't tell which type null should become + return False + if other.isUnion(): + otherTypes = other.unroll().memberTypes + else: + otherTypes = [other] + # For every type in otherTypes, check that it's distinguishable from + # every type in our types + for u in otherTypes: + if any(not t.isDistinguishableFrom(u) for t in self.memberTypes): + return False + return True + + def isExposedInAllOf(self, exposureSet): + # We could have different member types in different globals. Just make sure that each thing in exposureSet has one of our member types exposed in it. + for globalName in exposureSet: + if not any(t.unroll().isExposedInAllOf(set([globalName])) for t + in self.flatMemberTypes): + return False + return True + + def _getDependentObjects(self): + return set(self.memberTypes) + +class IDLArrayType(IDLType): + def __init__(self, location, parameterType): + assert not parameterType.isVoid() + if parameterType.isSequence(): + raise WebIDLError("Array type cannot parameterize over a sequence type", + [location]) + if parameterType.isMozMap(): + raise WebIDLError("Array type cannot parameterize over a MozMap type", + [location]) + if parameterType.isDictionary(): + raise WebIDLError("Array type cannot parameterize over a dictionary type", + [location]) + + IDLType.__init__(self, location, parameterType.name) + self.inner = parameterType + self.builtin = False + + def __eq__(self, other): + return isinstance(other, IDLArrayType) and self.inner == other.inner + + def __str__(self): + return self.inner.__str__() + "Array" + + def nullable(self): + return False + + def isPrimitive(self): + return False + + def isString(self): + return False + + def isByteString(self): + return False + + def isDOMString(self): + return False + + def isScalarValueString(self): + return False + + def isVoid(self): + return False + + def isSequence(self): + assert not self.inner.isSequence() + return False + + def isArray(self): + return True + + def isDictionary(self): + assert not self.inner.isDictionary() + return False + + def isInterface(self): + return False + + def isEnum(self): + return False + + def tag(self): + return IDLType.Tags.array + + def resolveType(self, parentScope): + assert isinstance(parentScope, IDLScope) + self.inner.resolveType(parentScope) + + def isComplete(self): + return self.inner.isComplete() + + def complete(self, scope): + self.inner = self.inner.complete(scope) + self.name = self.inner.name + + if self.inner.isDictionary(): + raise WebIDLError("Array type must not contain " + "dictionary as element type.", + [self.inner.location]) + + assert not self.inner.isSequence() + + return self + + def unroll(self): + return self.inner.unroll() + + def isDistinguishableFrom(self, other): + if other.isUnion(): + # Just forward to the union; it'll deal + return other.isDistinguishableFrom(self) + return (other.isPrimitive() or other.isString() or other.isEnum() or + other.isDate() or other.isNonCallbackInterface()) + + def _getDependentObjects(self): + return self.inner._getDependentObjects() + +class IDLTypedefType(IDLType, IDLObjectWithIdentifier): + def __init__(self, location, innerType, name): + IDLType.__init__(self, location, innerType.name) + + identifier = IDLUnresolvedIdentifier(location, name) + + IDLObjectWithIdentifier.__init__(self, location, None, identifier) + + self.inner = innerType + self.name = name + self.builtin = False + + def __eq__(self, other): + return isinstance(other, IDLTypedefType) and self.inner == other.inner + + def __str__(self): + return self.identifier.name + + def nullable(self): + return self.inner.nullable() + + def isPrimitive(self): + return self.inner.isPrimitive() + + def isBoolean(self): + return self.inner.isBoolean() + + def isNumeric(self): + return self.inner.isNumeric() + + def isString(self): + return self.inner.isString() + + def isByteString(self): + return self.inner.isByteString() + + def isDOMString(self): + return self.inner.isDOMString() + + def isScalarValueString(self): + return self.inner.isScalarValueString() + + def isVoid(self): + return self.inner.isVoid() + + def isSequence(self): + return self.inner.isSequence() + + def isMozMap(self): + return self.inner.isMozMap() + + def isArray(self): + return self.inner.isArray() + + def isDictionary(self): + return self.inner.isDictionary() + + def isArrayBuffer(self): + return self.inner.isArrayBuffer() + + def isArrayBufferView(self): + return self.inner.isArrayBufferView() + + def isTypedArray(self): + return self.inner.isTypedArray() + + def isInterface(self): + return self.inner.isInterface() + + def isCallbackInterface(self): + return self.inner.isCallbackInterface() + + def isNonCallbackInterface(self): + return self.inner.isNonCallbackInterface() + + def isComplete(self): + return False + + def complete(self, parentScope): + if not self.inner.isComplete(): + self.inner = self.inner.complete(parentScope) + assert self.inner.isComplete() + return self.inner + + def finish(self, parentScope): + # Maybe the IDLObjectWithIdentifier for the typedef should be + # a separate thing from the type? If that happens, we can + # remove some hackery around avoiding isInterface() in + # Configuration.py. + self.complete(parentScope) + + def validate(self): + pass + + # Do we need a resolveType impl? I don't think it's particularly useful.... + + def tag(self): + return self.inner.tag() + + def unroll(self): + return self.inner.unroll() + + def isDistinguishableFrom(self, other): + return self.inner.isDistinguishableFrom(other) + + def _getDependentObjects(self): + return self.inner._getDependentObjects() + +class IDLWrapperType(IDLType): + def __init__(self, location, inner, promiseInnerType=None): + IDLType.__init__(self, location, inner.identifier.name) + self.inner = inner + self._identifier = inner.identifier + self.builtin = False + assert not promiseInnerType or inner.identifier.name == "Promise" + self._promiseInnerType = promiseInnerType + + def __eq__(self, other): + return isinstance(other, IDLWrapperType) and \ + self._identifier == other._identifier and \ + self.builtin == other.builtin + + def __str__(self): + return str(self.name) + " (Wrapper)" + + def nullable(self): + return False + + def isPrimitive(self): + return False + + def isString(self): + return False + + def isByteString(self): + return False + + def isDOMString(self): + return False + + def isScalarValueString(self): + return False + + def isVoid(self): + return False + + def isSequence(self): + return False + + def isArray(self): + return False + + def isDictionary(self): + return isinstance(self.inner, IDLDictionary) + + def isInterface(self): + return isinstance(self.inner, IDLInterface) or \ + isinstance(self.inner, IDLExternalInterface) + + def isCallbackInterface(self): + return self.isInterface() and self.inner.isCallback() + + def isNonCallbackInterface(self): + return self.isInterface() and not self.inner.isCallback() + + def isEnum(self): + return isinstance(self.inner, IDLEnum) + + def isPromise(self): + return isinstance(self.inner, IDLInterface) and \ + self.inner.identifier.name == "Promise" + + def isSerializable(self): + if self.isInterface(): + if self.inner.isExternal(): + return False + return any(m.isMethod() and m.isJsonifier() for m in self.inner.members) + elif self.isEnum(): + return True + elif self.isDictionary(): + return all(m.type.isSerializable() for m in self.inner.members) + else: + raise WebIDLError("IDLWrapperType wraps type %s that we don't know if " + "is serializable" % type(self.inner), [self.location]) + + def resolveType(self, parentScope): + assert isinstance(parentScope, IDLScope) + self.inner.resolve(parentScope) + + def isComplete(self): + return True + + def tag(self): + if self.isInterface(): + return IDLType.Tags.interface + elif self.isEnum(): + return IDLType.Tags.enum + elif self.isDictionary(): + return IDLType.Tags.dictionary + else: + assert False + + def isDistinguishableFrom(self, other): + if other.isUnion(): + # Just forward to the union; it'll deal + return other.isDistinguishableFrom(self) + assert self.isInterface() or self.isEnum() or self.isDictionary() + if self.isEnum(): + return (other.isPrimitive() or other.isInterface() or other.isObject() or + other.isCallback() or other.isDictionary() or + other.isSequence() or other.isMozMap() or other.isArray() or + other.isDate()) + if self.isDictionary() and other.nullable(): + return False + if other.isPrimitive() or other.isString() or other.isEnum() or other.isDate(): + return True + if self.isDictionary(): + return other.isNonCallbackInterface() + + assert self.isInterface() + if other.isInterface(): + if other.isSpiderMonkeyInterface(): + # Just let |other| handle things + return other.isDistinguishableFrom(self) + assert self.isGeckoInterface() and other.isGeckoInterface() + if self.inner.isExternal() or other.unroll().inner.isExternal(): + return self != other + return (len(self.inner.interfacesBasedOnSelf & + other.unroll().inner.interfacesBasedOnSelf) == 0 and + (self.isNonCallbackInterface() or + other.isNonCallbackInterface())) + if (other.isDictionary() or other.isCallback() or + other.isSequence() or other.isMozMap() or other.isArray()): + return self.isNonCallbackInterface() + + # Not much else |other| can be + assert other.isObject() + return False + + def isExposedInAllOf(self, exposureSet): + if not self.isInterface(): + return True + iface = self.inner + if iface.isExternal(): + # Let's say true, though ideally we'd only do this when + # exposureSet contains the primary global's name. + return True + if (iface.identifier.name == "Promise" and + # Check the internal type + not self._promiseInnerType.unroll().isExposedInAllOf(exposureSet)): + return False + return iface.exposureSet.issuperset(exposureSet) + + def _getDependentObjects(self): + # NB: The codegen for an interface type depends on + # a) That the identifier is in fact an interface (as opposed to + # a dictionary or something else). + # b) The native type of the interface. + # If we depend on the interface object we will also depend on + # anything the interface depends on which is undesirable. We + # considered implementing a dependency just on the interface type + # file, but then every modification to an interface would cause this + # to be regenerated which is still undesirable. We decided not to + # depend on anything, reasoning that: + # 1) Changing the concrete type of the interface requires modifying + # Bindings.conf, which is still a global dependency. + # 2) Changing an interface to a dictionary (or vice versa) with the + # same identifier should be incredibly rare. + return set() + +class IDLBuiltinType(IDLType): + + Types = enum( + # The integer types + 'byte', + 'octet', + 'short', + 'unsigned_short', + 'long', + 'unsigned_long', + 'long_long', + 'unsigned_long_long', + # Additional primitive types + 'boolean', + 'unrestricted_float', + 'float', + 'unrestricted_double', + # IMPORTANT: "double" must be the last primitive type listed + 'double', + # Other types + 'any', + 'domstring', + 'bytestring', + 'scalarvaluestring', + 'object', + 'date', + 'void', + # Funny stuff + 'ArrayBuffer', + 'ArrayBufferView', + 'Int8Array', + 'Uint8Array', + 'Uint8ClampedArray', + 'Int16Array', + 'Uint16Array', + 'Int32Array', + 'Uint32Array', + 'Float32Array', + 'Float64Array' + ) + + TagLookup = { + Types.byte: IDLType.Tags.int8, + Types.octet: IDLType.Tags.uint8, + Types.short: IDLType.Tags.int16, + Types.unsigned_short: IDLType.Tags.uint16, + Types.long: IDLType.Tags.int32, + Types.unsigned_long: IDLType.Tags.uint32, + Types.long_long: IDLType.Tags.int64, + Types.unsigned_long_long: IDLType.Tags.uint64, + Types.boolean: IDLType.Tags.bool, + Types.unrestricted_float: IDLType.Tags.unrestricted_float, + Types.float: IDLType.Tags.float, + Types.unrestricted_double: IDLType.Tags.unrestricted_double, + Types.double: IDLType.Tags.double, + Types.any: IDLType.Tags.any, + Types.domstring: IDLType.Tags.domstring, + Types.bytestring: IDLType.Tags.bytestring, + Types.scalarvaluestring: IDLType.Tags.scalarvaluestring, + Types.object: IDLType.Tags.object, + Types.date: IDLType.Tags.date, + Types.void: IDLType.Tags.void, + Types.ArrayBuffer: IDLType.Tags.interface, + Types.ArrayBufferView: IDLType.Tags.interface, + Types.Int8Array: IDLType.Tags.interface, + Types.Uint8Array: IDLType.Tags.interface, + Types.Uint8ClampedArray: IDLType.Tags.interface, + Types.Int16Array: IDLType.Tags.interface, + Types.Uint16Array: IDLType.Tags.interface, + Types.Int32Array: IDLType.Tags.interface, + Types.Uint32Array: IDLType.Tags.interface, + Types.Float32Array: IDLType.Tags.interface, + Types.Float64Array: IDLType.Tags.interface + } + + def __init__(self, location, name, type): + IDLType.__init__(self, location, name) + self.builtin = True + self._typeTag = type + + def isPrimitive(self): + return self._typeTag <= IDLBuiltinType.Types.double + + def isBoolean(self): + return self._typeTag == IDLBuiltinType.Types.boolean + + def isNumeric(self): + return self.isPrimitive() and not self.isBoolean() + + def isString(self): + return self._typeTag == IDLBuiltinType.Types.domstring or \ + self._typeTag == IDLBuiltinType.Types.bytestring or \ + self._typeTag == IDLBuiltinType.Types.scalarvaluestring + + def isByteString(self): + return self._typeTag == IDLBuiltinType.Types.bytestring + + def isDOMString(self): + return self._typeTag == IDLBuiltinType.Types.domstring + + def isScalarValueString(self): + return self._typeTag == IDLBuiltinType.Types.scalarvaluestring + + def isInteger(self): + return self._typeTag <= IDLBuiltinType.Types.unsigned_long_long + + def isArrayBuffer(self): + return self._typeTag == IDLBuiltinType.Types.ArrayBuffer + + def isArrayBufferView(self): + return self._typeTag == IDLBuiltinType.Types.ArrayBufferView + + def isTypedArray(self): + return self._typeTag >= IDLBuiltinType.Types.Int8Array and \ + self._typeTag <= IDLBuiltinType.Types.Float64Array + + def isInterface(self): + # TypedArray things are interface types per the TypedArray spec, + # but we handle them as builtins because SpiderMonkey implements + # all of it internally. + return self.isArrayBuffer() or \ + self.isArrayBufferView() or \ + self.isTypedArray() + + def isNonCallbackInterface(self): + # All the interfaces we can be are non-callback + return self.isInterface() + + def isFloat(self): + return self._typeTag == IDLBuiltinType.Types.float or \ + self._typeTag == IDLBuiltinType.Types.double or \ + self._typeTag == IDLBuiltinType.Types.unrestricted_float or \ + self._typeTag == IDLBuiltinType.Types.unrestricted_double + + def isUnrestricted(self): + assert self.isFloat() + return self._typeTag == IDLBuiltinType.Types.unrestricted_float or \ + self._typeTag == IDLBuiltinType.Types.unrestricted_double + + def isSerializable(self): + return self.isPrimitive() or self.isDOMString() or self.isDate() + + def includesRestrictedFloat(self): + return self.isFloat() and not self.isUnrestricted() + + def tag(self): + return IDLBuiltinType.TagLookup[self._typeTag] + + def isDistinguishableFrom(self, other): + if other.isUnion(): + # Just forward to the union; it'll deal + return other.isDistinguishableFrom(self) + if self.isBoolean(): + return (other.isNumeric() or other.isString() or other.isEnum() or + other.isInterface() or other.isObject() or + other.isCallback() or other.isDictionary() or + other.isSequence() or other.isMozMap() or other.isArray() or + other.isDate()) + if self.isNumeric(): + return (other.isBoolean() or other.isString() or other.isEnum() or + other.isInterface() or other.isObject() or + other.isCallback() or other.isDictionary() or + other.isSequence() or other.isMozMap() or other.isArray() or + other.isDate()) + if self.isString(): + return (other.isPrimitive() or other.isInterface() or + other.isObject() or + other.isCallback() or other.isDictionary() or + other.isSequence() or other.isMozMap() or other.isArray() or + other.isDate()) + if self.isAny(): + # Can't tell "any" apart from anything + return False + if self.isObject(): + return other.isPrimitive() or other.isString() or other.isEnum() + if self.isDate(): + return (other.isPrimitive() or other.isString() or other.isEnum() or + other.isInterface() or other.isCallback() or + other.isDictionary() or other.isSequence() or + other.isMozMap() or other.isArray()) + if self.isVoid(): + return not other.isVoid() + # Not much else we could be! + assert self.isSpiderMonkeyInterface() + # Like interfaces, but we know we're not a callback + return (other.isPrimitive() or other.isString() or other.isEnum() or + other.isCallback() or other.isDictionary() or + other.isSequence() or other.isMozMap() or other.isArray() or + other.isDate() or + (other.isInterface() and ( + # ArrayBuffer is distinguishable from everything + # that's not an ArrayBuffer or a callback interface + (self.isArrayBuffer() and not other.isArrayBuffer()) or + # ArrayBufferView is distinguishable from everything + # that's not an ArrayBufferView or typed array. + (self.isArrayBufferView() and not other.isArrayBufferView() and + not other.isTypedArray()) or + # Typed arrays are distinguishable from everything + # except ArrayBufferView and the same type of typed + # array + (self.isTypedArray() and not other.isArrayBufferView() and not + (other.isTypedArray() and other.name == self.name))))) + + def _getDependentObjects(self): + return set() + +BuiltinTypes = { + IDLBuiltinType.Types.byte: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Byte", + IDLBuiltinType.Types.byte), + IDLBuiltinType.Types.octet: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Octet", + IDLBuiltinType.Types.octet), + IDLBuiltinType.Types.short: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Short", + IDLBuiltinType.Types.short), + IDLBuiltinType.Types.unsigned_short: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "UnsignedShort", + IDLBuiltinType.Types.unsigned_short), + IDLBuiltinType.Types.long: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Long", + IDLBuiltinType.Types.long), + IDLBuiltinType.Types.unsigned_long: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "UnsignedLong", + IDLBuiltinType.Types.unsigned_long), + IDLBuiltinType.Types.long_long: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "LongLong", + IDLBuiltinType.Types.long_long), + IDLBuiltinType.Types.unsigned_long_long: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "UnsignedLongLong", + IDLBuiltinType.Types.unsigned_long_long), + IDLBuiltinType.Types.boolean: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Boolean", + IDLBuiltinType.Types.boolean), + IDLBuiltinType.Types.float: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Float", + IDLBuiltinType.Types.float), + IDLBuiltinType.Types.unrestricted_float: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "UnrestrictedFloat", + IDLBuiltinType.Types.unrestricted_float), + IDLBuiltinType.Types.double: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Double", + IDLBuiltinType.Types.double), + IDLBuiltinType.Types.unrestricted_double: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "UnrestrictedDouble", + IDLBuiltinType.Types.unrestricted_double), + IDLBuiltinType.Types.any: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Any", + IDLBuiltinType.Types.any), + IDLBuiltinType.Types.domstring: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "String", + IDLBuiltinType.Types.domstring), + IDLBuiltinType.Types.bytestring: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "ByteString", + IDLBuiltinType.Types.bytestring), + IDLBuiltinType.Types.scalarvaluestring: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "ScalarValueString", + IDLBuiltinType.Types.scalarvaluestring), + IDLBuiltinType.Types.object: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Object", + IDLBuiltinType.Types.object), + IDLBuiltinType.Types.date: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Date", + IDLBuiltinType.Types.date), + IDLBuiltinType.Types.void: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Void", + IDLBuiltinType.Types.void), + IDLBuiltinType.Types.ArrayBuffer: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "ArrayBuffer", + IDLBuiltinType.Types.ArrayBuffer), + IDLBuiltinType.Types.ArrayBufferView: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "ArrayBufferView", + IDLBuiltinType.Types.ArrayBufferView), + IDLBuiltinType.Types.Int8Array: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Int8Array", + IDLBuiltinType.Types.Int8Array), + IDLBuiltinType.Types.Uint8Array: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Uint8Array", + IDLBuiltinType.Types.Uint8Array), + IDLBuiltinType.Types.Uint8ClampedArray: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Uint8ClampedArray", + IDLBuiltinType.Types.Uint8ClampedArray), + IDLBuiltinType.Types.Int16Array: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Int16Array", + IDLBuiltinType.Types.Int16Array), + IDLBuiltinType.Types.Uint16Array: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Uint16Array", + IDLBuiltinType.Types.Uint16Array), + IDLBuiltinType.Types.Int32Array: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Int32Array", + IDLBuiltinType.Types.Int32Array), + IDLBuiltinType.Types.Uint32Array: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Uint32Array", + IDLBuiltinType.Types.Uint32Array), + IDLBuiltinType.Types.Float32Array: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Float32Array", + IDLBuiltinType.Types.Float32Array), + IDLBuiltinType.Types.Float64Array: + IDLBuiltinType(BuiltinLocation("<builtin type>"), "Float64Array", + IDLBuiltinType.Types.Float64Array) + } + + +integerTypeSizes = { + IDLBuiltinType.Types.byte: (-128, 127), + IDLBuiltinType.Types.octet: (0, 255), + IDLBuiltinType.Types.short: (-32768, 32767), + IDLBuiltinType.Types.unsigned_short: (0, 65535), + IDLBuiltinType.Types.long: (-2147483648, 2147483647), + IDLBuiltinType.Types.unsigned_long: (0, 4294967295), + IDLBuiltinType.Types.long_long: (-9223372036854775808, + 9223372036854775807), + IDLBuiltinType.Types.unsigned_long_long: (0, 18446744073709551615) + } + +def matchIntegerValueToType(value): + for type, extremes in integerTypeSizes.items(): + (min, max) = extremes + if value <= max and value >= min: + return BuiltinTypes[type] + + return None + +class IDLValue(IDLObject): + def __init__(self, location, type, value): + IDLObject.__init__(self, location) + self.type = type + assert isinstance(type, IDLType) + + self.value = value + + def coerceToType(self, type, location): + if type == self.type: + return self # Nothing to do + + # We first check for unions to ensure that even if the union is nullable + # we end up with the right flat member type, not the union's type. + if type.isUnion(): + # We use the flat member types here, because if we have a nullable + # member type, or a nested union, we want the type the value + # actually coerces to, not the nullable or nested union type. + for subtype in type.unroll().flatMemberTypes: + try: + coercedValue = self.coerceToType(subtype, location) + # Create a new IDLValue to make sure that we have the + # correct float/double type. This is necessary because we + # use the value's type when it is a default value of a + # union, and the union cares about the exact float type. + return IDLValue(self.location, subtype, coercedValue.value) + except: + pass + # If the type allows null, rerun this matching on the inner type, except + # nullable enums. We handle those specially, because we want our + # default string values to stay strings even when assigned to a nullable + # enum. + elif type.nullable() and not type.isEnum(): + innerValue = self.coerceToType(type.inner, location) + return IDLValue(self.location, type, innerValue.value) + + elif self.type.isInteger() and type.isInteger(): + # We're both integer types. See if we fit. + + (min, max) = integerTypeSizes[type._typeTag] + if self.value <= max and self.value >= min: + # Promote + return IDLValue(self.location, type, self.value) + else: + raise WebIDLError("Value %s is out of range for type %s." % + (self.value, type), [location]) + elif self.type.isInteger() and type.isFloat(): + # Convert an integer literal into float + if -2**24 <= self.value <= 2**24: + floatType = BuiltinTypes[IDLBuiltinType.Types.float] + return IDLValue(self.location, floatType, float(self.value)) + else: + raise WebIDLError("Converting value %s to %s will lose precision." % + (self.value, type), [location]) + elif self.type.isString() and type.isEnum(): + # Just keep our string, but make sure it's a valid value for this enum + enum = type.unroll().inner + if self.value not in enum.values(): + raise WebIDLError("'%s' is not a valid default value for enum %s" + % (self.value, enum.identifier.name), + [location, enum.location]) + return self + elif self.type.isFloat() and type.isFloat(): + if (not type.isUnrestricted() and + (self.value == float("inf") or self.value == float("-inf") or + math.isnan(self.value))): + raise WebIDLError("Trying to convert unrestricted value %s to non-unrestricted" + % self.value, [location]); + return self + elif self.type.isString() and type.isScalarValueString(): + # Allow ScalarValueStrings to use default value just like + # DOMString. No coercion is required in this case as Codegen.py + # treats ScalarValueString just like DOMString, but with an + # extra normalization step. + assert self.type.isDOMString() + return self + raise WebIDLError("Cannot coerce type %s to type %s." % + (self.type, type), [location]) + + def _getDependentObjects(self): + return set() + +class IDLNullValue(IDLObject): + def __init__(self, location): + IDLObject.__init__(self, location) + self.type = None + self.value = None + + def coerceToType(self, type, location): + if (not isinstance(type, IDLNullableType) and + not (type.isUnion() and type.hasNullableType) and + not (type.isUnion() and type.hasDictionaryType) and + not type.isDictionary() and + not type.isAny()): + raise WebIDLError("Cannot coerce null value to type %s." % type, + [location]) + + nullValue = IDLNullValue(self.location) + if type.isUnion() and not type.nullable() and type.hasDictionaryType: + # We're actually a default value for the union's dictionary member. + # Use its type. + for t in type.flatMemberTypes: + if t.isDictionary(): + nullValue.type = t + return nullValue + nullValue.type = type + return nullValue + + def _getDependentObjects(self): + return set() + +class IDLEmptySequenceValue(IDLObject): + def __init__(self, location): + IDLObject.__init__(self, location) + self.type = None + self.value = None + + def coerceToType(self, type, location): + if type.isUnion(): + # We use the flat member types here, because if we have a nullable + # member type, or a nested union, we want the type the value + # actually coerces to, not the nullable or nested union type. + for subtype in type.unroll().flatMemberTypes: + try: + return self.coerceToType(subtype, location) + except: + pass + + if not type.isSequence(): + raise WebIDLError("Cannot coerce empty sequence value to type %s." % type, + [location]) + + emptySequenceValue = IDLEmptySequenceValue(self.location) + emptySequenceValue.type = type + return emptySequenceValue + + def _getDependentObjects(self): + return set() + +class IDLUndefinedValue(IDLObject): + def __init__(self, location): + IDLObject.__init__(self, location) + self.type = None + self.value = None + + def coerceToType(self, type, location): + if not type.isAny(): + raise WebIDLError("Cannot coerce undefined value to type %s." % type, + [location]) + + undefinedValue = IDLUndefinedValue(self.location) + undefinedValue.type = type + return undefinedValue + + def _getDependentObjects(self): + return set() + +class IDLInterfaceMember(IDLObjectWithIdentifier): + + Tags = enum( + 'Const', + 'Attr', + 'Method' + ) + + Special = enum( + 'Static', + 'Stringifier' + ) + + def __init__(self, location, identifier, tag): + IDLObjectWithIdentifier.__init__(self, location, None, identifier) + self.tag = tag + self._extendedAttrDict = {} + # _exposureGlobalNames are the global names listed in our [Exposed] + # extended attribute. exposureSet is the exposure set as defined in the + # Web IDL spec: it contains interface names. + self._exposureGlobalNames = set() + self.exposureSet = set() + + def isMethod(self): + return self.tag == IDLInterfaceMember.Tags.Method + + def isAttr(self): + return self.tag == IDLInterfaceMember.Tags.Attr + + def isConst(self): + return self.tag == IDLInterfaceMember.Tags.Const + + def addExtendedAttributes(self, attrs): + for attr in attrs: + self.handleExtendedAttribute(attr) + attrlist = attr.listValue() + self._extendedAttrDict[attr.identifier()] = attrlist if len(attrlist) else True + + def handleExtendedAttribute(self, attr): + pass + + def getExtendedAttribute(self, name): + return self._extendedAttrDict.get(name, None) + + def finish(self, scope): + for globalName in self._exposureGlobalNames: + if globalName not in scope.globalNames: + raise WebIDLError("Unknown [Exposed] value %s" % globalName, + [self.location]) + globalNameSetToExposureSet(scope, self._exposureGlobalNames, + self.exposureSet) + self._scope = scope + + def validate(self): + if (self.getExtendedAttribute("Pref") and + self.exposureSet != set([self._scope.primaryGlobalName])): + raise WebIDLError("[Pref] used on an interface member that is not " + "%s-only" % self._scope.primaryGlobalName, + [self.location]) + +class IDLConst(IDLInterfaceMember): + def __init__(self, location, identifier, type, value): + IDLInterfaceMember.__init__(self, location, identifier, + IDLInterfaceMember.Tags.Const) + + assert isinstance(type, IDLType) + if type.isDictionary(): + raise WebIDLError("A constant cannot be of a dictionary type", + [self.location]) + self.type = type + self.value = value + + if identifier.name == "prototype": + raise WebIDLError("The identifier of a constant must not be 'prototype'", + [location]) + + def __str__(self): + return "'%s' const '%s'" % (self.type, self.identifier) + + def finish(self, scope): + IDLInterfaceMember.finish(self, scope) + + if not self.type.isComplete(): + type = self.type.complete(scope) + if not type.isPrimitive() and not type.isString(): + locations = [self.type.location, type.location] + try: + locations.append(type.inner.location) + except: + pass + raise WebIDLError("Incorrect type for constant", locations) + self.type = type + + # The value might not match the type + coercedValue = self.value.coerceToType(self.type, self.location) + assert coercedValue + + self.value = coercedValue + + def validate(self): + IDLInterfaceMember.validate(self) + + def handleExtendedAttribute(self, attr): + identifier = attr.identifier() + if identifier == "Exposed": + convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames) + elif (identifier == "Pref" or + identifier == "ChromeOnly" or + identifier == "Func" or + identifier == "AvailableIn" or + identifier == "CheckPermissions"): + # Known attributes that we don't need to do anything with here + pass + else: + raise WebIDLError("Unknown extended attribute %s on constant" % identifier, + [attr.location]) + IDLInterfaceMember.handleExtendedAttribute(self, attr) + + def _getDependentObjects(self): + return set([self.type, self.value]) + +class IDLAttribute(IDLInterfaceMember): + def __init__(self, location, identifier, type, readonly, inherit=False, + static=False, stringifier=False): + IDLInterfaceMember.__init__(self, location, identifier, + IDLInterfaceMember.Tags.Attr) + + assert isinstance(type, IDLType) + self.type = type + self.readonly = readonly + self.inherit = inherit + self.static = static + self.lenientThis = False + self._unforgeable = False + self.stringifier = stringifier + self.enforceRange = False + self.clamp = False + self.slotIndex = None + + if static and identifier.name == "prototype": + raise WebIDLError("The identifier of a static attribute must not be 'prototype'", + [location]) + + if readonly and inherit: + raise WebIDLError("An attribute cannot be both 'readonly' and 'inherit'", + [self.location]) + + def isStatic(self): + return self.static + + def __str__(self): + return "'%s' attribute '%s'" % (self.type, self.identifier) + + def finish(self, scope): + IDLInterfaceMember.finish(self, scope) + + if not self.type.isComplete(): + t = self.type.complete(scope) + + assert not isinstance(t, IDLUnresolvedType) + assert not isinstance(t, IDLTypedefType) + assert not isinstance(t.name, IDLUnresolvedIdentifier) + self.type = t + + if self.type.isDictionary() and not self.getExtendedAttribute("Cached"): + raise WebIDLError("An attribute cannot be of a dictionary type", + [self.location]) + if self.type.isSequence() and not self.getExtendedAttribute("Cached"): + raise WebIDLError("A non-cached attribute cannot be of a sequence " + "type", [self.location]) + if self.type.isMozMap() and not self.getExtendedAttribute("Cached"): + raise WebIDLError("A non-cached attribute cannot be of a MozMap " + "type", [self.location]) + if self.type.isUnion(): + for f in self.type.unroll().flatMemberTypes: + if f.isDictionary(): + raise WebIDLError("An attribute cannot be of a union " + "type if one of its member types (or " + "one of its member types's member " + "types, and so on) is a dictionary " + "type", [self.location, f.location]) + if f.isSequence(): + raise WebIDLError("An attribute cannot be of a union " + "type if one of its member types (or " + "one of its member types's member " + "types, and so on) is a sequence " + "type", [self.location, f.location]) + if f.isMozMap(): + raise WebIDLError("An attribute cannot be of a union " + "type if one of its member types (or " + "one of its member types's member " + "types, and so on) is a MozMap " + "type", [self.location, f.location]) + if not self.type.isInterface() and self.getExtendedAttribute("PutForwards"): + raise WebIDLError("An attribute with [PutForwards] must have an " + "interface type as its type", [self.location]) + + if not self.type.isInterface() and self.getExtendedAttribute("SameObject"): + raise WebIDLError("An attribute with [SameObject] must have an " + "interface type as its type", [self.location]) + + def validate(self): + IDLInterfaceMember.validate(self) + + if ((self.getExtendedAttribute("Cached") or + self.getExtendedAttribute("StoreInSlot")) and + not self.getExtendedAttribute("Constant") and + not self.getExtendedAttribute("Pure")): + raise WebIDLError("Cached attributes and attributes stored in " + "slots must be constant or pure, since the " + "getter won't always be called.", + [self.location]) + if self.getExtendedAttribute("Frozen"): + if (not self.type.isSequence() and not self.type.isDictionary() and + not self.type.isMozMap()): + raise WebIDLError("[Frozen] is only allowed on " + "sequence-valued, dictionary-valued, and " + "MozMap-valued attributes", + [self.location]) + if not self.type.unroll().isExposedInAllOf(self.exposureSet): + raise WebIDLError("Attribute returns a type that is not exposed " + "everywhere where the attribute is exposed", + [self.location]) + + def handleExtendedAttribute(self, attr): + identifier = attr.identifier() + if identifier == "SetterThrows" and self.readonly: + raise WebIDLError("Readonly attributes must not be flagged as " + "[SetterThrows]", + [self.location]) + elif (((identifier == "Throws" or identifier == "GetterThrows") and + self.getExtendedAttribute("StoreInSlot")) or + (identifier == "StoreInSlot" and + (self.getExtendedAttribute("Throws") or + self.getExtendedAttribute("GetterThrows")))): + raise WebIDLError("Throwing things can't be [Pure] or [Constant] " + "or [SameObject] or [StoreInSlot]", + [attr.location]) + elif identifier == "LenientThis": + if not attr.noArguments(): + raise WebIDLError("[LenientThis] must take no arguments", + [attr.location]) + if self.isStatic(): + raise WebIDLError("[LenientThis] is only allowed on non-static " + "attributes", [attr.location, self.location]) + if self.getExtendedAttribute("CrossOriginReadable"): + raise WebIDLError("[LenientThis] is not allowed in combination " + "with [CrossOriginReadable]", + [attr.location, self.location]) + if self.getExtendedAttribute("CrossOriginWritable"): + raise WebIDLError("[LenientThis] is not allowed in combination " + "with [CrossOriginWritable]", + [attr.location, self.location]) + self.lenientThis = True + elif identifier == "Unforgeable": + if self.isStatic(): + raise WebIDLError("[Unforgeable] is only allowed on non-static " + "attributes", [attr.location, self.location]) + self._unforgeable = True + elif identifier == "SameObject" and not self.readonly: + raise WebIDLError("[SameObject] only allowed on readonly attributes", + [attr.location, self.location]) + elif identifier == "Constant" and not self.readonly: + raise WebIDLError("[Constant] only allowed on readonly attributes", + [attr.location, self.location]) + elif identifier == "PutForwards": + if not self.readonly: + raise WebIDLError("[PutForwards] is only allowed on readonly " + "attributes", [attr.location, self.location]) + if self.isStatic(): + raise WebIDLError("[PutForwards] is only allowed on non-static " + "attributes", [attr.location, self.location]) + if self.getExtendedAttribute("Replaceable") is not None: + raise WebIDLError("[PutForwards] and [Replaceable] can't both " + "appear on the same attribute", + [attr.location, self.location]) + if not attr.hasValue(): + raise WebIDLError("[PutForwards] takes an identifier", + [attr.location, self.location]) + elif identifier == "Replaceable": + if self.getExtendedAttribute("PutForwards") is not None: + raise WebIDLError("[PutForwards] and [Replaceable] can't both " + "appear on the same attribute", + [attr.location, self.location]) + elif identifier == "LenientFloat": + if self.readonly: + raise WebIDLError("[LenientFloat] used on a readonly attribute", + [attr.location, self.location]) + if not self.type.includesRestrictedFloat(): + raise WebIDLError("[LenientFloat] used on an attribute with a " + "non-restricted-float type", + [attr.location, self.location]) + elif identifier == "EnforceRange": + if self.readonly: + raise WebIDLError("[EnforceRange] used on a readonly attribute", + [attr.location, self.location]) + self.enforceRange = True + elif identifier == "Clamp": + if self.readonly: + raise WebIDLError("[Clamp] used on a readonly attribute", + [attr.location, self.location]) + self.clamp = True + elif identifier == "StoreInSlot": + if self.getExtendedAttribute("Cached"): + raise WebIDLError("[StoreInSlot] and [Cached] must not be " + "specified on the same attribute", + [attr.location, self.location]) + elif identifier == "Cached": + if self.getExtendedAttribute("StoreInSlot"): + raise WebIDLError("[Cached] and [StoreInSlot] must not be " + "specified on the same attribute", + [attr.location, self.location]) + elif (identifier == "CrossOriginReadable" or + identifier == "CrossOriginWritable"): + if not attr.noArguments() and identifier == "CrossOriginReadable": + raise WebIDLError("[%s] must take no arguments" % identifier, + [attr.location]) + if self.isStatic(): + raise WebIDLError("[%s] is only allowed on non-static " + "attributes" % identifier, + [attr.location, self.location]) + if self.getExtendedAttribute("LenientThis"): + raise WebIDLError("[LenientThis] is not allowed in combination " + "with [%s]" % identifier, + [attr.location, self.location]) + elif identifier == "Exposed": + convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames) + elif (identifier == "Pref" or + identifier == "SetterThrows" or + identifier == "Pure" or + identifier == "Throws" or + identifier == "GetterThrows" or + identifier == "ChromeOnly" or + identifier == "SameObject" or + identifier == "Constant" or + identifier == "Func" or + identifier == "Frozen" or + identifier == "AvailableIn" or + identifier == "NewObject" or + identifier == "CheckPermissions"): + # Known attributes that we don't need to do anything with here + pass + else: + raise WebIDLError("Unknown extended attribute %s on attribute" % identifier, + [attr.location]) + IDLInterfaceMember.handleExtendedAttribute(self, attr) + + def resolve(self, parentScope): + assert isinstance(parentScope, IDLScope) + self.type.resolveType(parentScope) + IDLObjectWithIdentifier.resolve(self, parentScope) + + def addExtendedAttributes(self, attrs): + attrs = self.checkForStringHandlingExtendedAttributes(attrs) + IDLInterfaceMember.addExtendedAttributes(self, attrs) + + def hasLenientThis(self): + return self.lenientThis + + def isUnforgeable(self): + return self._unforgeable + + def _getDependentObjects(self): + return set([self.type]) + +class IDLArgument(IDLObjectWithIdentifier): + def __init__(self, location, identifier, type, optional=False, defaultValue=None, variadic=False, dictionaryMember=False): + IDLObjectWithIdentifier.__init__(self, location, None, identifier) + + assert isinstance(type, IDLType) + self.type = type + + self.optional = optional + self.defaultValue = defaultValue + self.variadic = variadic + self.dictionaryMember = dictionaryMember + self._isComplete = False + self.enforceRange = False + self.clamp = False + self._allowTreatNonCallableAsNull = False + + assert not variadic or optional + + def addExtendedAttributes(self, attrs): + attrs = self.checkForStringHandlingExtendedAttributes( + attrs, + isDictionaryMember=self.dictionaryMember, + isOptional=self.optional) + for attribute in attrs: + identifier = attribute.identifier() + if identifier == "Clamp": + if not attribute.noArguments(): + raise WebIDLError("[Clamp] must take no arguments", + [attribute.location]) + if self.enforceRange: + raise WebIDLError("[EnforceRange] and [Clamp] are mutually exclusive", + [self.location]); + self.clamp = True + elif identifier == "EnforceRange": + if not attribute.noArguments(): + raise WebIDLError("[EnforceRange] must take no arguments", + [attribute.location]) + if self.clamp: + raise WebIDLError("[EnforceRange] and [Clamp] are mutually exclusive", + [self.location]); + self.enforceRange = True + elif identifier == "TreatNonCallableAsNull": + self._allowTreatNonCallableAsNull = True + else: + raise WebIDLError("Unhandled extended attribute on an argument", + [attribute.location]) + + def isComplete(self): + return self._isComplete + + def complete(self, scope): + if self._isComplete: + return + + self._isComplete = True + + if not self.type.isComplete(): + type = self.type.complete(scope) + assert not isinstance(type, IDLUnresolvedType) + assert not isinstance(type, IDLTypedefType) + assert not isinstance(type.name, IDLUnresolvedIdentifier) + self.type = type + + if ((self.type.isDictionary() or + self.type.isUnion() and self.type.unroll().hasDictionaryType) and + self.optional and not self.defaultValue): + # Default optional dictionaries to null, for simplicity, + # so the codegen doesn't have to special-case this. + self.defaultValue = IDLNullValue(self.location) + elif self.type.isAny(): + assert (self.defaultValue is None or + isinstance(self.defaultValue, IDLNullValue)) + # optional 'any' values always have a default value + if self.optional and not self.defaultValue and not self.variadic: + # Set the default value to undefined, for simplicity, so the + # codegen doesn't have to special-case this. + self.defaultValue = IDLUndefinedValue(self.location) + + # Now do the coercing thing; this needs to happen after the + # above creation of a default value. + if self.defaultValue: + self.defaultValue = self.defaultValue.coerceToType(self.type, + self.location) + assert self.defaultValue + + def allowTreatNonCallableAsNull(self): + return self._allowTreatNonCallableAsNull + + def _getDependentObjects(self): + deps = set([self.type]) + if self.defaultValue: + deps.add(self.defaultValue) + return deps + +class IDLCallbackType(IDLType, IDLObjectWithScope): + def __init__(self, location, parentScope, identifier, returnType, arguments): + assert isinstance(returnType, IDLType) + + IDLType.__init__(self, location, identifier.name) + + self._returnType = returnType + # Clone the list + self._arguments = list(arguments) + + IDLObjectWithScope.__init__(self, location, parentScope, identifier) + + for (returnType, arguments) in self.signatures(): + for argument in arguments: + argument.resolve(self) + + self._treatNonCallableAsNull = False + self._treatNonObjectAsNull = False + + def module(self): + return self.location.filename().split('/')[-1].split('.webidl')[0] + 'Binding' + + def isCallback(self): + return True + + def signatures(self): + return [(self._returnType, self._arguments)] + + def tag(self): + return IDLType.Tags.callback + + def finish(self, scope): + if not self._returnType.isComplete(): + type = self._returnType.complete(scope) + + assert not isinstance(type, IDLUnresolvedType) + assert not isinstance(type, IDLTypedefType) + assert not isinstance(type.name, IDLUnresolvedIdentifier) + self._returnType = type + + for argument in self._arguments: + if argument.type.isComplete(): + continue + + type = argument.type.complete(scope) + + assert not isinstance(type, IDLUnresolvedType) + assert not isinstance(type, IDLTypedefType) + assert not isinstance(type.name, IDLUnresolvedIdentifier) + argument.type = type + + def validate(self): + pass + + def isDistinguishableFrom(self, other): + if other.isUnion(): + # Just forward to the union; it'll deal + return other.isDistinguishableFrom(self) + return (other.isPrimitive() or other.isString() or other.isEnum() or + other.isNonCallbackInterface() or other.isDate()) + + def addExtendedAttributes(self, attrs): + unhandledAttrs = [] + for attr in attrs: + if attr.identifier() == "TreatNonCallableAsNull": + self._treatNonCallableAsNull = True + elif attr.identifier() == "TreatNonObjectAsNull": + self._treatNonObjectAsNull = True + else: + unhandledAttrs.append(attr) + if self._treatNonCallableAsNull and self._treatNonObjectAsNull: + raise WebIDLError("Cannot specify both [TreatNonCallableAsNull] " + "and [TreatNonObjectAsNull]", [self.location]) + if len(unhandledAttrs) != 0: + IDLType.addExtendedAttributes(self, unhandledAttrs) + + def _getDependentObjects(self): + return set([self._returnType] + self._arguments) + +class IDLMethodOverload: + """ + A class that represents a single overload of a WebIDL method. This is not + quite the same as an element of the "effective overload set" in the spec, + because separate IDLMethodOverloads are not created based on arguments being + optional. Rather, when multiple methods have the same name, there is an + IDLMethodOverload for each one, all hanging off an IDLMethod representing + the full set of overloads. + """ + def __init__(self, returnType, arguments, location): + self.returnType = returnType + # Clone the list of arguments, just in case + self.arguments = list(arguments) + self.location = location + + def _getDependentObjects(self): + deps = set(self.arguments) + deps.add(self.returnType) + return deps + +class IDLMethod(IDLInterfaceMember, IDLScope): + + Special = enum( + 'Getter', + 'Setter', + 'Creator', + 'Deleter', + 'LegacyCaller', + base=IDLInterfaceMember.Special + ) + + TypeSuffixModifier = enum( + 'None', + 'QMark', + 'Brackets' + ) + + NamedOrIndexed = enum( + 'Neither', + 'Named', + 'Indexed' + ) + + def __init__(self, location, identifier, returnType, arguments, + static=False, getter=False, setter=False, creator=False, + deleter=False, specialType=NamedOrIndexed.Neither, + legacycaller=False, stringifier=False, jsonifier=False): + # REVIEW: specialType is NamedOrIndexed -- wow, this is messed up. + IDLInterfaceMember.__init__(self, location, identifier, + IDLInterfaceMember.Tags.Method) + + self._hasOverloads = False + + assert isinstance(returnType, IDLType) + + # self._overloads is a list of IDLMethodOverloads + self._overloads = [IDLMethodOverload(returnType, arguments, location)] + + assert isinstance(static, bool) + self._static = static + assert isinstance(getter, bool) + self._getter = getter + assert isinstance(setter, bool) + self._setter = setter + assert isinstance(creator, bool) + self._creator = creator + assert isinstance(deleter, bool) + self._deleter = deleter + assert isinstance(legacycaller, bool) + self._legacycaller = legacycaller + assert isinstance(stringifier, bool) + self._stringifier = stringifier + assert isinstance(jsonifier, bool) + self._jsonifier = jsonifier + self._specialType = specialType + self._unforgeable = False + + if static and identifier.name == "prototype": + raise WebIDLError("The identifier of a static operation must not be 'prototype'", + [location]) + + self.assertSignatureConstraints() + + def __str__(self): + return "Method '%s'" % self.identifier + + def assertSignatureConstraints(self): + if self._getter or self._deleter: + assert len(self._overloads) == 1 + overload = self._overloads[0] + arguments = overload.arguments + assert len(arguments) == 1 + assert arguments[0].type == BuiltinTypes[IDLBuiltinType.Types.domstring] or \ + arguments[0].type == BuiltinTypes[IDLBuiltinType.Types.unsigned_long] + assert not arguments[0].optional and not arguments[0].variadic + assert not self._getter or not overload.returnType.isVoid() + + if self._setter or self._creator: + assert len(self._overloads) == 1 + arguments = self._overloads[0].arguments + assert len(arguments) == 2 + assert arguments[0].type == BuiltinTypes[IDLBuiltinType.Types.domstring] or \ + arguments[0].type == BuiltinTypes[IDLBuiltinType.Types.unsigned_long] + assert not arguments[0].optional and not arguments[0].variadic + assert not arguments[1].optional and not arguments[1].variadic + + if self._stringifier: + assert len(self._overloads) == 1 + overload = self._overloads[0] + assert len(overload.arguments) == 0 + assert overload.returnType == BuiltinTypes[IDLBuiltinType.Types.domstring] + + if self._jsonifier: + assert len(self._overloads) == 1 + overload = self._overloads[0] + assert len(overload.arguments) == 0 + assert overload.returnType == BuiltinTypes[IDLBuiltinType.Types.object] + + def isStatic(self): + return self._static + + def isGetter(self): + return self._getter + + def isSetter(self): + return self._setter + + def isCreator(self): + return self._creator + + def isDeleter(self): + return self._deleter + + def isNamed(self): + assert self._specialType == IDLMethod.NamedOrIndexed.Named or \ + self._specialType == IDLMethod.NamedOrIndexed.Indexed + return self._specialType == IDLMethod.NamedOrIndexed.Named + + def isIndexed(self): + assert self._specialType == IDLMethod.NamedOrIndexed.Named or \ + self._specialType == IDLMethod.NamedOrIndexed.Indexed + return self._specialType == IDLMethod.NamedOrIndexed.Indexed + + def isLegacycaller(self): + return self._legacycaller + + def isStringifier(self): + return self._stringifier + + def isJsonifier(self): + return self._jsonifier + + def hasOverloads(self): + return self._hasOverloads + + def isIdentifierLess(self): + return self.identifier.name[:2] == "__" and self.identifier.name != "__noSuchMethod__" + + def resolve(self, parentScope): + assert isinstance(parentScope, IDLScope) + IDLObjectWithIdentifier.resolve(self, parentScope) + IDLScope.__init__(self, self.location, parentScope, self.identifier) + for (returnType, arguments) in self.signatures(): + for argument in arguments: + argument.resolve(self) + + def addOverload(self, method): + assert len(method._overloads) == 1 + + if self._extendedAttrDict != method ._extendedAttrDict: + raise WebIDLError("Extended attributes differ on different " + "overloads of %s" % method.identifier, + [self.location, method.location]) + + self._overloads.extend(method._overloads) + + self._hasOverloads = True + + if self.isStatic() != method.isStatic(): + raise WebIDLError("Overloaded identifier %s appears with different values of the 'static' attribute" % method.identifier, + [method.location]) + + if self.isLegacycaller() != method.isLegacycaller(): + raise WebIDLError("Overloaded identifier %s appears with different values of the 'legacycaller' attribute" % method.identifier, + [method.location]) + + # Can't overload special things! + assert not self.isGetter() + assert not method.isGetter() + assert not self.isSetter() + assert not method.isSetter() + assert not self.isCreator() + assert not method.isCreator() + assert not self.isDeleter() + assert not method.isDeleter() + assert not self.isStringifier() + assert not method.isStringifier() + assert not self.isJsonifier() + assert not method.isJsonifier() + + return self + + def signatures(self): + return [(overload.returnType, overload.arguments) for overload in + self._overloads] + + def finish(self, scope): + IDLInterfaceMember.finish(self, scope) + + overloadWithPromiseReturnType = None + overloadWithoutPromiseReturnType = None + for overload in self._overloads: + variadicArgument = None + + arguments = overload.arguments + for (idx, argument) in enumerate(arguments): + if not argument.isComplete(): + argument.complete(scope) + assert argument.type.isComplete() + + if (argument.type.isDictionary() or + (argument.type.isUnion() and + argument.type.unroll().hasDictionaryType)): + # Dictionaries and unions containing dictionaries at the + # end of the list or followed by optional arguments must be + # optional. + if (not argument.optional and + all(arg.optional for arg in arguments[idx+1:])): + raise WebIDLError("Dictionary argument or union " + "argument containing a dictionary " + "not followed by a required argument " + "must be optional", + [argument.location]) + + # An argument cannot be a Nullable Dictionary + if argument.type.nullable(): + raise WebIDLError("An argument cannot be a nullable " + "dictionary or nullable union " + "containing a dictionary", + [argument.location]) + + # Only the last argument can be variadic + if variadicArgument: + raise WebIDLError("Variadic argument is not last argument", + [variadicArgument.location]) + if argument.variadic: + variadicArgument = argument + + returnType = overload.returnType + if not returnType.isComplete(): + returnType = returnType.complete(scope) + assert not isinstance(returnType, IDLUnresolvedType) + assert not isinstance(returnType, IDLTypedefType) + assert not isinstance(returnType.name, IDLUnresolvedIdentifier) + overload.returnType = returnType + + if returnType.isPromise(): + overloadWithPromiseReturnType = overload + else: + overloadWithoutPromiseReturnType = overload + + # Make sure either all our overloads return Promises or none do + if overloadWithPromiseReturnType and overloadWithoutPromiseReturnType: + raise WebIDLError("We have overloads with both Promise and " + "non-Promise return types", + [overloadWithPromiseReturnType.location, + overloadWithoutPromiseReturnType.location]) + + if overloadWithPromiseReturnType and self._legacycaller: + raise WebIDLError("May not have a Promise return type for a " + "legacycaller.", + [overloadWithPromiseReturnType.location]) + + # Now compute various information that will be used by the + # WebIDL overload resolution algorithm. + self.maxArgCount = max(len(s[1]) for s in self.signatures()) + self.allowedArgCounts = [ i for i in range(self.maxArgCount+1) + if len(self.signaturesForArgCount(i)) != 0 ] + + def validate(self): + IDLInterfaceMember.validate(self) + + # Make sure our overloads are properly distinguishable and don't have + # different argument types before the distinguishing args. + for argCount in self.allowedArgCounts: + possibleOverloads = self.overloadsForArgCount(argCount) + if len(possibleOverloads) == 1: + continue + distinguishingIndex = self.distinguishingIndexForArgCount(argCount) + for idx in range(distinguishingIndex): + firstSigType = possibleOverloads[0].arguments[idx].type + for overload in possibleOverloads[1:]: + if overload.arguments[idx].type != firstSigType: + raise WebIDLError( + "Signatures for method '%s' with %d arguments have " + "different types of arguments at index %d, which " + "is before distinguishing index %d" % + (self.identifier.name, argCount, idx, + distinguishingIndex), + [self.location, overload.location]) + + for overload in self._overloads: + if not overload.returnType.unroll().isExposedInAllOf(self.exposureSet): + raise WebIDLError("Overload returns a type that is not exposed " + "everywhere where the method is exposed", + [overload.location]) + + def overloadsForArgCount(self, argc): + return [overload for overload in self._overloads if + len(overload.arguments) == argc or + (len(overload.arguments) > argc and + all(arg.optional for arg in overload.arguments[argc:])) or + (len(overload.arguments) < argc and + len(overload.arguments) > 0 and + overload.arguments[-1].variadic)] + + def signaturesForArgCount(self, argc): + return [(overload.returnType, overload.arguments) for overload + in self.overloadsForArgCount(argc)] + + def locationsForArgCount(self, argc): + return [overload.location for overload in self.overloadsForArgCount(argc)] + + def distinguishingIndexForArgCount(self, argc): + def isValidDistinguishingIndex(idx, signatures): + for (firstSigIndex, (firstRetval, firstArgs)) in enumerate(signatures[:-1]): + for (secondRetval, secondArgs) in signatures[firstSigIndex+1:]: + if idx < len(firstArgs): + firstType = firstArgs[idx].type + else: + assert(firstArgs[-1].variadic) + firstType = firstArgs[-1].type + if idx < len(secondArgs): + secondType = secondArgs[idx].type + else: + assert(secondArgs[-1].variadic) + secondType = secondArgs[-1].type + if not firstType.isDistinguishableFrom(secondType): + return False + return True + signatures = self.signaturesForArgCount(argc) + for idx in range(argc): + if isValidDistinguishingIndex(idx, signatures): + return idx + # No valid distinguishing index. Time to throw + locations = self.locationsForArgCount(argc) + raise WebIDLError("Signatures with %d arguments for method '%s' are not " + "distinguishable" % (argc, self.identifier.name), + locations) + + def handleExtendedAttribute(self, attr): + identifier = attr.identifier() + if identifier == "GetterThrows": + raise WebIDLError("Methods must not be flagged as " + "[GetterThrows]", + [attr.location, self.location]) + elif identifier == "SetterThrows": + raise WebIDLError("Methods must not be flagged as " + "[SetterThrows]", + [attr.location, self.location]) + elif identifier == "Unforgeable": + if self.isStatic(): + raise WebIDLError("[Unforgeable] is only allowed on non-static " + "methods", [attr.location, self.location]) + self._unforgeable = True + elif identifier == "SameObject": + raise WebIDLError("Methods must not be flagged as [SameObject]", + [attr.location, self.location]); + elif identifier == "Constant": + raise WebIDLError("Methods must not be flagged as [Constant]", + [attr.location, self.location]); + elif identifier == "PutForwards": + raise WebIDLError("Only attributes support [PutForwards]", + [attr.location, self.location]) + elif identifier == "LenientFloat": + # This is called before we've done overload resolution + assert len(self.signatures()) == 1 + sig = self.signatures()[0] + if not sig[0].isVoid(): + raise WebIDLError("[LenientFloat] used on a non-void method", + [attr.location, self.location]) + if not any(arg.type.includesRestrictedFloat() for arg in sig[1]): + raise WebIDLError("[LenientFloat] used on an operation with no " + "restricted float type arguments", + [attr.location, self.location]) + elif identifier == "Exposed": + convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames) + elif (identifier == "Pure" or + identifier == "CrossOriginCallable" or + identifier == "WebGLHandlesContextLoss"): + # Known no-argument attributes. + if not attr.noArguments(): + raise WebIDLError("[%s] must take no arguments" % identifier, + [attr.location]) + elif (identifier == "Throws" or + identifier == "NewObject" or + identifier == "ChromeOnly" or + identifier == "Pref" or + identifier == "Func" or + identifier == "AvailableIn" or + identifier == "CheckPermissions"): + # Known attributes that we don't need to do anything with here + pass + else: + raise WebIDLError("Unknown extended attribute %s on method" % identifier, + [attr.location]) + IDLInterfaceMember.handleExtendedAttribute(self, attr) + + def returnsPromise(self): + return self._overloads[0].returnType.isPromise() + + def isUnforgeable(self): + return self._unforgeable + + def _getDependentObjects(self): + deps = set() + for overload in self._overloads: + deps.union(overload._getDependentObjects()) + return deps + +class IDLImplementsStatement(IDLObject): + def __init__(self, location, implementor, implementee): + IDLObject.__init__(self, location) + self.implementor = implementor; + self.implementee = implementee + + def finish(self, scope): + assert(isinstance(self.implementor, IDLIdentifierPlaceholder)) + assert(isinstance(self.implementee, IDLIdentifierPlaceholder)) + implementor = self.implementor.finish(scope) + implementee = self.implementee.finish(scope) + # NOTE: we depend on not setting self.implementor and + # self.implementee here to keep track of the original + # locations. + if not isinstance(implementor, IDLInterface): + raise WebIDLError("Left-hand side of 'implements' is not an " + "interface", + [self.implementor.location]) + if implementor.isCallback(): + raise WebIDLError("Left-hand side of 'implements' is a callback " + "interface", + [self.implementor.location]) + if not isinstance(implementee, IDLInterface): + raise WebIDLError("Right-hand side of 'implements' is not an " + "interface", + [self.implementee.location]) + if implementee.isCallback(): + raise WebIDLError("Right-hand side of 'implements' is a callback " + "interface", + [self.implementee.location]) + implementor.addImplementedInterface(implementee) + + def validate(self): + pass + + def addExtendedAttributes(self, attrs): + assert len(attrs) == 0 + +class IDLExtendedAttribute(IDLObject): + """ + A class to represent IDL extended attributes so we can give them locations + """ + def __init__(self, location, tuple): + IDLObject.__init__(self, location) + self._tuple = tuple + + def identifier(self): + return self._tuple[0] + + def noArguments(self): + return len(self._tuple) == 1 + + def hasValue(self): + return len(self._tuple) >= 2 and isinstance(self._tuple[1], str) + + def value(self): + assert(self.hasValue()) + return self._tuple[1] + + def hasArgs(self): + return (len(self._tuple) == 2 and isinstance(self._tuple[1], list) or + len(self._tuple) == 3) + + def args(self): + assert(self.hasArgs()) + # Our args are our last element + return self._tuple[-1] + + def listValue(self): + """ + Backdoor for storing random data in _extendedAttrDict + """ + return list(self._tuple)[1:] + +# Parser + +class Tokenizer(object): + tokens = [ + "INTEGER", + "FLOATLITERAL", + "IDENTIFIER", + "STRING", + "WHITESPACE", + "OTHER" + ] + + def t_FLOATLITERAL(self, t): + r'(-?(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][+-]?[0-9]+)?|[0-9]+[Ee][+-]?[0-9]+|Infinity))|NaN' + t.value = float(t.value) + return t + + def t_INTEGER(self, t): + r'-?(0([0-7]+|[Xx][0-9A-Fa-f]+)?|[1-9][0-9]*)' + try: + # Can't use int(), because that doesn't handle octal properly. + t.value = parseInt(t.value) + except: + raise WebIDLError("Invalid integer literal", + [Location(lexer=self.lexer, + lineno=self.lexer.lineno, + lexpos=self.lexer.lexpos, + filename=self._filename)]) + return t + + def t_IDENTIFIER(self, t): + r'[A-Z_a-z][0-9A-Z_a-z-]*' + t.type = self.keywords.get(t.value, 'IDENTIFIER') + return t + + def t_STRING(self, t): + r'"[^"]*"' + t.value = t.value[1:-1] + return t + + def t_WHITESPACE(self, t): + r'[\t\n\r ]+|[\t\n\r ]*((//[^\n]*|/\*.*?\*/)[\t\n\r ]*)+' + pass + + def t_ELLIPSIS(self, t): + r'\.\.\.' + t.type = self.keywords.get(t.value) + return t + + def t_OTHER(self, t): + r'[^\t\n\r 0-9A-Z_a-z]' + t.type = self.keywords.get(t.value, 'OTHER') + return t + + keywords = { + "module": "MODULE", + "interface": "INTERFACE", + "partial": "PARTIAL", + "dictionary": "DICTIONARY", + "exception": "EXCEPTION", + "enum": "ENUM", + "callback": "CALLBACK", + "typedef": "TYPEDEF", + "implements": "IMPLEMENTS", + "const": "CONST", + "null": "NULL", + "true": "TRUE", + "false": "FALSE", + "serializer": "SERIALIZER", + "stringifier": "STRINGIFIER", + "jsonifier": "JSONIFIER", + "unrestricted": "UNRESTRICTED", + "attribute": "ATTRIBUTE", + "readonly": "READONLY", + "inherit": "INHERIT", + "static": "STATIC", + "getter": "GETTER", + "setter": "SETTER", + "creator": "CREATOR", + "deleter": "DELETER", + "legacycaller": "LEGACYCALLER", + "optional": "OPTIONAL", + "...": "ELLIPSIS", + "::": "SCOPE", + "Date": "DATE", + "DOMString": "DOMSTRING", + "ByteString": "BYTESTRING", + "ScalarValueString": "SCALARVALUESTRING", + "any": "ANY", + "boolean": "BOOLEAN", + "byte": "BYTE", + "double": "DOUBLE", + "float": "FLOAT", + "long": "LONG", + "object": "OBJECT", + "octet": "OCTET", + "optional": "OPTIONAL", + "Promise": "PROMISE", + "sequence": "SEQUENCE", + "MozMap": "MOZMAP", + "short": "SHORT", + "unsigned": "UNSIGNED", + "void": "VOID", + ":": "COLON", + ";": "SEMICOLON", + "{": "LBRACE", + "}": "RBRACE", + "(": "LPAREN", + ")": "RPAREN", + "[": "LBRACKET", + "]": "RBRACKET", + "?": "QUESTIONMARK", + ",": "COMMA", + "=": "EQUALS", + "<": "LT", + ">": "GT", + "ArrayBuffer": "ARRAYBUFFER", + "or": "OR" + } + + tokens.extend(keywords.values()) + + def t_error(self, t): + raise WebIDLError("Unrecognized Input", + [Location(lexer=self.lexer, + lineno=self.lexer.lineno, + lexpos=self.lexer.lexpos, + filename = self.filename)]) + + def __init__(self, outputdir, lexer=None): + if lexer: + self.lexer = lexer + else: + self.lexer = lex.lex(object=self, + outputdir=outputdir, + lextab='webidllex', + reflags=re.DOTALL) + +class SqueakyCleanLogger(object): + errorWhitelist = [ + # Web IDL defines the WHITESPACE token, but doesn't actually + # use it ... so far. + "Token 'WHITESPACE' defined, but not used", + # And that means we have an unused token + "There is 1 unused token", + # Web IDL defines a OtherOrComma rule that's only used in + # ExtendedAttributeInner, which we don't use yet. + "Rule 'OtherOrComma' defined, but not used", + # And an unused rule + "There is 1 unused rule", + # And the OtherOrComma grammar symbol is unreachable. + "Symbol 'OtherOrComma' is unreachable", + # Which means the Other symbol is unreachable. + "Symbol 'Other' is unreachable", + ] + def __init__(self): + self.errors = [] + def debug(self, msg, *args, **kwargs): + pass + info = debug + def warning(self, msg, *args, **kwargs): + if msg == "%s:%d: Rule '%s' defined, but not used": + # Munge things so we don't have to hardcode filenames and + # line numbers in our whitelist. + whitelistmsg = "Rule '%s' defined, but not used" + whitelistargs = args[2:] + else: + whitelistmsg = msg + whitelistargs = args + if (whitelistmsg % whitelistargs) not in SqueakyCleanLogger.errorWhitelist: + self.errors.append(msg % args) + error = warning + + def reportGrammarErrors(self): + if self.errors: + raise WebIDLError("\n".join(self.errors), []) + +class Parser(Tokenizer): + def getLocation(self, p, i): + return Location(self.lexer, p.lineno(i), p.lexpos(i), self._filename) + + def globalScope(self): + return self._globalScope + + # The p_Foo functions here must match the WebIDL spec's grammar. + # It's acceptable to split things at '|' boundaries. + def p_Definitions(self, p): + """ + Definitions : ExtendedAttributeList Definition Definitions + """ + if p[2]: + p[0] = [p[2]] + p[2].addExtendedAttributes(p[1]) + else: + assert not p[1] + p[0] = [] + + p[0].extend(p[3]) + + def p_DefinitionsEmpty(self, p): + """ + Definitions : + """ + p[0] = [] + + def p_Definition(self, p): + """ + Definition : CallbackOrInterface + | PartialInterface + | Dictionary + | Exception + | Enum + | Typedef + | ImplementsStatement + """ + p[0] = p[1] + assert p[1] # We might not have implemented something ... + + def p_CallbackOrInterfaceCallback(self, p): + """ + CallbackOrInterface : CALLBACK CallbackRestOrInterface + """ + if p[2].isInterface(): + assert isinstance(p[2], IDLInterface) + p[2].setCallback(True) + + p[0] = p[2] + + def p_CallbackOrInterfaceInterface(self, p): + """ + CallbackOrInterface : Interface + """ + p[0] = p[1] + + def p_CallbackRestOrInterface(self, p): + """ + CallbackRestOrInterface : CallbackRest + | Interface + """ + assert p[1] + p[0] = p[1] + + def p_Interface(self, p): + """ + Interface : INTERFACE IDENTIFIER Inheritance LBRACE InterfaceMembers RBRACE SEMICOLON + """ + location = self.getLocation(p, 1) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + members = p[5] + parent = p[3] + + try: + existingObj = self.globalScope()._lookupIdentifier(identifier) + if existingObj: + p[0] = existingObj + if not isinstance(p[0], IDLInterface): + raise WebIDLError("Interface has the same name as " + "non-interface object", + [location, p[0].location]) + p[0].setNonPartial(location, parent, members) + return + except Exception, ex: + if isinstance(ex, WebIDLError): + raise ex + pass + + p[0] = IDLInterface(location, self.globalScope(), identifier, parent, + members, isKnownNonPartial=True) + + def p_InterfaceForwardDecl(self, p): + """ + Interface : INTERFACE IDENTIFIER SEMICOLON + """ + location = self.getLocation(p, 1) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + + try: + if self.globalScope()._lookupIdentifier(identifier): + p[0] = self.globalScope()._lookupIdentifier(identifier) + if not isinstance(p[0], IDLExternalInterface): + raise WebIDLError("Name collision between external " + "interface declaration for identifier " + "%s and %s" % (identifier.name, p[0]), + [location, p[0].location]) + return + except Exception, ex: + if isinstance(ex, WebIDLError): + raise ex + pass + + p[0] = IDLExternalInterface(location, self.globalScope(), identifier) + + def p_PartialInterface(self, p): + """ + PartialInterface : PARTIAL INTERFACE IDENTIFIER LBRACE InterfaceMembers RBRACE SEMICOLON + """ + location = self.getLocation(p, 2) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 3), p[3]) + members = p[5] + + nonPartialInterface = None + try: + nonPartialInterface = self.globalScope()._lookupIdentifier(identifier) + if nonPartialInterface: + if not isinstance(nonPartialInterface, IDLInterface): + raise WebIDLError("Partial interface has the same name as " + "non-interface object", + [location, nonPartialInterface.location]) + except Exception, ex: + if isinstance(ex, WebIDLError): + raise ex + pass + + if not nonPartialInterface: + nonPartialInterface = IDLInterface(location, self.globalScope(), + identifier, None, + [], isKnownNonPartial=False) + partialInterface = IDLPartialInterface(location, identifier, members, + nonPartialInterface) + p[0] = partialInterface + + def p_Inheritance(self, p): + """ + Inheritance : COLON ScopedName + """ + p[0] = IDLIdentifierPlaceholder(self.getLocation(p, 2), p[2]) + + def p_InheritanceEmpty(self, p): + """ + Inheritance : + """ + pass + + def p_InterfaceMembers(self, p): + """ + InterfaceMembers : ExtendedAttributeList InterfaceMember InterfaceMembers + """ + p[0] = [p[2]] if p[2] else [] + + assert not p[1] or p[2] + p[2].addExtendedAttributes(p[1]) + + p[0].extend(p[3]) + + def p_InterfaceMembersEmpty(self, p): + """ + InterfaceMembers : + """ + p[0] = [] + + def p_InterfaceMember(self, p): + """ + InterfaceMember : Const + | AttributeOrOperation + """ + p[0] = p[1] + + def p_Dictionary(self, p): + """ + Dictionary : DICTIONARY IDENTIFIER Inheritance LBRACE DictionaryMembers RBRACE SEMICOLON + """ + location = self.getLocation(p, 1) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + members = p[5] + p[0] = IDLDictionary(location, self.globalScope(), identifier, p[3], members) + + def p_DictionaryMembers(self, p): + """ + DictionaryMembers : ExtendedAttributeList DictionaryMember DictionaryMembers + | + """ + if len(p) == 1: + # We're at the end of the list + p[0] = [] + return + # Add our extended attributes + p[2].addExtendedAttributes(p[1]) + p[0] = [p[2]] + p[0].extend(p[3]) + + def p_DictionaryMember(self, p): + """ + DictionaryMember : Type IDENTIFIER Default SEMICOLON + """ + # These quack a lot like optional arguments, so just treat them that way. + t = p[1] + assert isinstance(t, IDLType) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + defaultValue = p[3] + + p[0] = IDLArgument(self.getLocation(p, 2), identifier, t, optional=True, + defaultValue=defaultValue, variadic=False, + dictionaryMember=True) + + def p_Default(self, p): + """ + Default : EQUALS DefaultValue + | + """ + if len(p) > 1: + p[0] = p[2] + else: + p[0] = None + + def p_DefaultValue(self, p): + """ + DefaultValue : ConstValue + | LBRACKET RBRACKET + """ + if len(p) == 2: + p[0] = p[1] + else: + assert len(p) == 3 # Must be [] + p[0] = IDLEmptySequenceValue(self.getLocation(p, 1)) + + def p_Exception(self, p): + """ + Exception : EXCEPTION IDENTIFIER Inheritance LBRACE ExceptionMembers RBRACE SEMICOLON + """ + pass + + def p_Enum(self, p): + """ + Enum : ENUM IDENTIFIER LBRACE EnumValueList RBRACE SEMICOLON + """ + location = self.getLocation(p, 1) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) + + values = p[4] + assert values + p[0] = IDLEnum(location, self.globalScope(), identifier, values) + + def p_EnumValueList(self, p): + """ + EnumValueList : STRING EnumValueListComma + """ + p[0] = [p[1]] + p[0].extend(p[2]) + + def p_EnumValueListComma(self, p): + """ + EnumValueListComma : COMMA EnumValueListString + """ + p[0] = p[2] + + def p_EnumValueListCommaEmpty(self, p): + """ + EnumValueListComma : + """ + p[0] = [] + + def p_EnumValueListString(self, p): + """ + EnumValueListString : STRING EnumValueListComma + """ + p[0] = [p[1]] + p[0].extend(p[2]) + + def p_EnumValueListStringEmpty(self, p): + """ + EnumValueListString : + """ + p[0] = [] + + def p_CallbackRest(self, p): + """ + CallbackRest : IDENTIFIER EQUALS ReturnType LPAREN ArgumentList RPAREN SEMICOLON + """ + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1]) + p[0] = IDLCallbackType(self.getLocation(p, 1), self.globalScope(), + identifier, p[3], p[5]) + + def p_ExceptionMembers(self, p): + """ + ExceptionMembers : ExtendedAttributeList ExceptionMember ExceptionMembers + | + """ + pass + + def p_Typedef(self, p): + """ + Typedef : TYPEDEF Type IDENTIFIER SEMICOLON + """ + typedef = IDLTypedefType(self.getLocation(p, 1), p[2], p[3]) + typedef.resolve(self.globalScope()) + p[0] = typedef + + def p_ImplementsStatement(self, p): + """ + ImplementsStatement : ScopedName IMPLEMENTS ScopedName SEMICOLON + """ + assert(p[2] == "implements") + implementor = IDLIdentifierPlaceholder(self.getLocation(p, 1), p[1]) + implementee = IDLIdentifierPlaceholder(self.getLocation(p, 3), p[3]) + p[0] = IDLImplementsStatement(self.getLocation(p, 1), implementor, + implementee) + + def p_Const(self, p): + """ + Const : CONST ConstType IDENTIFIER EQUALS ConstValue SEMICOLON + """ + location = self.getLocation(p, 1) + type = p[2] + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 3), p[3]) + value = p[5] + p[0] = IDLConst(location, identifier, type, value) + + def p_ConstValueBoolean(self, p): + """ + ConstValue : BooleanLiteral + """ + location = self.getLocation(p, 1) + booleanType = BuiltinTypes[IDLBuiltinType.Types.boolean] + p[0] = IDLValue(location, booleanType, p[1]) + + def p_ConstValueInteger(self, p): + """ + ConstValue : INTEGER + """ + location = self.getLocation(p, 1) + + # We don't know ahead of time what type the integer literal is. + # Determine the smallest type it could possibly fit in and use that. + integerType = matchIntegerValueToType(p[1]) + if integerType == None: + raise WebIDLError("Integer literal out of range", [location]) + + p[0] = IDLValue(location, integerType, p[1]) + + def p_ConstValueFloat(self, p): + """ + ConstValue : FLOATLITERAL + """ + location = self.getLocation(p, 1) + p[0] = IDLValue(location, BuiltinTypes[IDLBuiltinType.Types.unrestricted_float], p[1]) + + def p_ConstValueString(self, p): + """ + ConstValue : STRING + """ + location = self.getLocation(p, 1) + stringType = BuiltinTypes[IDLBuiltinType.Types.domstring] + p[0] = IDLValue(location, stringType, p[1]) + + def p_ConstValueNull(self, p): + """ + ConstValue : NULL + """ + p[0] = IDLNullValue(self.getLocation(p, 1)) + + def p_BooleanLiteralTrue(self, p): + """ + BooleanLiteral : TRUE + """ + p[0] = True + + def p_BooleanLiteralFalse(self, p): + """ + BooleanLiteral : FALSE + """ + p[0] = False + + def p_AttributeOrOperation(self, p): + """ + AttributeOrOperation : Attribute + | Operation + """ + p[0] = p[1] + + def p_AttributeWithQualifier(self, p): + """ + Attribute : Qualifier AttributeRest + """ + static = IDLInterfaceMember.Special.Static in p[1] + stringifier = IDLInterfaceMember.Special.Stringifier in p[1] + (location, identifier, type, readonly) = p[2] + p[0] = IDLAttribute(location, identifier, type, readonly, static=static, + stringifier=stringifier) + + def p_Attribute(self, p): + """ + Attribute : Inherit AttributeRest + """ + (location, identifier, type, readonly) = p[2] + p[0] = IDLAttribute(location, identifier, type, readonly, inherit=p[1]) + + def p_AttributeRest(self, p): + """ + AttributeRest : ReadOnly ATTRIBUTE Type IDENTIFIER SEMICOLON + """ + location = self.getLocation(p, 2) + readonly = p[1] + t = p[3] + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 4), p[4]) + p[0] = (location, identifier, t, readonly) + + def p_ReadOnly(self, p): + """ + ReadOnly : READONLY + """ + p[0] = True + + def p_ReadOnlyEmpty(self, p): + """ + ReadOnly : + """ + p[0] = False + + def p_Inherit(self, p): + """ + Inherit : INHERIT + """ + p[0] = True + + def p_InheritEmpty(self, p): + """ + Inherit : + """ + p[0] = False + + def p_Operation(self, p): + """ + Operation : Qualifiers OperationRest + """ + qualifiers = p[1] + + # Disallow duplicates in the qualifier set + if not len(set(qualifiers)) == len(qualifiers): + raise WebIDLError("Duplicate qualifiers are not allowed", + [self.getLocation(p, 1)]) + + static = IDLInterfaceMember.Special.Static in p[1] + # If static is there that's all that's allowed. This is disallowed + # by the parser, so we can assert here. + assert not static or len(qualifiers) == 1 + + stringifier = IDLInterfaceMember.Special.Stringifier in p[1] + # If stringifier is there that's all that's allowed. This is disallowed + # by the parser, so we can assert here. + assert not stringifier or len(qualifiers) == 1 + + getter = True if IDLMethod.Special.Getter in p[1] else False + setter = True if IDLMethod.Special.Setter in p[1] else False + creator = True if IDLMethod.Special.Creator in p[1] else False + deleter = True if IDLMethod.Special.Deleter in p[1] else False + legacycaller = True if IDLMethod.Special.LegacyCaller in p[1] else False + + if getter or deleter: + if setter or creator: + raise WebIDLError("getter and deleter are incompatible with setter and creator", + [self.getLocation(p, 1)]) + + (returnType, identifier, arguments) = p[2] + + assert isinstance(returnType, IDLType) + + specialType = IDLMethod.NamedOrIndexed.Neither + + if getter or deleter: + if len(arguments) != 1: + raise WebIDLError("%s has wrong number of arguments" % + ("getter" if getter else "deleter"), + [self.getLocation(p, 2)]) + argType = arguments[0].type + if argType == BuiltinTypes[IDLBuiltinType.Types.domstring]: + specialType = IDLMethod.NamedOrIndexed.Named + elif argType == BuiltinTypes[IDLBuiltinType.Types.unsigned_long]: + specialType = IDLMethod.NamedOrIndexed.Indexed + else: + raise WebIDLError("%s has wrong argument type (must be DOMString or UnsignedLong)" % + ("getter" if getter else "deleter"), + [arguments[0].location]) + if arguments[0].optional or arguments[0].variadic: + raise WebIDLError("%s cannot have %s argument" % + ("getter" if getter else "deleter", + "optional" if arguments[0].optional else "variadic"), + [arguments[0].location]) + if getter: + if returnType.isVoid(): + raise WebIDLError("getter cannot have void return type", + [self.getLocation(p, 2)]) + if setter or creator: + if len(arguments) != 2: + raise WebIDLError("%s has wrong number of arguments" % + ("setter" if setter else "creator"), + [self.getLocation(p, 2)]) + argType = arguments[0].type + if argType == BuiltinTypes[IDLBuiltinType.Types.domstring]: + specialType = IDLMethod.NamedOrIndexed.Named + elif argType == BuiltinTypes[IDLBuiltinType.Types.unsigned_long]: + specialType = IDLMethod.NamedOrIndexed.Indexed + else: + raise WebIDLError("%s has wrong argument type (must be DOMString or UnsignedLong)" % + ("setter" if setter else "creator"), + [arguments[0].location]) + if arguments[0].optional or arguments[0].variadic: + raise WebIDLError("%s cannot have %s argument" % + ("setter" if setter else "creator", + "optional" if arguments[0].optional else "variadic"), + [arguments[0].location]) + if arguments[1].optional or arguments[1].variadic: + raise WebIDLError("%s cannot have %s argument" % + ("setter" if setter else "creator", + "optional" if arguments[1].optional else "variadic"), + [arguments[1].location]) + + if stringifier: + if len(arguments) != 0: + raise WebIDLError("stringifier has wrong number of arguments", + [self.getLocation(p, 2)]) + if not returnType.isDOMString(): + raise WebIDLError("stringifier must have DOMString return type", + [self.getLocation(p, 2)]) + + # identifier might be None. This is only permitted for special methods. + if not identifier: + if not getter and not setter and not creator and \ + not deleter and not legacycaller and not stringifier: + raise WebIDLError("Identifier required for non-special methods", + [self.getLocation(p, 2)]) + + location = BuiltinLocation("<auto-generated-identifier>") + identifier = IDLUnresolvedIdentifier(location, "__%s%s%s%s%s%s%s" % + ("named" if specialType == IDLMethod.NamedOrIndexed.Named else \ + "indexed" if specialType == IDLMethod.NamedOrIndexed.Indexed else "", + "getter" if getter else "", + "setter" if setter else "", + "deleter" if deleter else "", + "creator" if creator else "", + "legacycaller" if legacycaller else "", + "stringifier" if stringifier else ""), allowDoubleUnderscore=True) + + method = IDLMethod(self.getLocation(p, 2), identifier, returnType, arguments, + static=static, getter=getter, setter=setter, creator=creator, + deleter=deleter, specialType=specialType, + legacycaller=legacycaller, stringifier=stringifier) + p[0] = method + + def p_Stringifier(self, p): + """ + Operation : STRINGIFIER SEMICOLON + """ + identifier = IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"), + "__stringifier", + allowDoubleUnderscore=True) + method = IDLMethod(self.getLocation(p, 1), + identifier, + returnType=BuiltinTypes[IDLBuiltinType.Types.domstring], + arguments=[], + stringifier=True) + p[0] = method + + def p_Jsonifier(self, p): + """ + Operation : JSONIFIER SEMICOLON + """ + identifier = IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"), + "__jsonifier", allowDoubleUnderscore=True) + method = IDLMethod(self.getLocation(p, 1), + identifier, + returnType=BuiltinTypes[IDLBuiltinType.Types.object], + arguments=[], + jsonifier=True) + p[0] = method + + def p_QualifierStatic(self, p): + """ + Qualifier : STATIC + """ + p[0] = [IDLInterfaceMember.Special.Static] + + def p_QualifierStringifier(self, p): + """ + Qualifier : STRINGIFIER + """ + p[0] = [IDLInterfaceMember.Special.Stringifier] + + def p_Qualifiers(self, p): + """ + Qualifiers : Qualifier + | Specials + """ + p[0] = p[1] + + def p_Specials(self, p): + """ + Specials : Special Specials + """ + p[0] = [p[1]] + p[0].extend(p[2]) + + def p_SpecialsEmpty(self, p): + """ + Specials : + """ + p[0] = [] + + def p_SpecialGetter(self, p): + """ + Special : GETTER + """ + p[0] = IDLMethod.Special.Getter + + def p_SpecialSetter(self, p): + """ + Special : SETTER + """ + p[0] = IDLMethod.Special.Setter + + def p_SpecialCreator(self, p): + """ + Special : CREATOR + """ + p[0] = IDLMethod.Special.Creator + + def p_SpecialDeleter(self, p): + """ + Special : DELETER + """ + p[0] = IDLMethod.Special.Deleter + + def p_SpecialLegacyCaller(self, p): + """ + Special : LEGACYCALLER + """ + p[0] = IDLMethod.Special.LegacyCaller + + def p_OperationRest(self, p): + """ + OperationRest : ReturnType OptionalIdentifier LPAREN ArgumentList RPAREN SEMICOLON + """ + p[0] = (p[1], p[2], p[4]) + + def p_OptionalIdentifier(self, p): + """ + OptionalIdentifier : IDENTIFIER + """ + p[0] = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1]) + + def p_OptionalIdentifierEmpty(self, p): + """ + OptionalIdentifier : + """ + pass + + def p_ArgumentList(self, p): + """ + ArgumentList : Argument Arguments + """ + p[0] = [p[1]] if p[1] else [] + p[0].extend(p[2]) + + def p_ArgumentListEmpty(self, p): + """ + ArgumentList : + """ + p[0] = [] + + def p_Arguments(self, p): + """ + Arguments : COMMA Argument Arguments + """ + p[0] = [p[2]] if p[2] else [] + p[0].extend(p[3]) + + def p_ArgumentsEmpty(self, p): + """ + Arguments : + """ + p[0] = [] + + def p_Argument(self, p): + """ + Argument : ExtendedAttributeList Optional Type Ellipsis ArgumentName Default + """ + t = p[3] + assert isinstance(t, IDLType) + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 5), p[5]) + + optional = p[2] + variadic = p[4] + defaultValue = p[6] + + if not optional and defaultValue: + raise WebIDLError("Mandatory arguments can't have a default value.", + [self.getLocation(p, 6)]) + + # We can't test t.isAny() here and give it a default value as needed, + # since at this point t is not a fully resolved type yet (e.g. it might + # be a typedef). We'll handle the 'any' case in IDLArgument.complete. + + if variadic: + if optional: + raise WebIDLError("Variadic arguments should not be marked optional.", + [self.getLocation(p, 2)]) + optional = variadic + + p[0] = IDLArgument(self.getLocation(p, 5), identifier, t, optional, defaultValue, variadic) + p[0].addExtendedAttributes(p[1]) + + def p_ArgumentName(self, p): + """ + ArgumentName : IDENTIFIER + | ATTRIBUTE + | CALLBACK + | CONST + | CREATOR + | DELETER + | DICTIONARY + | ENUM + | EXCEPTION + | GETTER + | IMPLEMENTS + | INHERIT + | INTERFACE + | LEGACYCALLER + | PARTIAL + | SERIALIZER + | SETTER + | STATIC + | STRINGIFIER + | JSONIFIER + | TYPEDEF + | UNRESTRICTED + """ + p[0] = p[1] + + def p_Optional(self, p): + """ + Optional : OPTIONAL + """ + p[0] = True + + def p_OptionalEmpty(self, p): + """ + Optional : + """ + p[0] = False + + def p_Ellipsis(self, p): + """ + Ellipsis : ELLIPSIS + """ + p[0] = True + + def p_EllipsisEmpty(self, p): + """ + Ellipsis : + """ + p[0] = False + + def p_ExceptionMember(self, p): + """ + ExceptionMember : Const + | ExceptionField + """ + pass + + def p_ExceptionField(self, p): + """ + ExceptionField : Type IDENTIFIER SEMICOLON + """ + pass + + def p_ExtendedAttributeList(self, p): + """ + ExtendedAttributeList : LBRACKET ExtendedAttribute ExtendedAttributes RBRACKET + """ + p[0] = [p[2]] + if p[3]: + p[0].extend(p[3]) + + def p_ExtendedAttributeListEmpty(self, p): + """ + ExtendedAttributeList : + """ + p[0] = [] + + def p_ExtendedAttribute(self, p): + """ + ExtendedAttribute : ExtendedAttributeNoArgs + | ExtendedAttributeArgList + | ExtendedAttributeIdent + | ExtendedAttributeNamedArgList + | ExtendedAttributeIdentList + """ + p[0] = IDLExtendedAttribute(self.getLocation(p, 1), p[1]) + + def p_ExtendedAttributeEmpty(self, p): + """ + ExtendedAttribute : + """ + pass + + def p_ExtendedAttributes(self, p): + """ + ExtendedAttributes : COMMA ExtendedAttribute ExtendedAttributes + """ + p[0] = [p[2]] if p[2] else [] + p[0].extend(p[3]) + + def p_ExtendedAttributesEmpty(self, p): + """ + ExtendedAttributes : + """ + p[0] = [] + + def p_Other(self, p): + """ + Other : INTEGER + | FLOATLITERAL + | IDENTIFIER + | STRING + | OTHER + | ELLIPSIS + | COLON + | SCOPE + | SEMICOLON + | LT + | EQUALS + | GT + | QUESTIONMARK + | DATE + | DOMSTRING + | BYTESTRING + | SCALARVALUESTRING + | ANY + | ATTRIBUTE + | BOOLEAN + | BYTE + | LEGACYCALLER + | CONST + | CREATOR + | DELETER + | DOUBLE + | EXCEPTION + | FALSE + | FLOAT + | GETTER + | IMPLEMENTS + | INHERIT + | INTERFACE + | LONG + | MODULE + | NULL + | OBJECT + | OCTET + | OPTIONAL + | SEQUENCE + | MOZMAP + | SETTER + | SHORT + | STATIC + | STRINGIFIER + | JSONIFIER + | TRUE + | TYPEDEF + | UNSIGNED + | VOID + """ + pass + + def p_OtherOrComma(self, p): + """ + OtherOrComma : Other + | COMMA + """ + pass + + def p_TypeSingleType(self, p): + """ + Type : SingleType + """ + p[0] = p[1] + + def p_TypeUnionType(self, p): + """ + Type : UnionType TypeSuffix + """ + p[0] = self.handleModifiers(p[1], p[2]) + + def p_SingleTypeNonAnyType(self, p): + """ + SingleType : NonAnyType + """ + p[0] = p[1] + + def p_SingleTypeAnyType(self, p): + """ + SingleType : ANY TypeSuffixStartingWithArray + """ + p[0] = self.handleModifiers(BuiltinTypes[IDLBuiltinType.Types.any], p[2]) + + def p_UnionType(self, p): + """ + UnionType : LPAREN UnionMemberType OR UnionMemberType UnionMemberTypes RPAREN + """ + types = [p[2], p[4]] + types.extend(p[5]) + p[0] = IDLUnionType(self.getLocation(p, 1), types) + + def p_UnionMemberTypeNonAnyType(self, p): + """ + UnionMemberType : NonAnyType + """ + p[0] = p[1] + + def p_UnionMemberTypeArrayOfAny(self, p): + """ + UnionMemberTypeArrayOfAny : ANY LBRACKET RBRACKET + """ + p[0] = IDLArrayType(self.getLocation(p, 2), + BuiltinTypes[IDLBuiltinType.Types.any]) + + def p_UnionMemberType(self, p): + """ + UnionMemberType : UnionType TypeSuffix + | UnionMemberTypeArrayOfAny TypeSuffix + """ + p[0] = self.handleModifiers(p[1], p[2]) + + def p_UnionMemberTypes(self, p): + """ + UnionMemberTypes : OR UnionMemberType UnionMemberTypes + """ + p[0] = [p[2]] + p[0].extend(p[3]) + + def p_UnionMemberTypesEmpty(self, p): + """ + UnionMemberTypes : + """ + p[0] = [] + + def p_NonAnyType(self, p): + """ + NonAnyType : PrimitiveOrStringType TypeSuffix + | ARRAYBUFFER TypeSuffix + | OBJECT TypeSuffix + """ + if p[1] == "object": + type = BuiltinTypes[IDLBuiltinType.Types.object] + elif p[1] == "ArrayBuffer": + type = BuiltinTypes[IDLBuiltinType.Types.ArrayBuffer] + else: + type = BuiltinTypes[p[1]] + + p[0] = self.handleModifiers(type, p[2]) + + def p_NonAnyTypeSequenceType(self, p): + """ + NonAnyType : SEQUENCE LT Type GT Null + """ + innerType = p[3] + type = IDLSequenceType(self.getLocation(p, 1), innerType) + if p[5]: + type = IDLNullableType(self.getLocation(p, 5), type) + p[0] = type + + # Note: Promise<void> is allowed, so we want to parametrize on + # ReturnType, not Type. Also, we want this to end up picking up + # the Promise interface for now, hence the games with IDLUnresolvedType. + def p_NonAnyTypePromiseType(self, p): + """ + NonAnyType : PROMISE LT ReturnType GT Null + """ + innerType = p[3] + promiseIdent = IDLUnresolvedIdentifier(self.getLocation(p, 1), "Promise") + type = IDLUnresolvedType(self.getLocation(p, 1), promiseIdent, p[3]) + if p[5]: + type = IDLNullableType(self.getLocation(p, 5), type) + p[0] = type + + def p_NonAnyTypeMozMapType(self, p): + """ + NonAnyType : MOZMAP LT Type GT Null + """ + innerType = p[3] + type = IDLMozMapType(self.getLocation(p, 1), innerType) + if p[5]: + type = IDLNullableType(self.getLocation(p, 5), type) + p[0] = type + + def p_NonAnyTypeScopedName(self, p): + """ + NonAnyType : ScopedName TypeSuffix + """ + assert isinstance(p[1], IDLUnresolvedIdentifier) + + if p[1].name == "Promise": + raise WebIDLError("Promise used without saying what it's " + "parametrized over", + [self.getLocation(p, 1)]) + + type = None + + try: + if self.globalScope()._lookupIdentifier(p[1]): + obj = self.globalScope()._lookupIdentifier(p[1]) + if obj.isType(): + type = obj + else: + type = IDLWrapperType(self.getLocation(p, 1), p[1]) + p[0] = self.handleModifiers(type, p[2]) + return + except: + pass + + type = IDLUnresolvedType(self.getLocation(p, 1), p[1]) + p[0] = self.handleModifiers(type, p[2]) + + def p_NonAnyTypeDate(self, p): + """ + NonAnyType : DATE TypeSuffix + """ + p[0] = self.handleModifiers(BuiltinTypes[IDLBuiltinType.Types.date], + p[2]) + + def p_ConstType(self, p): + """ + ConstType : PrimitiveOrStringType Null + """ + type = BuiltinTypes[p[1]] + if p[2]: + type = IDLNullableType(self.getLocation(p, 1), type) + p[0] = type + + def p_ConstTypeIdentifier(self, p): + """ + ConstType : IDENTIFIER Null + """ + identifier = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1]) + + type = IDLUnresolvedType(self.getLocation(p, 1), identifier) + if p[2]: + type = IDLNullableType(self.getLocation(p, 1), type) + p[0] = type + + def p_PrimitiveOrStringTypeUint(self, p): + """ + PrimitiveOrStringType : UnsignedIntegerType + """ + p[0] = p[1] + + def p_PrimitiveOrStringTypeBoolean(self, p): + """ + PrimitiveOrStringType : BOOLEAN + """ + p[0] = IDLBuiltinType.Types.boolean + + def p_PrimitiveOrStringTypeByte(self, p): + """ + PrimitiveOrStringType : BYTE + """ + p[0] = IDLBuiltinType.Types.byte + + def p_PrimitiveOrStringTypeOctet(self, p): + """ + PrimitiveOrStringType : OCTET + """ + p[0] = IDLBuiltinType.Types.octet + + def p_PrimitiveOrStringTypeFloat(self, p): + """ + PrimitiveOrStringType : FLOAT + """ + p[0] = IDLBuiltinType.Types.float + + def p_PrimitiveOrStringTypeUnrestictedFloat(self, p): + """ + PrimitiveOrStringType : UNRESTRICTED FLOAT + """ + p[0] = IDLBuiltinType.Types.unrestricted_float + + def p_PrimitiveOrStringTypeDouble(self, p): + """ + PrimitiveOrStringType : DOUBLE + """ + p[0] = IDLBuiltinType.Types.double + + def p_PrimitiveOrStringTypeUnrestictedDouble(self, p): + """ + PrimitiveOrStringType : UNRESTRICTED DOUBLE + """ + p[0] = IDLBuiltinType.Types.unrestricted_double + + def p_PrimitiveOrStringTypeDOMString(self, p): + """ + PrimitiveOrStringType : DOMSTRING + """ + p[0] = IDLBuiltinType.Types.domstring + + def p_PrimitiveOrStringTypeBytestring(self, p): + """ + PrimitiveOrStringType : BYTESTRING + """ + p[0] = IDLBuiltinType.Types.bytestring + + def p_PrimitiveOrStringTypeScalarValueString(self, p): + """ + PrimitiveOrStringType : SCALARVALUESTRING + """ + p[0] = IDLBuiltinType.Types.scalarvaluestring + + def p_UnsignedIntegerTypeUnsigned(self, p): + """ + UnsignedIntegerType : UNSIGNED IntegerType + """ + p[0] = p[2] + 1 # Adding one to a given signed integer type + # gets you the unsigned type. + + def p_UnsignedIntegerType(self, p): + """ + UnsignedIntegerType : IntegerType + """ + p[0] = p[1] + + def p_IntegerTypeShort(self, p): + """ + IntegerType : SHORT + """ + p[0] = IDLBuiltinType.Types.short + + def p_IntegerTypeLong(self, p): + """ + IntegerType : LONG OptionalLong + """ + if p[2]: + p[0] = IDLBuiltinType.Types.long_long + else: + p[0] = IDLBuiltinType.Types.long + + def p_OptionalLong(self, p): + """ + OptionalLong : LONG + """ + p[0] = True + + def p_OptionalLongEmpty(self, p): + """ + OptionalLong : + """ + p[0] = False + + def p_TypeSuffixBrackets(self, p): + """ + TypeSuffix : LBRACKET RBRACKET TypeSuffix + """ + p[0] = [(IDLMethod.TypeSuffixModifier.Brackets, self.getLocation(p, 1))] + p[0].extend(p[3]) + + def p_TypeSuffixQMark(self, p): + """ + TypeSuffix : QUESTIONMARK TypeSuffixStartingWithArray + """ + p[0] = [(IDLMethod.TypeSuffixModifier.QMark, self.getLocation(p, 1))] + p[0].extend(p[2]) + + def p_TypeSuffixEmpty(self, p): + """ + TypeSuffix : + """ + p[0] = [] + + def p_TypeSuffixStartingWithArray(self, p): + """ + TypeSuffixStartingWithArray : LBRACKET RBRACKET TypeSuffix + """ + p[0] = [(IDLMethod.TypeSuffixModifier.Brackets, self.getLocation(p, 1))] + p[0].extend(p[3]) + + def p_TypeSuffixStartingWithArrayEmpty(self, p): + """ + TypeSuffixStartingWithArray : + """ + p[0] = [] + + def p_Null(self, p): + """ + Null : QUESTIONMARK + | + """ + if len(p) > 1: + p[0] = True + else: + p[0] = False + + def p_ReturnTypeType(self, p): + """ + ReturnType : Type + """ + p[0] = p[1] + + def p_ReturnTypeVoid(self, p): + """ + ReturnType : VOID + """ + p[0] = BuiltinTypes[IDLBuiltinType.Types.void] + + def p_ScopedName(self, p): + """ + ScopedName : AbsoluteScopedName + | RelativeScopedName + """ + p[0] = p[1] + + def p_AbsoluteScopedName(self, p): + """ + AbsoluteScopedName : SCOPE IDENTIFIER ScopedNameParts + """ + assert False + pass + + def p_RelativeScopedName(self, p): + """ + RelativeScopedName : IDENTIFIER ScopedNameParts + """ + assert not p[2] # Not implemented! + + p[0] = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1]) + + def p_ScopedNameParts(self, p): + """ + ScopedNameParts : SCOPE IDENTIFIER ScopedNameParts + """ + assert False + pass + + def p_ScopedNamePartsEmpty(self, p): + """ + ScopedNameParts : + """ + p[0] = None + + def p_ExtendedAttributeNoArgs(self, p): + """ + ExtendedAttributeNoArgs : IDENTIFIER + """ + p[0] = (p[1],) + + def p_ExtendedAttributeArgList(self, p): + """ + ExtendedAttributeArgList : IDENTIFIER LPAREN ArgumentList RPAREN + """ + p[0] = (p[1], p[3]) + + def p_ExtendedAttributeIdent(self, p): + """ + ExtendedAttributeIdent : IDENTIFIER EQUALS STRING + | IDENTIFIER EQUALS IDENTIFIER + """ + p[0] = (p[1], p[3]) + + def p_ExtendedAttributeNamedArgList(self, p): + """ + ExtendedAttributeNamedArgList : IDENTIFIER EQUALS IDENTIFIER LPAREN ArgumentList RPAREN + """ + p[0] = (p[1], p[3], p[5]) + + def p_ExtendedAttributeIdentList(self, p): + """ + ExtendedAttributeIdentList : IDENTIFIER EQUALS LPAREN IdentifierList RPAREN + """ + p[0] = (p[1], p[4]) + + def p_IdentifierList(self, p): + """ + IdentifierList : IDENTIFIER Identifiers + """ + idents = list(p[2]) + idents.insert(0, p[1]) + p[0] = idents + + def p_IdentifiersList(self, p): + """ + Identifiers : COMMA IDENTIFIER Identifiers + """ + idents = list(p[3]) + idents.insert(0, p[2]) + p[0] = idents + + def p_IdentifiersEmpty(self, p): + """ + Identifiers : + """ + p[0] = [] + + def p_error(self, p): + if not p: + raise WebIDLError("Syntax Error at end of file. Possibly due to missing semicolon(;), braces(}) or both", + [self._filename]) + else: + raise WebIDLError("invalid syntax", [Location(self.lexer, p.lineno, p.lexpos, self._filename)]) + + def __init__(self, outputdir='', lexer=None): + Tokenizer.__init__(self, outputdir, lexer) + + logger = SqueakyCleanLogger() + self.parser = yacc.yacc(module=self, + outputdir=outputdir, + tabmodule='webidlyacc', + errorlog=logger + # Pickling the grammar is a speedup in + # some cases (older Python?) but a + # significant slowdown in others. + # We're not pickling for now, until it + # becomes a speedup again. + # , picklefile='WebIDLGrammar.pkl' + ) + logger.reportGrammarErrors() + + self._globalScope = IDLScope(BuiltinLocation("<Global Scope>"), None, None) + # To make our test harness work, pretend like we have a primary global already. Note that we _don't_ set _globalScope.primaryGlobalAttr, so we'll still be able to detect multiple PrimaryGlobal extended attributes. + self._globalScope.primaryGlobalName = "FakeTestPrimaryGlobal" + self._globalScope.globalNames.add("FakeTestPrimaryGlobal") + self._globalScope.globalNameMapping["FakeTestPrimaryGlobal"].add("FakeTestPrimaryGlobal") + self._installBuiltins(self._globalScope) + self._productions = [] + + self._filename = "<builtin>" + self.lexer.input(Parser._builtins) + self._filename = None + + self.parser.parse(lexer=self.lexer,tracking=True) + + def _installBuiltins(self, scope): + assert isinstance(scope, IDLScope) + + # xrange omits the last value. + for x in xrange(IDLBuiltinType.Types.ArrayBuffer, IDLBuiltinType.Types.Float64Array + 1): + builtin = BuiltinTypes[x] + name = builtin.name + + typedef = IDLTypedefType(BuiltinLocation("<builtin type>"), builtin, name) + typedef.resolve(scope) + + @ staticmethod + def handleModifiers(type, modifiers): + for (modifier, modifierLocation) in modifiers: + assert modifier == IDLMethod.TypeSuffixModifier.QMark or \ + modifier == IDLMethod.TypeSuffixModifier.Brackets + + if modifier == IDLMethod.TypeSuffixModifier.QMark: + type = IDLNullableType(modifierLocation, type) + elif modifier == IDLMethod.TypeSuffixModifier.Brackets: + type = IDLArrayType(modifierLocation, type) + + return type + + def parse(self, t, filename=None): + self.lexer.input(t) + + #for tok in iter(self.lexer.token, None): + # print tok + + self._filename = filename + self._productions.extend(self.parser.parse(lexer=self.lexer,tracking=True)) + self._filename = None + + def finish(self): + # First, finish all the IDLImplementsStatements. In particular, we + # have to make sure we do those before we do the IDLInterfaces. + # XXX khuey hates this bit and wants to nuke it from orbit. + implementsStatements = [ p for p in self._productions if + isinstance(p, IDLImplementsStatement)] + otherStatements = [ p for p in self._productions if + not isinstance(p, IDLImplementsStatement)] + for production in implementsStatements: + production.finish(self.globalScope()) + for production in otherStatements: + production.finish(self.globalScope()) + + # Do any post-finish validation we need to do + for production in self._productions: + production.validate() + + # De-duplicate self._productions, without modifying its order. + seen = set() + result = [] + for p in self._productions: + if p not in seen: + seen.add(p) + result.append(p) + return result + + def reset(self): + return Parser(lexer=self.lexer) + + # Builtin IDL defined by WebIDL + _builtins = """ + typedef unsigned long long DOMTimeStamp; + """ + +def main(): + # Parse arguments. + from optparse import OptionParser + usageString = "usage: %prog [options] files" + o = OptionParser(usage=usageString) + o.add_option("--cachedir", dest='cachedir', default=None, + help="Directory in which to cache lex/parse tables.") + o.add_option("--verbose-errors", action='store_true', default=False, + help="When an error happens, display the Python traceback.") + (options, args) = o.parse_args() + + if len(args) < 1: + o.error(usageString) + + fileList = args + baseDir = os.getcwd() + + # Parse the WebIDL. + parser = Parser(options.cachedir) + try: + for filename in fileList: + fullPath = os.path.normpath(os.path.join(baseDir, filename)) + f = open(fullPath, 'rb') + lines = f.readlines() + f.close() + print fullPath + parser.parse(''.join(lines), fullPath) + parser.finish() + except WebIDLError, e: + if options.verbose_errors: + traceback.print_exc() + else: + print e + +if __name__ == '__main__': + main() diff --git a/components/script/dom/bindings/codegen/parser/external.patch b/components/script/dom/bindings/codegen/parser/external.patch new file mode 100644 index 00000000000..9464511a9d0 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/external.patch @@ -0,0 +1,49 @@ +--- WebIDL.py ++++ WebIDL.py +@@ -450,44 +450,8 @@ class IDLIdentifierPlaceholder(IDLObjectWithIdentifier): + + class IDLExternalInterface(IDLObjectWithIdentifier): + def __init__(self, location, parentScope, identifier): +- assert isinstance(identifier, IDLUnresolvedIdentifier) +- assert isinstance(parentScope, IDLScope) +- self.parent = None +- IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier) +- IDLObjectWithIdentifier.resolve(self, parentScope) +- +- def finish(self, scope): +- pass +- +- def validate(self): +- pass +- +- def isExternal(self): +- return True +- +- def isInterface(self): +- return True +- +- def isConsequential(self): +- return False +- +- def addExtendedAttributes(self, attrs): +- assert len(attrs) == 0 +- +- def resolve(self, parentScope): +- pass +- +- def getJSImplementation(self): +- return None +- +- def isJSImplemented(self): +- return False +- +- def getNavigatorProperty(self): +- return None +- +- def _getDependentObjects(self): +- return set() ++ raise WebIDLError("Servo does not support external interfaces.", ++ [self.location]) + + class IDLPartialInterface(IDLObject): + def __init__(self, location, name, members, nonPartialInterface): diff --git a/components/script/dom/bindings/codegen/parser/module.patch b/components/script/dom/bindings/codegen/parser/module.patch new file mode 100644 index 00000000000..977947b4c63 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/module.patch @@ -0,0 +1,12 @@ +--- WebIDL.py ++++ WebIDL.py +@@ -3398,6 +3398,9 @@ class IDLCallbackType(IDLType, IDLObjectWithScope): + self._treatNonCallableAsNull = False + self._treatNonObjectAsNull = False + ++ def module(self): ++ return self.location.filename().split('/')[-1].split('.webidl')[0] + 'Binding' ++ + def isCallback(self): + return True + diff --git a/components/script/dom/bindings/codegen/parser/runtests.py b/components/script/dom/bindings/codegen/parser/runtests.py new file mode 100644 index 00000000000..98a7d2b81d3 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/runtests.py @@ -0,0 +1,79 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.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 optparse +import traceback +import WebIDL + +class TestHarness(object): + def __init__(self, test, verbose): + self.test = test + self.verbose = verbose + self.printed_intro = False + + 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): + if self.verbose: + print "TEST-PASS | %s" % msg + + def test_fail(self, msg): + self.maybe_print_intro() + 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) + print "\tGot %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) + + 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: + print "TEST-UNEXPECTED-FAIL | Unhandled exception in test %s: %s" % (testpath, ex) + traceback.print_exc() + finally: + harness.finish() + +if __name__ == '__main__': + usage = """%prog [OPTIONS] [TESTS] + Where TESTS are relative to the tests directory.""" + parser = optparse.OptionParser(usage=usage) + parser.add_option('-q', '--quiet', action='store_false', dest='verbose', default=True, + help="Don't print passing tests.") + options, tests = parser.parse_args() + + run_tests(tests, verbose=options.verbose) diff --git a/components/script/dom/bindings/codegen/parser/tests/test_any_null.py b/components/script/dom/bindings/codegen/parser/tests/test_any_null.py new file mode 100644 index 00000000000..e3b690bf6f1 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_any_null.py @@ -0,0 +1,14 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface DoubleNull { + attribute any? foo; + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_argument_identifier_conflicts.py b/components/script/dom/bindings/codegen/parser/tests/test_argument_identifier_conflicts.py new file mode 100644 index 00000000000..eb1f6d3c92e --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_argument_identifier_conflicts.py @@ -0,0 +1,14 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface ArgumentIdentifierConflict { + void foo(boolean arg1, boolean arg1); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_argument_novoid.py b/components/script/dom/bindings/codegen/parser/tests/test_argument_novoid.py new file mode 100644 index 00000000000..ef8c2229aed --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_argument_novoid.py @@ -0,0 +1,14 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface VoidArgument1 { + void foo(void arg2); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_array_of_interface.py b/components/script/dom/bindings/codegen/parser/tests/test_array_of_interface.py new file mode 100644 index 00000000000..26528984595 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_array_of_interface.py @@ -0,0 +1,13 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + interface A { + attribute long a; + }; + + interface B { + attribute A[] b; + }; + """); + parser.finish() diff --git a/components/script/dom/bindings/codegen/parser/tests/test_arraybuffer.py b/components/script/dom/bindings/codegen/parser/tests/test_arraybuffer.py new file mode 100644 index 00000000000..5b8e56f86ca --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_arraybuffer.py @@ -0,0 +1,84 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + interface TestArrayBuffer { + attribute ArrayBuffer bufferAttr; + void bufferMethod(ArrayBuffer arg1, ArrayBuffer? arg2, ArrayBuffer[] arg3, sequence<ArrayBuffer> arg4); + + attribute ArrayBufferView viewAttr; + void viewMethod(ArrayBufferView arg1, ArrayBufferView? arg2, ArrayBufferView[] arg3, sequence<ArrayBufferView> arg4); + + attribute Int8Array int8ArrayAttr; + void int8ArrayMethod(Int8Array arg1, Int8Array? arg2, Int8Array[] arg3, sequence<Int8Array> arg4); + + attribute Uint8Array uint8ArrayAttr; + void uint8ArrayMethod(Uint8Array arg1, Uint8Array? arg2, Uint8Array[] arg3, sequence<Uint8Array> arg4); + + attribute Uint8ClampedArray uint8ClampedArrayAttr; + void uint8ClampedArrayMethod(Uint8ClampedArray arg1, Uint8ClampedArray? arg2, Uint8ClampedArray[] arg3, sequence<Uint8ClampedArray> arg4); + + attribute Int16Array int16ArrayAttr; + void int16ArrayMethod(Int16Array arg1, Int16Array? arg2, Int16Array[] arg3, sequence<Int16Array> arg4); + + attribute Uint16Array uint16ArrayAttr; + void uint16ArrayMethod(Uint16Array arg1, Uint16Array? arg2, Uint16Array[] arg3, sequence<Uint16Array> arg4); + + attribute Int32Array int32ArrayAttr; + void int32ArrayMethod(Int32Array arg1, Int32Array? arg2, Int32Array[] arg3, sequence<Int32Array> arg4); + + attribute Uint32Array uint32ArrayAttr; + void uint32ArrayMethod(Uint32Array arg1, Uint32Array? arg2, Uint32Array[] arg3, sequence<Uint32Array> arg4); + + attribute Float32Array float32ArrayAttr; + void float32ArrayMethod(Float32Array arg1, Float32Array? arg2, Float32Array[] arg3, sequence<Float32Array> arg4); + + attribute Float64Array float64ArrayAttr; + void float64ArrayMethod(Float64Array arg1, Float64Array? arg2, Float64Array[] arg3, sequence<Float64Array> arg4); + }; + """) + + 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), 4, "Expect 4 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 + "Array", "Expect an ArrayBuffer type") + harness.ok(arguments[2].type.inner.isSpiderMonkeyInterface(), "Should test as a js interface") + + harness.check(str(arguments[3].type), t + "Sequence", "Expect an ArrayBuffer type") + harness.ok(arguments[3].type.inner.isSpiderMonkeyInterface(), "Should test as a js interface") + + + checkStuff(members[0], members[1], "ArrayBuffer") + checkStuff(members[2], members[3], "ArrayBufferView") + checkStuff(members[4], members[5], "Int8Array") + checkStuff(members[6], members[7], "Uint8Array") + checkStuff(members[8], members[9], "Uint8ClampedArray") + checkStuff(members[10], members[11], "Int16Array") + checkStuff(members[12], members[13], "Uint16Array") + checkStuff(members[14], members[15], "Int32Array") + checkStuff(members[16], members[17], "Uint32Array") + checkStuff(members[18], members[19], "Float32Array") + checkStuff(members[20], members[21], "Float64Array") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_attr.py b/components/script/dom/bindings/codegen/parser/tests/test_attr.py new file mode 100644 index 00000000000..6b6142b6243 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_attr.py @@ -0,0 +1,302 @@ +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; + }; + + interface TestAttrArray { + 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 TestAttrNullableArray { + 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 TestAttrArrayOfNullableTypes { + 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 TestAttrNullableArrayOfNullableTypes { + 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), 6, "Should be six 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) + + iface = results[2] + harness.ok(isinstance(iface, WebIDL.IDLInterface), + "Should be an IDLInterface") + harness.check(iface.identifier.QName(), "::TestAttrArray", "Interface has the right QName") + harness.check(iface.identifier.name, "TestAttrArray", "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 % "Array", name, type % "Array", readonly) + + iface = results[3] + harness.ok(isinstance(iface, WebIDL.IDLInterface), + "Should be an IDLInterface") + harness.check(iface.identifier.QName(), "::TestAttrNullableArray", "Interface has the right QName") + harness.check(iface.identifier.name, "TestAttrNullableArray", "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 % "NullableArray", name, type % "ArrayOrNull", readonly) + + iface = results[4] + harness.ok(isinstance(iface, WebIDL.IDLInterface), + "Should be an IDLInterface") + harness.check(iface.identifier.QName(), "::TestAttrArrayOfNullableTypes", "Interface has the right QName") + harness.check(iface.identifier.name, "TestAttrArrayOfNullableTypes", "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 % "ArrayOfNullableTypes", name, type % "OrNullArray", readonly) + + iface = results[5] + harness.ok(isinstance(iface, WebIDL.IDLInterface), + "Should be an IDLInterface") + harness.check(iface.identifier.QName(), "::TestAttrNullableArrayOfNullableTypes", "Interface has the right QName") + harness.check(iface.identifier.name, "TestAttrNullableArrayOfNullableTypes", "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 % "NullableArrayOfNullableTypes", name, type % "OrNullArrayOrNull", readonly) + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A { + [SetterInfallible] readonly attribute boolean foo; + }; + """) + results = parser.finish() + except Exception, x: + threw = True + harness.ok(threw, "Should not allow [SetterInfallible] on readonly attributes") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_attr_sequence_type.py b/components/script/dom/bindings/codegen/parser/tests/test_attr_sequence_type.py new file mode 100644 index 00000000000..fb1b97812bc --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_attr_sequence_type.py @@ -0,0 +1,67 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface AttrSequenceType { + attribute sequence<object> foo; + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Attribute type must not be a sequence type") + + parser.reset() + + threw = False + try: + parser.parse(""" + interface AttrUnionWithSequenceType { + attribute (sequence<object> or DOMString) foo; + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Attribute type must not be a union with a sequence member type") + + parser.reset() + + threw = False + try: + parser.parse(""" + interface AttrNullableUnionWithSequenceType { + attribute (sequence<object>? or DOMString) foo; + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Attribute type must not be a union with a nullable sequence " + "member type") + + parser.reset() + + threw = False + try: + parser.parse(""" + interface AttrUnionWithUnionWithSequenceType { + attribute ((sequence<object> or DOMString) or AttrUnionWithUnionWithSequenceType) foo; + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Attribute type must not be a union type with a union member " + "type that has a sequence member type") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_builtin_filename.py b/components/script/dom/bindings/codegen/parser/tests/test_builtin_filename.py new file mode 100644 index 00000000000..631e52eba0b --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_builtin_filename.py @@ -0,0 +1,11 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + interface Test { + attribute long b; + }; + """); + + attr = parser.finish()[0].members[0] + harness.check(attr.type.filename(), '<builtin>', 'Filename on builtin type') diff --git a/components/script/dom/bindings/codegen/parser/tests/test_builtins.py b/components/script/dom/bindings/codegen/parser/tests/test_builtins.py new file mode 100644 index 00000000000..f8563fc2d9b --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_builtins.py @@ -0,0 +1,41 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + interface TestBuiltins { + attribute boolean b; + attribute byte s8; + attribute octet u8; + attribute short s16; + attribute unsigned short u16; + attribute long s32; + attribute unsigned long u32; + attribute long long s64; + attribute unsigned long long u64; + attribute DOMTimeStamp ts; + }; + """) + + results = parser.finish() + + harness.ok(True, "TestBuiltins interface parsed without error.") + harness.check(len(results), 1, "Should be one production") + harness.ok(isinstance(results[0], WebIDL.IDLInterface), + "Should be an IDLInterface") + iface = results[0] + harness.check(iface.identifier.QName(), "::TestBuiltins", "Interface has the right QName") + harness.check(iface.identifier.name, "TestBuiltins", "Interface has the right name") + harness.check(iface.parent, None, "Interface has no parent") + + members = iface.members + harness.check(len(members), 10, "Should be one production") + + names = ["b", "s8", "u8", "s16", "u16", "s32", "u32", "s64", "u64", "ts"] + types = ["Boolean", "Byte", "Octet", "Short", "UnsignedShort", "Long", "UnsignedLong", "LongLong", "UnsignedLongLong", "UnsignedLongLong"] + for i in range(10): + attr = members[i] + harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Should be an IDLAttribute") + harness.check(attr.identifier.QName(), "::TestBuiltins::" + names[i], "Attr has correct QName") + harness.check(attr.identifier.name, names[i], "Attr has correct name") + harness.check(str(attr.type), types[i], "Attr type is the correct name") + harness.ok(attr.type.isPrimitive(), "Should be a primitive type") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_callback.py b/components/script/dom/bindings/codegen/parser/tests/test_callback.py new file mode 100644 index 00000000000..267d27dc087 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_callback.py @@ -0,0 +1,34 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + interface TestCallback { + attribute CallbackType? listener; + }; + + callback CallbackType = boolean (unsigned long arg); + """) + + results = parser.finish() + + harness.ok(True, "TestCallback interface parsed without error.") + harness.check(len(results), 2, "Should be one production.") + iface = results[0] + harness.ok(isinstance(iface, WebIDL.IDLInterface), + "Should be an IDLInterface") + harness.check(iface.identifier.QName(), "::TestCallback", "Interface has the right QName") + harness.check(iface.identifier.name, "TestCallback", "Interface has the right name") + harness.check(len(iface.members), 1, "Expect %s members" % 1) + + attr = iface.members[0] + harness.ok(isinstance(attr, WebIDL.IDLAttribute), + "Should be an IDLAttribute") + harness.ok(attr.isAttr(), "Should be an attribute") + harness.ok(not attr.isMethod(), "Attr is not an method") + harness.ok(not attr.isConst(), "Attr is not a const") + harness.check(attr.identifier.QName(), "::TestCallback::listener", "Attr has the right QName") + harness.check(attr.identifier.name, "listener", "Attr has the right name") + t = attr.type + harness.ok(not isinstance(t, WebIDL.IDLWrapperType), "Attr has the right type") + harness.ok(isinstance(t, WebIDL.IDLNullableType), "Attr has the right type") + harness.ok(t.isCallback(), "Attr has the right type") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_callback_interface.py b/components/script/dom/bindings/codegen/parser/tests/test_callback_interface.py new file mode 100644 index 00000000000..80896ca1edb --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_callback_interface.py @@ -0,0 +1,47 @@ +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") + diff --git a/components/script/dom/bindings/codegen/parser/tests/test_const.py b/components/script/dom/bindings/codegen/parser/tests/test_const.py new file mode 100644 index 00000000000..12f411363fb --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_const.py @@ -0,0 +1,64 @@ +import WebIDL + +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; + }; + """) + + 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), 14, "Expect 14 members") + + consts = iface.members + + def checkConst(const, QName, name, type, value): + 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.") + + checkConst(consts[0], "::TestConsts::zero", "zero", "Byte", 0) + checkConst(consts[1], "::TestConsts::b", "b", "Byte", -1) + checkConst(consts[2], "::TestConsts::o", "o", "Octet", 2) + checkConst(consts[3], "::TestConsts::s", "s", "Short", -3) + checkConst(consts[4], "::TestConsts::us", "us", "UnsignedShort", 4) + checkConst(consts[5], "::TestConsts::l", "l", "Long", -5) + checkConst(consts[6], "::TestConsts::ul", "ul", "UnsignedLong", 6) + checkConst(consts[7], "::TestConsts::ull", "ull", "UnsignedLongLong", 7) + checkConst(consts[8], "::TestConsts::ll", "ll", "LongLong", -8) + checkConst(consts[9], "::TestConsts::t", "t", "Boolean", True) + checkConst(consts[10], "::TestConsts::f", "f", "Boolean", False) + checkConst(consts[11], "::TestConsts::n", "n", "BooleanOrNull", None) + checkConst(consts[12], "::TestConsts::nt", "nt", "BooleanOrNull", True) + checkConst(consts[13], "::TestConsts::nf", "nf", "BooleanOrNull", False) + diff --git a/components/script/dom/bindings/codegen/parser/tests/test_constructor.py b/components/script/dom/bindings/codegen/parser/tests/test_constructor.py new file mode 100644 index 00000000000..6ec1be1871b --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_constructor.py @@ -0,0 +1,75 @@ +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=False, getter=False, setter=False, creator=False, + deleter=False, legacycaller=False, stringifier=False): + harness.ok(isinstance(method, WebIDL.IDLMethod), + "Should be an IDLMethod") + harness.ok(method.isMethod(), "Method is a method") + harness.ok(not method.isAttr(), "Method is not an attr") + harness.ok(not method.isConst(), "Method is not a const") + harness.check(method.identifier.QName(), QName, "Method has the right QName") + harness.check(method.identifier.name, name, "Method has the right name") + harness.check(method.isStatic(), static, "Method has the correct static value") + harness.check(method.isGetter(), getter, "Method has the correct getter value") + harness.check(method.isSetter(), setter, "Method has the correct setter value") + harness.check(method.isCreator(), creator, "Method has the correct creator value") + harness.check(method.isDeleter(), deleter, "Method has the correct deleter value") + harness.check(method.isLegacycaller(), legacycaller, "Method has the correct legacycaller value") + harness.check(method.isStringifier(), stringifier, "Method has the correct stringifier value") + harness.check(len(method.signatures()), len(signatures), "Method has the correct number of signatures") + + sigpairs = zip(method.signatures(), signatures) + for (gotSignature, expectedSignature) in sigpairs: + (gotRetType, gotArgs) = gotSignature + (expectedRetType, expectedArgs) = expectedSignature + + harness.check(str(gotRetType), expectedRetType, + "Method has the expected return type.") + + for i in range(0, len(gotArgs)): + (QName, name, type, optional, variadic) = expectedArgs[i] + checkArgument(gotArgs[i], QName, name, type, optional, variadic) + + 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 two productions") + harness.ok(isinstance(results[0], WebIDL.IDLInterface), + "Should be an IDLInterface") + harness.ok(isinstance(results[1], 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)])]) diff --git a/components/script/dom/bindings/codegen/parser/tests/test_constructor_no_interface_object.py b/components/script/dom/bindings/codegen/parser/tests/test_constructor_no_interface_object.py new file mode 100644 index 00000000000..192c5f6f97b --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_constructor_no_interface_object.py @@ -0,0 +1,28 @@ +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.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_deduplicate.py b/components/script/dom/bindings/codegen/parser/tests/test_deduplicate.py new file mode 100644 index 00000000000..6249d36fb8f --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_deduplicate.py @@ -0,0 +1,15 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + interface Foo; + interface Bar; + interface Foo; + """); + + results = parser.finish() + + # There should be no duplicate interfaces in the result. + expectedNames = sorted(['Foo', 'Bar']) + actualNames = sorted(map(lambda iface: iface.identifier.name, results)) + harness.check(actualNames, expectedNames, "Parser shouldn't output duplicate names.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_dictionary.py b/components/script/dom/bindings/codegen/parser/tests/test_dictionary.py new file mode 100644 index 00000000000..9ae9eb2b66f --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_dictionary.py @@ -0,0 +1,198 @@ +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 { + [TreatUndefinedAs=EmptyString] DOMString foo; + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow [TreatUndefinedAs] 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 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() + 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((A or long)? arg1); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Dictionary arg must not be in a nullable union") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_distinguishability.py b/components/script/dom/bindings/codegen/parser/tests/test_distinguishability.py new file mode 100644 index 00000000000..86847800631 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_distinguishability.py @@ -0,0 +1,150 @@ +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") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_double_null.py b/components/script/dom/bindings/codegen/parser/tests/test_double_null.py new file mode 100644 index 00000000000..700c7eade00 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_double_null.py @@ -0,0 +1,14 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface DoubleNull { + attribute byte?? foo; + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_duplicate_qualifiers.py b/components/script/dom/bindings/codegen/parser/tests/test_duplicate_qualifiers.py new file mode 100644 index 00000000000..799f2e0e0ed --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_duplicate_qualifiers.py @@ -0,0 +1,84 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface DuplicateQualifiers1 { + getter getter byte foo(unsigned long index); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface DuplicateQualifiers2 { + setter setter byte foo(unsigned long index, byte value); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface DuplicateQualifiers3 { + creator creator byte foo(unsigned long index, byte value); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface DuplicateQualifiers4 { + deleter deleter byte foo(unsigned long index); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface DuplicateQualifiers5 { + getter deleter getter byte foo(unsigned long index); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + results = parser.parse(""" + interface DuplicateQualifiers6 { + creator setter creator byte foo(unsigned long index, byte value); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_empty_enum.py b/components/script/dom/bindings/codegen/parser/tests/test_empty_enum.py new file mode 100644 index 00000000000..ee0079f06da --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_empty_enum.py @@ -0,0 +1,14 @@ +import WebIDL + +def WebIDLTest(parser, harness): + try: + parser.parse(""" + enum TestEmptyEnum { + }; + """) + + harness.ok(False, "Should have thrown!") + except: + harness.ok(True, "Parsing TestEmptyEnum enum should fail") + + results = parser.finish() diff --git a/components/script/dom/bindings/codegen/parser/tests/test_enum.py b/components/script/dom/bindings/codegen/parser/tests/test_enum.py new file mode 100644 index 00000000000..69a6932062d --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_enum.py @@ -0,0 +1,81 @@ +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") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_enum_duplicate_values.py b/components/script/dom/bindings/codegen/parser/tests/test_enum_duplicate_values.py new file mode 100644 index 00000000000..51205d209e7 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_enum_duplicate_values.py @@ -0,0 +1,13 @@ +import WebIDL + +def WebIDLTest(parser, harness): + try: + parser.parse(""" + enum TestEnumDuplicateValue { + "", + "" + }; + """) + harness.ok(False, "Should have thrown!") + except: + harness.ok(True, "Enum TestEnumDuplicateValue should throw") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_error_colno.py b/components/script/dom/bindings/codegen/parser/tests/test_error_colno.py new file mode 100644 index 00000000000..ca0674aec04 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_error_colno.py @@ -0,0 +1,20 @@ +import WebIDL + +def WebIDLTest(parser, harness): + # Check that error messages put the '^' in the right place. + + threw = False + input = 'interface ?' + try: + parser.parse(input) + results = parser.finish() + except WebIDL.WebIDLError, e: + threw = True + lines = str(e).split('\n') + + harness.check(len(lines), 3, 'Expected number of lines in error message') + harness.check(lines[1], input, 'Second line shows error') + harness.check(lines[2], ' ' * (len(input) - 1) + '^', + 'Correct column pointer in error message') + + harness.ok(threw, "Should have thrown.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_error_lineno.py b/components/script/dom/bindings/codegen/parser/tests/test_error_lineno.py new file mode 100644 index 00000000000..f11222e7a4d --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_error_lineno.py @@ -0,0 +1,28 @@ +import WebIDL + +def WebIDLTest(parser, harness): + # Check that error messages put the '^' in the right place. + + threw = False + input = """\ +// This is a comment. +interface Foo { +}; + +/* This is also a comment. */ +interface ?""" + try: + parser.parse(input) + results = parser.finish() + except WebIDL.WebIDLError, e: + threw = True + lines = str(e).split('\n') + + harness.check(len(lines), 3, 'Expected number of lines in error message') + harness.ok(lines[0].endswith('line 6:10'), 'First line of error should end with "line 6:10", but was "%s".' % lines[0]) + harness.check(lines[1], 'interface ?', 'Second line of error message is the line which caused the error.') + harness.check(lines[2], ' ' * (len('interface ?') - 1) + '^', + 'Correct column pointer in error message.') + + harness.ok(threw, "Should have thrown.") + diff --git a/components/script/dom/bindings/codegen/parser/tests/test_extended_attributes.py b/components/script/dom/bindings/codegen/parser/tests/test_extended_attributes.py new file mode 100644 index 00000000000..5c6887331e7 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_extended_attributes.py @@ -0,0 +1,107 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + [Flippety] + interface TestExtendedAttr { + [Foopy] attribute byte b; + }; + """) + + results = parser.finish() + + parser = parser.reset() + parser.parse(""" + [Flippety="foo.bar",Floppety=flop] + interface TestExtendedAttr { + [Foopy="foo.bar"] attribute byte b; + }; + """) + + results = parser.finish() + + parser = parser.reset() + parser.parse(""" + interface TestLenientThis { + [LenientThis] attribute byte b; + }; + """) + + results = parser.finish() + harness.ok(results[0].members[0].hasLenientThis(), + "Should have a lenient this") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestLenientThis2 { + [LenientThis=something] attribute byte b; + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "[LenientThis] must take no arguments") + + parser = parser.reset() + parser.parse(""" + interface TestClamp { + void testClamp([Clamp] long foo); + void testNotClamp(long foo); + }; + """) + + results = parser.finish() + # Pull out the first argument out of the arglist of the first (and + # only) signature. + harness.ok(results[0].members[0].signatures()[0][1][0].clamp, + "Should be clamped") + harness.ok(not results[0].members[1].signatures()[0][1][0].clamp, + "Should not be clamped") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestClamp2 { + void testClamp([Clamp=something] long foo); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "[Clamp] must take no arguments") + + parser = parser.reset() + parser.parse(""" + interface TestEnforceRange { + void testEnforceRange([EnforceRange] long foo); + void testNotEnforceRange(long foo); + }; + """) + + results = parser.finish() + # Pull out the first argument out of the arglist of the first (and + # only) signature. + harness.ok(results[0].members[0].signatures()[0][1][0].enforceRange, + "Should be enforceRange") + harness.ok(not results[0].members[1].signatures()[0][1][0].enforceRange, + "Should not be enforceRange") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestEnforceRange2 { + void testEnforceRange([EnforceRange=something] long foo); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "[EnforceRange] must take no arguments") + diff --git a/components/script/dom/bindings/codegen/parser/tests/test_forward_decl.py b/components/script/dom/bindings/codegen/parser/tests/test_forward_decl.py new file mode 100644 index 00000000000..cac24c832cc --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_forward_decl.py @@ -0,0 +1,15 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + interface ForwardDeclared; + interface ForwardDeclared; + + interface TestForwardDecl { + attribute ForwardDeclared foo; + }; + """) + + results = parser.finish() + + harness.ok(True, "TestForwardDeclared interface parsed without error.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_implements.py b/components/script/dom/bindings/codegen/parser/tests/test_implements.py new file mode 100644 index 00000000000..04c47d92abe --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_implements.py @@ -0,0 +1,216 @@ +# Import the WebIDL module, so we can do isinstance checks and whatnot +import WebIDL + +def WebIDLTest(parser, harness): + # Basic functionality + threw = False + try: + parser.parse(""" + A implements B; + interface B { + attribute long x; + }; + interface A { + attribute long y; + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(not threw, "Should not have thrown on implements statement " + "before interfaces") + harness.check(len(results), 3, "We have three statements") + harness.ok(isinstance(results[1], WebIDL.IDLInterface), "B is an interface") + harness.check(len(results[1].members), 1, "B has one member") + A = results[2] + harness.ok(isinstance(A, WebIDL.IDLInterface), "A is an interface") + harness.check(len(A.members), 2, "A has two members") + harness.check(A.members[0].identifier.name, "y", "First member is 'y'") + harness.check(A.members[1].identifier.name, "x", "Second member is 'x'") + + # Duplicated member names not allowed + threw = False + try: + parser.parse(""" + C implements D; + interface D { + attribute long x; + }; + interface C { + attribute long x; + }; + """) + parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown on implemented interface duplicating " + "a name on base interface") + + # Same, but duplicated across implemented interfaces + threw = False + try: + parser.parse(""" + E implements F; + E implements G; + interface F { + attribute long x; + }; + interface G { + attribute long x; + }; + interface E {}; + """) + parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown on implemented interfaces " + "duplicating each other's member names") + + # Same, but duplicated across indirectly implemented interfaces + threw = False + try: + parser.parse(""" + H implements I; + H implements J; + I implements K; + interface K { + attribute long x; + }; + interface L { + attribute long x; + }; + interface I {}; + interface J : L {}; + interface H {}; + """) + parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown on indirectly implemented interfaces " + "duplicating each other's member names") + + # Same, but duplicated across an implemented interface and its parent + threw = False + try: + parser.parse(""" + M implements N; + interface O { + attribute long x; + }; + interface N : O { + attribute long x; + }; + interface M {}; + """) + parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown on implemented interface and its " + "ancestor duplicating member names") + + # Reset the parser so we can actually find things where we expect + # them in the list + parser = parser.reset() + + # Diamonds should be allowed + threw = False + try: + parser.parse(""" + P implements Q; + P implements R; + Q implements S; + R implements S; + interface Q {}; + interface R {}; + interface S { + attribute long x; + }; + interface P {}; + """) + results = parser.finish() + except: + threw = True + + harness.ok(not threw, "Diamond inheritance is fine") + harness.check(results[6].identifier.name, "S", "We should be looking at 'S'") + harness.check(len(results[6].members), 1, "S should have one member") + harness.check(results[6].members[0].identifier.name, "x", + "S's member should be 'x'") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestInterface { + }; + callback interface TestCallbackInterface { + }; + TestInterface implements TestCallbackInterface; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Should not allow callback interfaces on the right-hand side " + "of 'implements'") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestInterface { + }; + callback interface TestCallbackInterface { + }; + TestCallbackInterface implements TestInterface; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Should not allow callback interfaces on the left-hand side of " + "'implements'") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestInterface { + }; + dictionary Dict { + }; + Dict implements TestInterface; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Should not allow non-interfaces on the left-hand side " + "of 'implements'") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface TestInterface { + }; + dictionary Dict { + }; + TestInterface implements Dict; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Should not allow non-interfaces on the right-hand side " + "of 'implements'") + diff --git a/components/script/dom/bindings/codegen/parser/tests/test_incomplete_parent.py b/components/script/dom/bindings/codegen/parser/tests/test_incomplete_parent.py new file mode 100644 index 00000000000..1f520a28e16 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_incomplete_parent.py @@ -0,0 +1,18 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + interface TestIncompleteParent : NotYetDefined { + void foo(); + }; + + interface NotYetDefined : EvenHigherOnTheChain { + }; + + interface EvenHigherOnTheChain { + }; + """) + + parser.finish() + + harness.ok(True, "TestIncompleteParent interface parsed without error.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_incomplete_types.py b/components/script/dom/bindings/codegen/parser/tests/test_incomplete_types.py new file mode 100644 index 00000000000..fdc39604070 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_incomplete_types.py @@ -0,0 +1,44 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + interface TestIncompleteTypes { + attribute FooInterface attr1; + + FooInterface method1(FooInterface arg); + }; + + interface FooInterface { + }; + """) + + results = parser.finish() + + harness.ok(True, "TestIncompleteTypes interface parsed without error.") + harness.check(len(results), 2, "Should be two productions.") + iface = results[0] + harness.ok(isinstance(iface, WebIDL.IDLInterface), + "Should be an IDLInterface") + harness.check(iface.identifier.QName(), "::TestIncompleteTypes", "Interface has the right QName") + harness.check(iface.identifier.name, "TestIncompleteTypes", "Interface has the right name") + harness.check(len(iface.members), 2, "Expect 2 members") + + attr = iface.members[0] + harness.ok(isinstance(attr, WebIDL.IDLAttribute), + "Should be an IDLAttribute") + method = iface.members[1] + harness.ok(isinstance(method, WebIDL.IDLMethod), + "Should be an IDLMethod") + + harness.check(attr.identifier.QName(), "::TestIncompleteTypes::attr1", + "Attribute has the right QName") + harness.check(attr.type.name, "FooInterface", + "Previously unresolved type has the right name") + + harness.check(method.identifier.QName(), "::TestIncompleteTypes::method1", + "Attribute has the right QName") + (returnType, args) = method.signatures()[0] + harness.check(returnType.name, "FooInterface", + "Previously unresolved type has the right name") + harness.check(args[0].type.name, "FooInterface", + "Previously unresolved type has the right name") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_interface.py b/components/script/dom/bindings/codegen/parser/tests/test_interface.py new file mode 100644 index 00000000000..5b07172c636 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_interface.py @@ -0,0 +1,188 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse("interface Foo { };") + results = parser.finish() + harness.ok(True, "Empty interface parsed without error.") + harness.check(len(results), 1, "Should be one production") + harness.ok(isinstance(results[0], WebIDL.IDLInterface), + "Should be an IDLInterface") + iface = results[0] + harness.check(iface.identifier.QName(), "::Foo", "Interface has the right QName") + harness.check(iface.identifier.name, "Foo", "Interface has the right name") + harness.check(iface.parent, None, "Interface has no parent") + + parser.parse("interface Bar : Foo { };") + results = parser.finish() + harness.ok(True, "Empty interface parsed without error.") + harness.check(len(results), 2, "Should be two productions") + harness.ok(isinstance(results[1], WebIDL.IDLInterface), + "Should be an IDLInterface") + iface = results[1] + harness.check(iface.identifier.QName(), "::Bar", "Interface has the right QName") + harness.check(iface.identifier.name, "Bar", "Interface has the right name") + harness.ok(isinstance(iface.parent, WebIDL.IDLInterface), + "Interface has a parent") + + parser = parser.reset() + parser.parse(""" + interface QNameBase { + attribute long foo; + }; + + interface QNameDerived : QNameBase { + attribute long long foo; + attribute byte bar; + }; + """) + results = parser.finish() + harness.check(len(results), 2, "Should be two productions") + harness.ok(isinstance(results[0], WebIDL.IDLInterface), + "Should be an IDLInterface") + harness.ok(isinstance(results[1], WebIDL.IDLInterface), + "Should be an IDLInterface") + harness.check(results[1].parent, results[0], "Inheritance chain is right") + harness.check(len(results[0].members), 1, "Expect 1 productions") + harness.check(len(results[1].members), 2, "Expect 2 productions") + base = results[0] + derived = results[1] + harness.check(base.members[0].identifier.QName(), "::QNameBase::foo", + "Member has the right QName") + harness.check(derived.members[0].identifier.QName(), "::QNameDerived::foo", + "Member has the right QName") + harness.check(derived.members[1].identifier.QName(), "::QNameDerived::bar", + "Member has the right QName") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A : B {}; + interface B : A {}; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow cycles in interface inheritance chains") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A : C {}; + interface C : B {}; + interface B : A {}; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow indirect cycles in interface inheritance chains") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A {}; + interface B {}; + A implements B; + B implements A; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow cycles via implements") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A {}; + interface C {}; + interface B {}; + A implements C; + C implements B; + B implements A; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow indirect cycles via implements") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A : B {}; + interface B {}; + B implements A; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow inheriting from an interface that implements us") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A : B {}; + interface B {}; + interface C {}; + B implements C; + C implements A; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow inheriting from an interface that indirectly implements us") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A : B {}; + interface B : C {}; + interface C {}; + C implements A; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow indirectly inheriting from an interface that implements us") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A : B {}; + interface B : C {}; + interface C {}; + interface D {}; + C implements D; + D implements A; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow indirectly inheriting from an interface that indirectly implements us") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A; + interface B : A {}; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should not allow inheriting from an interface that is only forward declared") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_interface_const_identifier_conflicts.py b/components/script/dom/bindings/codegen/parser/tests/test_interface_const_identifier_conflicts.py new file mode 100644 index 00000000000..db944e7aaf7 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_interface_const_identifier_conflicts.py @@ -0,0 +1,15 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface IdentifierConflict { + const byte thing1 = 1; + const unsigned long thing1 = 1; + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_interface_identifier_conflicts_across_members.py b/components/script/dom/bindings/codegen/parser/tests/test_interface_identifier_conflicts_across_members.py new file mode 100644 index 00000000000..1a73fb917ed --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_interface_identifier_conflicts_across_members.py @@ -0,0 +1,60 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface IdentifierConflictAcrossMembers1 { + const byte thing1 = 1; + readonly attribute long thing1; + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface IdentifierConflictAcrossMembers2 { + readonly attribute long thing1; + const byte thing1 = 1; + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface IdentifierConflictAcrossMembers3 { + getter boolean thing1(DOMString name); + readonly attribute long thing1; + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface IdentifierConflictAcrossMembers1 { + const byte thing1 = 1; + long thing1(); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_method.py b/components/script/dom/bindings/codegen/parser/tests/test_method.py new file mode 100644 index 00000000000..40b2d2cf8b9 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_method.py @@ -0,0 +1,145 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + interface TestMethods { + void basic(); + static void basicStatic(); + void basicWithSimpleArgs(boolean arg1, byte arg2, unsigned long arg3); + boolean basicBoolean(); + static boolean basicStaticBoolean(); + boolean basicBooleanWithSimpleArgs(boolean arg1, byte arg2, unsigned long arg3); + void optionalArg(optional byte? arg1, optional sequence<byte> arg2); + void variadicArg(byte?... arg1); + void crazyTypes(sequence<long?[]>? arg1, boolean?[][]? arg2); + object getObject(); + void setObject(object arg1); + void setAny(any arg1); + float doFloats(float arg1); + }; + """) + + results = parser.finish() + + harness.ok(True, "TestMethods interface parsed without error.") + harness.check(len(results), 1, "Should be one production.") + iface = results[0] + harness.ok(isinstance(iface, WebIDL.IDLInterface), + "Should be an IDLInterface") + harness.check(iface.identifier.QName(), "::TestMethods", "Interface has the right QName") + harness.check(iface.identifier.name, "TestMethods", "Interface has the right name") + harness.check(len(iface.members), 13, "Expect 13 members") + + methods = iface.members + + def checkArgument(argument, QName, name, type, optional, variadic): + harness.ok(isinstance(argument, WebIDL.IDLArgument), + "Should be an IDLArgument") + harness.check(argument.identifier.QName(), QName, "Argument has the right QName") + harness.check(argument.identifier.name, name, "Argument has the right name") + harness.check(str(argument.type), type, "Argument has the right return type") + harness.check(argument.optional, optional, "Argument has the right optional value") + harness.check(argument.variadic, variadic, "Argument has the right variadic value") + + def checkMethod(method, QName, name, signatures, + static=False, getter=False, setter=False, creator=False, + deleter=False, legacycaller=False, stringifier=False): + harness.ok(isinstance(method, WebIDL.IDLMethod), + "Should be an IDLMethod") + harness.ok(method.isMethod(), "Method is a method") + harness.ok(not method.isAttr(), "Method is not an attr") + harness.ok(not method.isConst(), "Method is not a const") + harness.check(method.identifier.QName(), QName, "Method has the right QName") + harness.check(method.identifier.name, name, "Method has the right name") + harness.check(method.isStatic(), static, "Method has the correct static value") + harness.check(method.isGetter(), getter, "Method has the correct getter value") + harness.check(method.isSetter(), setter, "Method has the correct setter value") + harness.check(method.isCreator(), creator, "Method has the correct creator value") + harness.check(method.isDeleter(), deleter, "Method has the correct deleter value") + harness.check(method.isLegacycaller(), legacycaller, "Method has the correct legacycaller value") + harness.check(method.isStringifier(), stringifier, "Method has the correct stringifier value") + harness.check(len(method.signatures()), len(signatures), "Method has the correct number of signatures") + + sigpairs = zip(method.signatures(), signatures) + for (gotSignature, expectedSignature) in sigpairs: + (gotRetType, gotArgs) = gotSignature + (expectedRetType, expectedArgs) = expectedSignature + + harness.check(str(gotRetType), expectedRetType, + "Method has the expected return type.") + + for i in range(0, len(gotArgs)): + (QName, name, type, optional, variadic) = expectedArgs[i] + checkArgument(gotArgs[i], QName, name, type, optional, variadic) + + checkMethod(methods[0], "::TestMethods::basic", "basic", [("Void", [])]) + checkMethod(methods[1], "::TestMethods::basicStatic", "basicStatic", + [("Void", [])], static=True) + checkMethod(methods[2], "::TestMethods::basicWithSimpleArgs", + "basicWithSimpleArgs", + [("Void", + [("::TestMethods::basicWithSimpleArgs::arg1", "arg1", "Boolean", False, False), + ("::TestMethods::basicWithSimpleArgs::arg2", "arg2", "Byte", False, False), + ("::TestMethods::basicWithSimpleArgs::arg3", "arg3", "UnsignedLong", False, False)])]) + checkMethod(methods[3], "::TestMethods::basicBoolean", "basicBoolean", [("Boolean", [])]) + checkMethod(methods[4], "::TestMethods::basicStaticBoolean", "basicStaticBoolean", [("Boolean", [])], static=True) + checkMethod(methods[5], "::TestMethods::basicBooleanWithSimpleArgs", + "basicBooleanWithSimpleArgs", + [("Boolean", + [("::TestMethods::basicBooleanWithSimpleArgs::arg1", "arg1", "Boolean", False, False), + ("::TestMethods::basicBooleanWithSimpleArgs::arg2", "arg2", "Byte", False, False), + ("::TestMethods::basicBooleanWithSimpleArgs::arg3", "arg3", "UnsignedLong", False, False)])]) + checkMethod(methods[6], "::TestMethods::optionalArg", + "optionalArg", + [("Void", + [("::TestMethods::optionalArg::arg1", "arg1", "ByteOrNull", True, False), + ("::TestMethods::optionalArg::arg2", "arg2", "ByteSequence", True, False)])]) + checkMethod(methods[7], "::TestMethods::variadicArg", + "variadicArg", + [("Void", + [("::TestMethods::variadicArg::arg1", "arg1", "ByteOrNull", True, True)])]) + checkMethod(methods[8], "::TestMethods::crazyTypes", + "crazyTypes", + [("Void", + [("::TestMethods::crazyTypes::arg1", "arg1", "LongOrNullArraySequenceOrNull", False, False), + ("::TestMethods::crazyTypes::arg2", "arg2", "BooleanOrNullArrayArrayOrNull", False, False)])]) + checkMethod(methods[9], "::TestMethods::getObject", + "getObject", [("Object", [])]) + checkMethod(methods[10], "::TestMethods::setObject", + "setObject", + [("Void", + [("::TestMethods::setObject::arg1", "arg1", "Object", False, False)])]) + checkMethod(methods[11], "::TestMethods::setAny", + "setAny", + [("Void", + [("::TestMethods::setAny::arg1", "arg1", "Any", False, False)])]) + checkMethod(methods[12], "::TestMethods::doFloats", + "doFloats", + [("Float", + [("::TestMethods::doFloats::arg1", "arg1", "Float", False, False)])]) + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A { + [GetterInfallible] void foo(); + }; + """) + results = parser.finish() + except Exception, x: + threw = True + harness.ok(threw, "Should not allow [GetterInfallible] on methods") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface A { + [SetterInfallible] void foo(); + }; + """) + results = parser.finish() + except Exception, x: + threw = True + harness.ok(threw, "Should not allow [SetterInfallible] on methods") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_nullable_equivalency.py b/components/script/dom/bindings/codegen/parser/tests/test_nullable_equivalency.py new file mode 100644 index 00000000000..3366b9fbbbd --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_nullable_equivalency.py @@ -0,0 +1,126 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + interface TestNullableEquivalency1 { + attribute long a; + attribute long? b; + }; + + interface TestNullableEquivalency2 { + attribute ArrayBuffer a; + attribute ArrayBuffer? b; + }; + + /* Can't have dictionary-valued attributes, so can't test that here */ + + enum TestNullableEquivalency4Enum { + "Foo", + "Bar" + }; + + interface TestNullableEquivalency4 { + attribute TestNullableEquivalency4Enum a; + attribute TestNullableEquivalency4Enum? b; + }; + + interface TestNullableEquivalency5 { + attribute TestNullableEquivalency4 a; + attribute TestNullableEquivalency4? b; + }; + + interface TestNullableEquivalency6 { + attribute boolean a; + attribute boolean? b; + }; + + interface TestNullableEquivalency7 { + attribute DOMString a; + attribute DOMString? b; + }; + + /* Not implemented. */ + /*interface TestNullableEquivalency8 { + attribute float a; + attribute float? b; + };*/ + + interface TestNullableEquivalency8 { + attribute double a; + attribute double? b; + }; + + interface TestNullableEquivalency9 { + attribute object a; + attribute object? b; + }; + + interface TestNullableEquivalency10 { + attribute double[] a; + attribute double[]? b; + }; + + interface TestNullableEquivalency11 { + attribute TestNullableEquivalency9[] a; + attribute TestNullableEquivalency9[]? b; + }; + """) + + for decl in parser.finish(): + if decl.isInterface(): + checkEquivalent(decl, harness) + +def checkEquivalent(iface, harness): + type1 = iface.members[0].type + type2 = iface.members[1].type + + harness.check(type1.nullable(), False, 'attr1 should not be nullable') + harness.check(type2.nullable(), True, 'attr2 should be nullable') + + # We don't know about type1, but type2, the nullable type, definitely + # shouldn't be builtin. + harness.check(type2.builtin, False, 'attr2 should not be builtin') + + # Ensure that all attributes of type2 match those in type1, except for: + # - names on an ignore list, + # - names beginning with '_', + # - functions which throw when called with no args, and + # - class-level non-callables ("static variables"). + # + # Yes, this is an ugly, fragile hack. But it finds bugs... + for attr in dir(type1): + if attr.startswith('_') or \ + attr in ['nullable', 'builtin', 'filename', 'location', + 'inner', 'QName'] or \ + (hasattr(type(type1), attr) and not callable(getattr(type1, attr))): + continue + + a1 = getattr(type1, attr) + + if callable(a1): + try: + v1 = a1() + except: + # Can't call a1 with no args, so skip this attriute. + continue + + try: + a2 = getattr(type2, attr) + except: + harness.ok(False, 'Missing %s attribute on type %s in %s' % (attr, type2, iface)) + continue + + if not callable(a2): + harness.ok(False, "%s attribute on type %s in %s wasn't callable" % (attr, type2, iface)) + continue + + v2 = a2() + harness.check(v2, v1, '%s method return value' % attr) + else: + try: + a2 = getattr(type2, attr) + except: + harness.ok(False, 'Missing %s attribute on type %s in %s' % (attr, type2, iface)) + continue + + harness.check(a2, a1, '%s attribute should match' % attr) diff --git a/components/script/dom/bindings/codegen/parser/tests/test_nullable_void.py b/components/script/dom/bindings/codegen/parser/tests/test_nullable_void.py new file mode 100644 index 00000000000..961ff825e9f --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_nullable_void.py @@ -0,0 +1,14 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface NullableVoid { + void? foo(); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_optional_constraints.py b/components/script/dom/bindings/codegen/parser/tests/test_optional_constraints.py new file mode 100644 index 00000000000..1dcdc7fb8a5 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_optional_constraints.py @@ -0,0 +1,14 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface OptionalConstraints1 { + void foo(optional byte arg1, byte arg2); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_overload.py b/components/script/dom/bindings/codegen/parser/tests/test_overload.py new file mode 100644 index 00000000000..59d9be54e53 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_overload.py @@ -0,0 +1,47 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + interface TestOverloads { + void basic(); + void basic(long arg1); + boolean abitharder(TestOverloads foo); + boolean abitharder(boolean foo); + void abitharder(ArrayBuffer? foo); + }; + """) + + results = parser.finish() + + harness.ok(True, "TestOverloads interface parsed without error.") + harness.check(len(results), 1, "Should be one production.") + iface = results[0] + harness.ok(isinstance(iface, WebIDL.IDLInterface), + "Should be an IDLInterface") + harness.check(iface.identifier.QName(), "::TestOverloads", "Interface has the right QName") + harness.check(iface.identifier.name, "TestOverloads", "Interface has the right name") + harness.check(len(iface.members), 2, "Expect %s members" % 2) + + member = iface.members[0] + harness.check(member.identifier.QName(), "::TestOverloads::basic", "Method has the right QName") + harness.check(member.identifier.name, "basic", "Method has the right name") + harness.check(member.hasOverloads(), True, "Method has overloads") + + signatures = member.signatures() + harness.check(len(signatures), 2, "Method should have 2 signatures") + + (retval, argumentSet) = signatures[0] + + harness.check(str(retval), "Void", "Expect a void retval") + harness.check(len(argumentSet), 0, "Expect an empty argument set") + + (retval, argumentSet) = signatures[1] + harness.check(str(retval), "Void", "Expect a void retval") + harness.check(len(argumentSet), 1, "Expect an argument set with one argument") + + argument = argumentSet[0] + harness.ok(isinstance(argument, WebIDL.IDLArgument), + "Should be an IDLArgument") + harness.check(argument.identifier.QName(), "::TestOverloads::basic::arg1", "Argument has the right QName") + harness.check(argument.identifier.name, "arg1", "Argument has the right name") + harness.check(str(argument.type), "Long", "Argument has the right type") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_sanity.py b/components/script/dom/bindings/codegen/parser/tests/test_sanity.py new file mode 100644 index 00000000000..d3184c00731 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_sanity.py @@ -0,0 +1,7 @@ +def WebIDLTest(parser, harness): + parser.parse("") + parser.finish() + harness.ok(True, "Parsing nothing doesn't throw.") + parser.parse("interface Foo {};") + parser.finish() + harness.ok(True, "Parsing a silly interface doesn't throw.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_special_method_signature_mismatch.py b/components/script/dom/bindings/codegen/parser/tests/test_special_method_signature_mismatch.py new file mode 100644 index 00000000000..5ea1743d36a --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_special_method_signature_mismatch.py @@ -0,0 +1,294 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch1 { + getter long long foo(long index); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch2 { + getter void foo(unsigned long index); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch3 { + getter boolean foo(unsigned long index, boolean extraArg); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch4 { + getter boolean foo(unsigned long... index); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch5 { + getter boolean foo(optional unsigned long index); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch6 { + getter boolean foo(); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch7 { + deleter long long foo(long index); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch9 { + deleter boolean foo(unsigned long index, boolean extraArg); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch10 { + deleter boolean foo(unsigned long... index); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch11 { + deleter boolean foo(optional unsigned long index); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch12 { + deleter boolean foo(); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch13 { + setter long long foo(long index, long long value); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch15 { + setter boolean foo(unsigned long index, boolean value, long long extraArg); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch16 { + setter boolean foo(unsigned long index, boolean... value); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch17 { + setter boolean foo(unsigned long index, optional boolean value); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch18 { + setter boolean foo(); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch20 { + creator long long foo(long index, long long value); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch22 { + creator boolean foo(unsigned long index, boolean value, long long extraArg); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch23 { + creator boolean foo(unsigned long index, boolean... value); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch24 { + creator boolean foo(unsigned long index, optional boolean value); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodSignatureMismatch25 { + creator boolean foo(); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_special_methods.py b/components/script/dom/bindings/codegen/parser/tests/test_special_methods.py new file mode 100644 index 00000000000..695cfe4f250 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_special_methods.py @@ -0,0 +1,73 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + interface SpecialMethods { + getter long long (unsigned long index); + setter long long (unsigned long index, long long value); + creator long long (unsigned long index, long long value); + deleter long long (unsigned long index); + getter boolean (DOMString name); + setter boolean (DOMString name, boolean value); + creator boolean (DOMString name, boolean value); + deleter boolean (DOMString name); + }; + + interface SpecialMethodsCombination { + getter deleter long long (unsigned long index); + setter creator long long (unsigned long index, long long value); + getter deleter boolean (DOMString name); + setter creator boolean (DOMString name, boolean value); + }; + """) + + results = parser.finish() + + def checkMethod(method, QName, name, + static=False, getter=False, setter=False, creator=False, + deleter=False, legacycaller=False, stringifier=False): + harness.ok(isinstance(method, WebIDL.IDLMethod), + "Should be an IDLMethod") + harness.check(method.identifier.QName(), QName, "Method has the right QName") + harness.check(method.identifier.name, name, "Method has the right name") + harness.check(method.isStatic(), static, "Method has the correct static value") + harness.check(method.isGetter(), getter, "Method has the correct getter value") + harness.check(method.isSetter(), setter, "Method has the correct setter value") + harness.check(method.isCreator(), creator, "Method has the correct creator value") + harness.check(method.isDeleter(), deleter, "Method has the correct deleter value") + harness.check(method.isLegacycaller(), legacycaller, "Method has the correct legacycaller value") + harness.check(method.isStringifier(), stringifier, "Method has the correct stringifier value") + + harness.check(len(results), 2, "Expect 2 interfaces") + + iface = results[0] + harness.check(len(iface.members), 8, "Expect 8 members") + + checkMethod(iface.members[0], "::SpecialMethods::__indexedgetter", "__indexedgetter", + getter=True) + checkMethod(iface.members[1], "::SpecialMethods::__indexedsetter", "__indexedsetter", + setter=True) + checkMethod(iface.members[2], "::SpecialMethods::__indexedcreator", "__indexedcreator", + creator=True) + checkMethod(iface.members[3], "::SpecialMethods::__indexeddeleter", "__indexeddeleter", + deleter=True) + checkMethod(iface.members[4], "::SpecialMethods::__namedgetter", "__namedgetter", + getter=True) + checkMethod(iface.members[5], "::SpecialMethods::__namedsetter", "__namedsetter", + setter=True) + checkMethod(iface.members[6], "::SpecialMethods::__namedcreator", "__namedcreator", + creator=True) + checkMethod(iface.members[7], "::SpecialMethods::__nameddeleter", "__nameddeleter", + deleter=True) + + iface = results[1] + harness.check(len(iface.members), 4, "Expect 4 members") + + checkMethod(iface.members[0], "::SpecialMethodsCombination::__indexedgetterdeleter", + "__indexedgetterdeleter", getter=True, deleter=True) + checkMethod(iface.members[1], "::SpecialMethodsCombination::__indexedsettercreator", + "__indexedsettercreator", setter=True, creator=True) + checkMethod(iface.members[2], "::SpecialMethodsCombination::__namedgetterdeleter", + "__namedgetterdeleter", getter=True, deleter=True) + checkMethod(iface.members[3], "::SpecialMethodsCombination::__namedsettercreator", + "__namedsettercreator", setter=True, creator=True) diff --git a/components/script/dom/bindings/codegen/parser/tests/test_special_methods_uniqueness.py b/components/script/dom/bindings/codegen/parser/tests/test_special_methods_uniqueness.py new file mode 100644 index 00000000000..42e2c5bb71b --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_special_methods_uniqueness.py @@ -0,0 +1,62 @@ +import WebIDL + +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface SpecialMethodUniqueness1 { + getter deleter boolean (DOMString name); + getter boolean (DOMString name); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodUniqueness1 { + deleter boolean (DOMString name); + getter deleter boolean (DOMString name); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodUniqueness1 { + setter creator boolean (DOMString name); + creator boolean (DOMString name); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + parser.parse(""" + interface SpecialMethodUniqueness1 { + setter boolean (DOMString name); + creator setter boolean (DOMString name); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_treatNonCallableAsNull.py b/components/script/dom/bindings/codegen/parser/tests/test_treatNonCallableAsNull.py new file mode 100644 index 00000000000..3d0e5ca479f --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_treatNonCallableAsNull.py @@ -0,0 +1,56 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + callback Function = any(any... arguments); + + interface TestTreatNonCallableAsNull1 { + [TreatNonCallableAsNull] attribute Function? onfoo; + attribute Function? onbar; + }; + """) + + results = parser.finish() + + iface = results[1] + attr = iface.members[0] + harness.check(attr.type.treatNonCallableAsNull(), True, "Got the expected value") + attr = iface.members[1] + harness.check(attr.type.treatNonCallableAsNull(), False, "Got the expected value") + + parser = parser.reset() + + threw = False + try: + parser.parse(""" + callback Function = any(any... arguments); + + interface TestTreatNonCallableAsNull2 { + [TreatNonCallableAsNull] attribute Function onfoo; + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() + + threw = False + try: + parser.parse(""" + callback Function = any(any... arguments); + + [TreatNonCallableAsNull] + interface TestTreatNonCallableAsNull3 { + attribute Function onfoo; + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_typedef.py b/components/script/dom/bindings/codegen/parser/tests/test_typedef.py new file mode 100644 index 00000000000..9d2f3b3c2ce --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_typedef.py @@ -0,0 +1,76 @@ +def WebIDLTest(parser, harness): + parser.parse(""" + typedef long mylong; + typedef long? mynullablelong; + interface Foo { + const mylong X = 5; + const mynullablelong Y = 7; + const mynullablelong Z = null; + void foo(mylong arg); + }; + """) + + results = parser.finish() + + harness.check(results[2].members[1].type.name, "Long", + "Should expand typedefs") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + typedef long? mynullablelong; + interface Foo { + void foo(mynullablelong? Y); + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown on nullable inside nullable arg.") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + typedef long? mynullablelong; + interface Foo { + const mynullablelong? X = 5; + }; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown on nullable inside nullable const.") + + parser = parser.reset() + threw = False + try: + parser.parse(""" + interface Foo { + const mynullablelong? X = 5; + }; + typedef long? mynullablelong; + """) + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Should have thrown on nullable inside nullable const typedef " + "after interface.") + + parser = parser.reset() + parser.parse(""" + interface Foo { + const mylong X = 5; + }; + typedef long mylong; + """) + + results = parser.finish() + + harness.check(results[0].members[0].type.name, "Long", + "Should expand typedefs that come before interface") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_union.py b/components/script/dom/bindings/codegen/parser/tests/test_union.py new file mode 100644 index 00000000000..68c2bcade8c --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_union.py @@ -0,0 +1,169 @@ +import WebIDL +import itertools +import string + +# We'd like to use itertools.chain but it's 2.6 or higher. +def chain(*iterables): + # chain('ABC', 'DEF') --> A B C D E F + for it in iterables: + for element in it: + yield element + +# We'd like to use itertools.combinations but it's 2.6 or higher. +def combinations(iterable, r): + # combinations('ABCD', 2) --> AB AC AD BC BD CD + # combinations(range(4), 3) --> 012 013 023 123 + pool = tuple(iterable) + n = len(pool) + if r > n: + return + indices = range(r) + yield tuple(pool[i] for i in indices) + while True: + for i in reversed(range(r)): + if indices[i] != i + n - r: + break + else: + return + indices[i] += 1 + for j in range(i+1, r): + indices[j] = indices[j-1] + 1 + yield tuple(pool[i] for i in indices) + +# We'd like to use itertools.combinations_with_replacement but it's 2.7 or +# higher. +def combinations_with_replacement(iterable, r): + # combinations_with_replacement('ABC', 2) --> AA AB AC BB BC CC + pool = tuple(iterable) + n = len(pool) + if not n and r: + return + indices = [0] * r + yield tuple(pool[i] for i in indices) + while True: + for i in reversed(range(r)): + if indices[i] != n - 1: + break + else: + return + indices[i:] = [indices[i] + 1] * (r - i) + yield tuple(pool[i] for i in indices) + +def WebIDLTest(parser, harness): + types = ["float", + "double", + "short", + "unsigned short", + "long", + "unsigned long", + "long long", + "unsigned long long", + "boolean", + "byte", + "octet", + "DOMString", + #"sequence<float>", + "object", + "ArrayBuffer", + #"Date", + "TestInterface1", + "TestInterface2"] + + testPre = """ + interface TestInterface1 { + }; + interface TestInterface2 { + }; + """ + + interface = testPre + """ + interface PrepareForTest { + """ + for (i, type) in enumerate(types): + interface += string.Template(""" + readonly attribute ${type} attr${i}; + """).substitute(i=i, type=type) + interface += """ + }; + """ + + parser.parse(interface) + results = parser.finish() + + iface = results[2] + + parser = parser.reset() + + def typesAreDistinguishable(t): + return all(u[0].isDistinguishableFrom(u[1]) for u in combinations(t, 2)) + def typesAreNotDistinguishable(t): + return any(not u[0].isDistinguishableFrom(u[1]) for u in combinations(t, 2)) + def unionTypeName(t): + if len(t) > 2: + t[0:2] = [unionTypeName(t[0:2])] + return "(" + " or ".join(t) + ")" + + # typeCombinations is an iterable of tuples containing the name of the type + # as a string and the parsed IDL type. + def unionTypes(typeCombinations, predicate): + for c in typeCombinations: + if predicate(t[1] for t in c): + yield unionTypeName([t[0] for t in c]) + + # We limit invalid union types with a union member type to the subset of 3 + # types with one invalid combination. + # typeCombinations is an iterable of tuples containing the name of the type + # as a string and the parsed IDL type. + def invalidUnionWithUnion(typeCombinations): + for c in typeCombinations: + if (typesAreNotDistinguishable((c[0][1], c[1][1])) and + typesAreDistinguishable((c[1][1], c[2][1])) and + typesAreDistinguishable((c[0][1], c[2][1]))): + yield unionTypeName([t[0] for t in c]) + + # Create a list of tuples containing the name of the type as a string and + # the parsed IDL type. + types = zip(types, (a.type for a in iface.members)) + + validUnionTypes = chain(unionTypes(combinations(types, 2), typesAreDistinguishable), + unionTypes(combinations(types, 3), typesAreDistinguishable)) + invalidUnionTypes = chain(unionTypes(combinations_with_replacement(types, 2), typesAreNotDistinguishable), + invalidUnionWithUnion(combinations(types, 3))) + interface = testPre + """ + interface TestUnion { + """ + for (i, type) in enumerate(validUnionTypes): + interface += string.Template(""" + void method${i}(${type} arg); + ${type} returnMethod${i}(); + attribute ${type} attr${i}; + void arrayMethod${i}(${type}[] arg); + ${type}[] arrayReturnMethod${i}(); + attribute ${type}[] arrayAttr${i}; + void optionalMethod${i}(${type}? arg); + """).substitute(i=i, type=type) + interface += """ + }; + """ + parser.parse(interface) + results = parser.finish() + + parser = parser.reset() + + for invalid in invalidUnionTypes: + interface = testPre + string.Template(""" + interface TestUnion { + void method(${type} arg); + }; + """).substitute(type=invalid) + + threw = False + try: + parser.parse(interface) + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + parser = parser.reset() diff --git a/components/script/dom/bindings/codegen/parser/tests/test_union_any.py b/components/script/dom/bindings/codegen/parser/tests/test_union_any.py new file mode 100644 index 00000000000..e34cadab470 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_union_any.py @@ -0,0 +1,14 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface AnyNotInUnion { + void foo((any or DOMString) arg); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_union_nullable.py b/components/script/dom/bindings/codegen/parser/tests/test_union_nullable.py new file mode 100644 index 00000000000..08430a94a2e --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_union_nullable.py @@ -0,0 +1,53 @@ +def WebIDLTest(parser, harness): + threw = False + try: + parser.parse(""" + interface OneNullableInUnion { + void foo((object? or DOMString?) arg); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "Two nullable member types of a union should have thrown.") + + parser.reset() + threw = False + + try: + parser.parse(""" + interface NullableInNullableUnion { + void foo((object? or DOMString)? arg); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "A nullable union type with a nullable member type should have " + "thrown.") + + parser.reset() + threw = False + + try: + parser.parse(""" + interface NullableInUnionNullableUnionHelper { + }; + interface NullableInUnionNullableUnion { + void foo(((object? or DOMString) or NullableInUnionNullableUnionHelper)? arg); + }; + """) + + results = parser.finish() + except: + threw = True + + harness.ok(threw, + "A nullable union type with a nullable member type should have " + "thrown.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_variadic_callback.py b/components/script/dom/bindings/codegen/parser/tests/test_variadic_callback.py new file mode 100644 index 00000000000..d9a78db2043 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_variadic_callback.py @@ -0,0 +1,10 @@ +import WebIDL + +def WebIDLTest(parser, harness): + parser.parse(""" + callback TestVariadicCallback = any(any... arguments); + """) + + results = parser.finish() + + harness.ok(True, "TestVariadicCallback callback parsed without error.") diff --git a/components/script/dom/bindings/codegen/parser/tests/test_variadic_constraints.py b/components/script/dom/bindings/codegen/parser/tests/test_variadic_constraints.py new file mode 100644 index 00000000000..9cba22c5842 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/tests/test_variadic_constraints.py @@ -0,0 +1,39 @@ +def WebIDLTest(parser, harness): + threw = False + try: + results = parser.parse(""" + interface VariadicConstraints1 { + void foo(byte... arg1, byte arg2); + }; + """) + + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + results = parser.parse(""" + interface VariadicConstraints2 { + void foo(byte... arg1, optional byte arg2); + }; + """) + + except: + threw = True + + harness.ok(threw, "Should have thrown.") + + threw = False + try: + results = parser.parse(""" + interface VariadicConstraints3 { + void foo(optional byte... arg1); + }; + """) + + except: + threw = True + + harness.ok(threw, "Should have thrown.") diff --git a/components/script/dom/bindings/codegen/parser/update.sh b/components/script/dom/bindings/codegen/parser/update.sh new file mode 100755 index 00000000000..5dd513812e1 --- /dev/null +++ b/components/script/dom/bindings/codegen/parser/update.sh @@ -0,0 +1,3 @@ +wget https://mxr.mozilla.org/mozilla-central/source/dom/bindings/parser/WebIDL.py?raw=1 -O WebIDL.py +patch < external.patch +patch < module.patch diff --git a/components/script/dom/bindings/codegen/ply/COPYING b/components/script/dom/bindings/codegen/ply/COPYING new file mode 100644 index 00000000000..3b107de4508 --- /dev/null +++ b/components/script/dom/bindings/codegen/ply/COPYING @@ -0,0 +1,28 @@ +Copyright (C) 2001-2009, +David M. Beazley (Dabeaz LLC) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of the David Beazley or Dabeaz LLC may be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/components/script/dom/bindings/codegen/ply/README b/components/script/dom/bindings/codegen/ply/README new file mode 100644 index 00000000000..2459c490197 --- /dev/null +++ b/components/script/dom/bindings/codegen/ply/README @@ -0,0 +1,9 @@ +David Beazley's PLY (Python Lex-Yacc) +http://www.dabeaz.com/ply/ + +Licensed under BSD. + +This directory contains just the code and license from PLY version 3.3; +the full distribution (see the URL) also contains examples, tests, +documentation, and a longer README. + diff --git a/components/script/dom/bindings/codegen/ply/ply/__init__.py b/components/script/dom/bindings/codegen/ply/ply/__init__.py new file mode 100644 index 00000000000..853a985542b --- /dev/null +++ b/components/script/dom/bindings/codegen/ply/ply/__init__.py @@ -0,0 +1,4 @@ +# PLY package +# Author: David Beazley (dave@dabeaz.com) + +__all__ = ['lex','yacc'] diff --git a/components/script/dom/bindings/codegen/ply/ply/lex.py b/components/script/dom/bindings/codegen/ply/ply/lex.py new file mode 100644 index 00000000000..267ec100fc2 --- /dev/null +++ b/components/script/dom/bindings/codegen/ply/ply/lex.py @@ -0,0 +1,1058 @@ +# ----------------------------------------------------------------------------- +# ply: lex.py +# +# Copyright (C) 2001-2009, +# David M. Beazley (Dabeaz LLC) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the David Beazley or Dabeaz LLC may be used to +# endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------------------------- + +__version__ = "3.3" +__tabversion__ = "3.2" # Version of table file used + +import re, sys, types, copy, os + +# This tuple contains known string types +try: + # Python 2.6 + StringTypes = (types.StringType, types.UnicodeType) +except AttributeError: + # Python 3.0 + StringTypes = (str, bytes) + +# Extract the code attribute of a function. Different implementations +# are for Python 2/3 compatibility. + +if sys.version_info[0] < 3: + def func_code(f): + return f.func_code +else: + def func_code(f): + return f.__code__ + +# This regular expression is used to match valid token names +_is_identifier = re.compile(r'^[a-zA-Z0-9_]+$') + +# Exception thrown when invalid token encountered and no default error +# handler is defined. + +class LexError(Exception): + def __init__(self,message,s): + self.args = (message,) + self.text = s + +# Token class. This class is used to represent the tokens produced. +class LexToken(object): + def __str__(self): + return "LexToken(%s,%r,%d,%d)" % (self.type,self.value,self.lineno,self.lexpos) + def __repr__(self): + return str(self) + +# This object is a stand-in for a logging object created by the +# logging module. + +class PlyLogger(object): + def __init__(self,f): + self.f = f + def critical(self,msg,*args,**kwargs): + self.f.write((msg % args) + "\n") + + def warning(self,msg,*args,**kwargs): + self.f.write("WARNING: "+ (msg % args) + "\n") + + def error(self,msg,*args,**kwargs): + self.f.write("ERROR: " + (msg % args) + "\n") + + info = critical + debug = critical + +# Null logger is used when no output is generated. Does nothing. +class NullLogger(object): + def __getattribute__(self,name): + return self + def __call__(self,*args,**kwargs): + return self + +# ----------------------------------------------------------------------------- +# === Lexing Engine === +# +# The following Lexer class implements the lexer runtime. There are only +# a few public methods and attributes: +# +# input() - Store a new string in the lexer +# token() - Get the next token +# clone() - Clone the lexer +# +# lineno - Current line number +# lexpos - Current position in the input string +# ----------------------------------------------------------------------------- + +class Lexer: + def __init__(self): + self.lexre = None # Master regular expression. This is a list of + # tuples (re,findex) where re is a compiled + # regular expression and findex is a list + # mapping regex group numbers to rules + self.lexretext = None # Current regular expression strings + self.lexstatere = {} # Dictionary mapping lexer states to master regexs + self.lexstateretext = {} # Dictionary mapping lexer states to regex strings + self.lexstaterenames = {} # Dictionary mapping lexer states to symbol names + self.lexstate = "INITIAL" # Current lexer state + self.lexstatestack = [] # Stack of lexer states + self.lexstateinfo = None # State information + self.lexstateignore = {} # Dictionary of ignored characters for each state + self.lexstateerrorf = {} # Dictionary of error functions for each state + self.lexreflags = 0 # Optional re compile flags + self.lexdata = None # Actual input data (as a string) + self.lexpos = 0 # Current position in input text + self.lexlen = 0 # Length of the input text + self.lexerrorf = None # Error rule (if any) + self.lextokens = None # List of valid tokens + self.lexignore = "" # Ignored characters + self.lexliterals = "" # Literal characters that can be passed through + self.lexmodule = None # Module + self.lineno = 1 # Current line number + self.lexoptimize = 0 # Optimized mode + + def clone(self,object=None): + c = copy.copy(self) + + # If the object parameter has been supplied, it means we are attaching the + # lexer to a new object. In this case, we have to rebind all methods in + # the lexstatere and lexstateerrorf tables. + + if object: + newtab = { } + for key, ritem in self.lexstatere.items(): + newre = [] + for cre, findex in ritem: + newfindex = [] + for f in findex: + if not f or not f[0]: + newfindex.append(f) + continue + newfindex.append((getattr(object,f[0].__name__),f[1])) + newre.append((cre,newfindex)) + newtab[key] = newre + c.lexstatere = newtab + c.lexstateerrorf = { } + for key, ef in self.lexstateerrorf.items(): + c.lexstateerrorf[key] = getattr(object,ef.__name__) + c.lexmodule = object + return c + + # ------------------------------------------------------------ + # writetab() - Write lexer information to a table file + # ------------------------------------------------------------ + def writetab(self,tabfile,outputdir=""): + if isinstance(tabfile,types.ModuleType): + return + basetabfilename = tabfile.split(".")[-1] + filename = os.path.join(outputdir,basetabfilename)+".py" + tf = open(filename,"w") + tf.write("# %s.py. This file automatically created by PLY (version %s). Don't edit!\n" % (tabfile,__version__)) + tf.write("_tabversion = %s\n" % repr(__version__)) + tf.write("_lextokens = %s\n" % repr(self.lextokens)) + tf.write("_lexreflags = %s\n" % repr(self.lexreflags)) + tf.write("_lexliterals = %s\n" % repr(self.lexliterals)) + tf.write("_lexstateinfo = %s\n" % repr(self.lexstateinfo)) + + tabre = { } + # Collect all functions in the initial state + initial = self.lexstatere["INITIAL"] + initialfuncs = [] + for part in initial: + for f in part[1]: + if f and f[0]: + initialfuncs.append(f) + + for key, lre in self.lexstatere.items(): + titem = [] + for i in range(len(lre)): + titem.append((self.lexstateretext[key][i],_funcs_to_names(lre[i][1],self.lexstaterenames[key][i]))) + tabre[key] = titem + + tf.write("_lexstatere = %s\n" % repr(tabre)) + tf.write("_lexstateignore = %s\n" % repr(self.lexstateignore)) + + taberr = { } + for key, ef in self.lexstateerrorf.items(): + if ef: + taberr[key] = ef.__name__ + else: + taberr[key] = None + tf.write("_lexstateerrorf = %s\n" % repr(taberr)) + tf.close() + + # ------------------------------------------------------------ + # readtab() - Read lexer information from a tab file + # ------------------------------------------------------------ + def readtab(self,tabfile,fdict): + if isinstance(tabfile,types.ModuleType): + lextab = tabfile + else: + if sys.version_info[0] < 3: + exec("import %s as lextab" % tabfile) + else: + env = { } + exec("import %s as lextab" % tabfile, env,env) + lextab = env['lextab'] + + if getattr(lextab,"_tabversion","0.0") != __version__: + raise ImportError("Inconsistent PLY version") + + self.lextokens = lextab._lextokens + self.lexreflags = lextab._lexreflags + self.lexliterals = lextab._lexliterals + self.lexstateinfo = lextab._lexstateinfo + self.lexstateignore = lextab._lexstateignore + self.lexstatere = { } + self.lexstateretext = { } + for key,lre in lextab._lexstatere.items(): + titem = [] + txtitem = [] + for i in range(len(lre)): + titem.append((re.compile(lre[i][0],lextab._lexreflags | re.VERBOSE),_names_to_funcs(lre[i][1],fdict))) + txtitem.append(lre[i][0]) + self.lexstatere[key] = titem + self.lexstateretext[key] = txtitem + self.lexstateerrorf = { } + for key,ef in lextab._lexstateerrorf.items(): + self.lexstateerrorf[key] = fdict[ef] + self.begin('INITIAL') + + # ------------------------------------------------------------ + # input() - Push a new string into the lexer + # ------------------------------------------------------------ + def input(self,s): + # Pull off the first character to see if s looks like a string + c = s[:1] + if not isinstance(c,StringTypes): + raise ValueError("Expected a string") + self.lexdata = s + self.lexpos = 0 + self.lexlen = len(s) + + # ------------------------------------------------------------ + # begin() - Changes the lexing state + # ------------------------------------------------------------ + def begin(self,state): + if not state in self.lexstatere: + raise ValueError("Undefined state") + self.lexre = self.lexstatere[state] + self.lexretext = self.lexstateretext[state] + self.lexignore = self.lexstateignore.get(state,"") + self.lexerrorf = self.lexstateerrorf.get(state,None) + self.lexstate = state + + # ------------------------------------------------------------ + # push_state() - Changes the lexing state and saves old on stack + # ------------------------------------------------------------ + def push_state(self,state): + self.lexstatestack.append(self.lexstate) + self.begin(state) + + # ------------------------------------------------------------ + # pop_state() - Restores the previous state + # ------------------------------------------------------------ + def pop_state(self): + self.begin(self.lexstatestack.pop()) + + # ------------------------------------------------------------ + # current_state() - Returns the current lexing state + # ------------------------------------------------------------ + def current_state(self): + return self.lexstate + + # ------------------------------------------------------------ + # skip() - Skip ahead n characters + # ------------------------------------------------------------ + def skip(self,n): + self.lexpos += n + + # ------------------------------------------------------------ + # opttoken() - Return the next token from the Lexer + # + # Note: This function has been carefully implemented to be as fast + # as possible. Don't make changes unless you really know what + # you are doing + # ------------------------------------------------------------ + def token(self): + # Make local copies of frequently referenced attributes + lexpos = self.lexpos + lexlen = self.lexlen + lexignore = self.lexignore + lexdata = self.lexdata + + while lexpos < lexlen: + # This code provides some short-circuit code for whitespace, tabs, and other ignored characters + if lexdata[lexpos] in lexignore: + lexpos += 1 + continue + + # Look for a regular expression match + for lexre,lexindexfunc in self.lexre: + m = lexre.match(lexdata,lexpos) + if not m: continue + + # Create a token for return + tok = LexToken() + tok.value = m.group() + tok.lineno = self.lineno + tok.lexpos = lexpos + + i = m.lastindex + func,tok.type = lexindexfunc[i] + + if not func: + # If no token type was set, it's an ignored token + if tok.type: + self.lexpos = m.end() + return tok + else: + lexpos = m.end() + break + + lexpos = m.end() + + # If token is processed by a function, call it + + tok.lexer = self # Set additional attributes useful in token rules + self.lexmatch = m + self.lexpos = lexpos + + newtok = func(tok) + + # Every function must return a token, if nothing, we just move to next token + if not newtok: + lexpos = self.lexpos # This is here in case user has updated lexpos. + lexignore = self.lexignore # This is here in case there was a state change + break + + # Verify type of the token. If not in the token map, raise an error + if not self.lexoptimize: + if not newtok.type in self.lextokens: + raise LexError("%s:%d: Rule '%s' returned an unknown token type '%s'" % ( + func_code(func).co_filename, func_code(func).co_firstlineno, + func.__name__, newtok.type),lexdata[lexpos:]) + + return newtok + else: + # No match, see if in literals + if lexdata[lexpos] in self.lexliterals: + tok = LexToken() + tok.value = lexdata[lexpos] + tok.lineno = self.lineno + tok.type = tok.value + tok.lexpos = lexpos + self.lexpos = lexpos + 1 + return tok + + # No match. Call t_error() if defined. + if self.lexerrorf: + tok = LexToken() + tok.value = self.lexdata[lexpos:] + tok.lineno = self.lineno + tok.type = "error" + tok.lexer = self + tok.lexpos = lexpos + self.lexpos = lexpos + newtok = self.lexerrorf(tok) + if lexpos == self.lexpos: + # Error method didn't change text position at all. This is an error. + raise LexError("Scanning error. Illegal character '%s'" % (lexdata[lexpos]), lexdata[lexpos:]) + lexpos = self.lexpos + if not newtok: continue + return newtok + + self.lexpos = lexpos + raise LexError("Illegal character '%s' at index %d" % (lexdata[lexpos],lexpos), lexdata[lexpos:]) + + self.lexpos = lexpos + 1 + if self.lexdata is None: + raise RuntimeError("No input string given with input()") + return None + + # Iterator interface + def __iter__(self): + return self + + def next(self): + t = self.token() + if t is None: + raise StopIteration + return t + + __next__ = next + +# ----------------------------------------------------------------------------- +# ==== Lex Builder === +# +# The functions and classes below are used to collect lexing information +# and build a Lexer object from it. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# get_caller_module_dict() +# +# This function returns a dictionary containing all of the symbols defined within +# a caller further down the call stack. This is used to get the environment +# associated with the yacc() call if none was provided. +# ----------------------------------------------------------------------------- + +def get_caller_module_dict(levels): + try: + raise RuntimeError + except RuntimeError: + e,b,t = sys.exc_info() + f = t.tb_frame + while levels > 0: + f = f.f_back + levels -= 1 + ldict = f.f_globals.copy() + if f.f_globals != f.f_locals: + ldict.update(f.f_locals) + + return ldict + +# ----------------------------------------------------------------------------- +# _funcs_to_names() +# +# Given a list of regular expression functions, this converts it to a list +# suitable for output to a table file +# ----------------------------------------------------------------------------- + +def _funcs_to_names(funclist,namelist): + result = [] + for f,name in zip(funclist,namelist): + if f and f[0]: + result.append((name, f[1])) + else: + result.append(f) + return result + +# ----------------------------------------------------------------------------- +# _names_to_funcs() +# +# Given a list of regular expression function names, this converts it back to +# functions. +# ----------------------------------------------------------------------------- + +def _names_to_funcs(namelist,fdict): + result = [] + for n in namelist: + if n and n[0]: + result.append((fdict[n[0]],n[1])) + else: + result.append(n) + return result + +# ----------------------------------------------------------------------------- +# _form_master_re() +# +# This function takes a list of all of the regex components and attempts to +# form the master regular expression. Given limitations in the Python re +# module, it may be necessary to break the master regex into separate expressions. +# ----------------------------------------------------------------------------- + +def _form_master_re(relist,reflags,ldict,toknames): + if not relist: return [] + regex = "|".join(relist) + try: + lexre = re.compile(regex,re.VERBOSE | reflags) + + # Build the index to function map for the matching engine + lexindexfunc = [ None ] * (max(lexre.groupindex.values())+1) + lexindexnames = lexindexfunc[:] + + for f,i in lexre.groupindex.items(): + handle = ldict.get(f,None) + if type(handle) in (types.FunctionType, types.MethodType): + lexindexfunc[i] = (handle,toknames[f]) + lexindexnames[i] = f + elif handle is not None: + lexindexnames[i] = f + if f.find("ignore_") > 0: + lexindexfunc[i] = (None,None) + else: + lexindexfunc[i] = (None, toknames[f]) + + return [(lexre,lexindexfunc)],[regex],[lexindexnames] + except Exception: + m = int(len(relist)/2) + if m == 0: m = 1 + llist, lre, lnames = _form_master_re(relist[:m],reflags,ldict,toknames) + rlist, rre, rnames = _form_master_re(relist[m:],reflags,ldict,toknames) + return llist+rlist, lre+rre, lnames+rnames + +# ----------------------------------------------------------------------------- +# def _statetoken(s,names) +# +# Given a declaration name s of the form "t_" and a dictionary whose keys are +# state names, this function returns a tuple (states,tokenname) where states +# is a tuple of state names and tokenname is the name of the token. For example, +# calling this with s = "t_foo_bar_SPAM" might return (('foo','bar'),'SPAM') +# ----------------------------------------------------------------------------- + +def _statetoken(s,names): + nonstate = 1 + parts = s.split("_") + for i in range(1,len(parts)): + if not parts[i] in names and parts[i] != 'ANY': break + if i > 1: + states = tuple(parts[1:i]) + else: + states = ('INITIAL',) + + if 'ANY' in states: + states = tuple(names) + + tokenname = "_".join(parts[i:]) + return (states,tokenname) + + +# ----------------------------------------------------------------------------- +# LexerReflect() +# +# This class represents information needed to build a lexer as extracted from a +# user's input file. +# ----------------------------------------------------------------------------- +class LexerReflect(object): + def __init__(self,ldict,log=None,reflags=0): + self.ldict = ldict + self.error_func = None + self.tokens = [] + self.reflags = reflags + self.stateinfo = { 'INITIAL' : 'inclusive'} + self.files = {} + self.error = 0 + + if log is None: + self.log = PlyLogger(sys.stderr) + else: + self.log = log + + # Get all of the basic information + def get_all(self): + self.get_tokens() + self.get_literals() + self.get_states() + self.get_rules() + + # Validate all of the information + def validate_all(self): + self.validate_tokens() + self.validate_literals() + self.validate_rules() + return self.error + + # Get the tokens map + def get_tokens(self): + tokens = self.ldict.get("tokens",None) + if not tokens: + self.log.error("No token list is defined") + self.error = 1 + return + + if not isinstance(tokens,(list, tuple)): + self.log.error("tokens must be a list or tuple") + self.error = 1 + return + + if not tokens: + self.log.error("tokens is empty") + self.error = 1 + return + + self.tokens = tokens + + # Validate the tokens + def validate_tokens(self): + terminals = {} + for n in self.tokens: + if not _is_identifier.match(n): + self.log.error("Bad token name '%s'",n) + self.error = 1 + if n in terminals: + self.log.warning("Token '%s' multiply defined", n) + terminals[n] = 1 + + # Get the literals specifier + def get_literals(self): + self.literals = self.ldict.get("literals","") + + # Validate literals + def validate_literals(self): + try: + for c in self.literals: + if not isinstance(c,StringTypes) or len(c) > 1: + self.log.error("Invalid literal %s. Must be a single character", repr(c)) + self.error = 1 + continue + + except TypeError: + self.log.error("Invalid literals specification. literals must be a sequence of characters") + self.error = 1 + + def get_states(self): + self.states = self.ldict.get("states",None) + # Build statemap + if self.states: + if not isinstance(self.states,(tuple,list)): + self.log.error("states must be defined as a tuple or list") + self.error = 1 + else: + for s in self.states: + if not isinstance(s,tuple) or len(s) != 2: + self.log.error("Invalid state specifier %s. Must be a tuple (statename,'exclusive|inclusive')",repr(s)) + self.error = 1 + continue + name, statetype = s + if not isinstance(name,StringTypes): + self.log.error("State name %s must be a string", repr(name)) + self.error = 1 + continue + if not (statetype == 'inclusive' or statetype == 'exclusive'): + self.log.error("State type for state %s must be 'inclusive' or 'exclusive'",name) + self.error = 1 + continue + if name in self.stateinfo: + self.log.error("State '%s' already defined",name) + self.error = 1 + continue + self.stateinfo[name] = statetype + + # Get all of the symbols with a t_ prefix and sort them into various + # categories (functions, strings, error functions, and ignore characters) + + def get_rules(self): + tsymbols = [f for f in self.ldict if f[:2] == 't_' ] + + # Now build up a list of functions and a list of strings + + self.toknames = { } # Mapping of symbols to token names + self.funcsym = { } # Symbols defined as functions + self.strsym = { } # Symbols defined as strings + self.ignore = { } # Ignore strings by state + self.errorf = { } # Error functions by state + + for s in self.stateinfo: + self.funcsym[s] = [] + self.strsym[s] = [] + + if len(tsymbols) == 0: + self.log.error("No rules of the form t_rulename are defined") + self.error = 1 + return + + for f in tsymbols: + t = self.ldict[f] + states, tokname = _statetoken(f,self.stateinfo) + self.toknames[f] = tokname + + if hasattr(t,"__call__"): + if tokname == 'error': + for s in states: + self.errorf[s] = t + elif tokname == 'ignore': + line = func_code(t).co_firstlineno + file = func_code(t).co_filename + self.log.error("%s:%d: Rule '%s' must be defined as a string",file,line,t.__name__) + self.error = 1 + else: + for s in states: + self.funcsym[s].append((f,t)) + elif isinstance(t, StringTypes): + if tokname == 'ignore': + for s in states: + self.ignore[s] = t + if "\\" in t: + self.log.warning("%s contains a literal backslash '\\'",f) + + elif tokname == 'error': + self.log.error("Rule '%s' must be defined as a function", f) + self.error = 1 + else: + for s in states: + self.strsym[s].append((f,t)) + else: + self.log.error("%s not defined as a function or string", f) + self.error = 1 + + # Sort the functions by line number + for f in self.funcsym.values(): + if sys.version_info[0] < 3: + f.sort(lambda x,y: cmp(func_code(x[1]).co_firstlineno,func_code(y[1]).co_firstlineno)) + else: + # Python 3.0 + f.sort(key=lambda x: func_code(x[1]).co_firstlineno) + + # Sort the strings by regular expression length + for s in self.strsym.values(): + if sys.version_info[0] < 3: + s.sort(lambda x,y: (len(x[1]) < len(y[1])) - (len(x[1]) > len(y[1]))) + else: + # Python 3.0 + s.sort(key=lambda x: len(x[1]),reverse=True) + + # Validate all of the t_rules collected + def validate_rules(self): + for state in self.stateinfo: + # Validate all rules defined by functions + + + + for fname, f in self.funcsym[state]: + line = func_code(f).co_firstlineno + file = func_code(f).co_filename + self.files[file] = 1 + + tokname = self.toknames[fname] + if isinstance(f, types.MethodType): + reqargs = 2 + else: + reqargs = 1 + nargs = func_code(f).co_argcount + if nargs > reqargs: + self.log.error("%s:%d: Rule '%s' has too many arguments",file,line,f.__name__) + self.error = 1 + continue + + if nargs < reqargs: + self.log.error("%s:%d: Rule '%s' requires an argument", file,line,f.__name__) + self.error = 1 + continue + + if not f.__doc__: + self.log.error("%s:%d: No regular expression defined for rule '%s'",file,line,f.__name__) + self.error = 1 + continue + + try: + c = re.compile("(?P<%s>%s)" % (fname,f.__doc__), re.VERBOSE | self.reflags) + if c.match(""): + self.log.error("%s:%d: Regular expression for rule '%s' matches empty string", file,line,f.__name__) + self.error = 1 + except re.error: + _etype, e, _etrace = sys.exc_info() + self.log.error("%s:%d: Invalid regular expression for rule '%s'. %s", file,line,f.__name__,e) + if '#' in f.__doc__: + self.log.error("%s:%d. Make sure '#' in rule '%s' is escaped with '\\#'",file,line, f.__name__) + self.error = 1 + + # Validate all rules defined by strings + for name,r in self.strsym[state]: + tokname = self.toknames[name] + if tokname == 'error': + self.log.error("Rule '%s' must be defined as a function", name) + self.error = 1 + continue + + if not tokname in self.tokens and tokname.find("ignore_") < 0: + self.log.error("Rule '%s' defined for an unspecified token %s",name,tokname) + self.error = 1 + continue + + try: + c = re.compile("(?P<%s>%s)" % (name,r),re.VERBOSE | self.reflags) + if (c.match("")): + self.log.error("Regular expression for rule '%s' matches empty string",name) + self.error = 1 + except re.error: + _etype, e, _etrace = sys.exc_info() + self.log.error("Invalid regular expression for rule '%s'. %s",name,e) + if '#' in r: + self.log.error("Make sure '#' in rule '%s' is escaped with '\\#'",name) + self.error = 1 + + if not self.funcsym[state] and not self.strsym[state]: + self.log.error("No rules defined for state '%s'",state) + self.error = 1 + + # Validate the error function + efunc = self.errorf.get(state,None) + if efunc: + f = efunc + line = func_code(f).co_firstlineno + file = func_code(f).co_filename + self.files[file] = 1 + + if isinstance(f, types.MethodType): + reqargs = 2 + else: + reqargs = 1 + nargs = func_code(f).co_argcount + if nargs > reqargs: + self.log.error("%s:%d: Rule '%s' has too many arguments",file,line,f.__name__) + self.error = 1 + + if nargs < reqargs: + self.log.error("%s:%d: Rule '%s' requires an argument", file,line,f.__name__) + self.error = 1 + + for f in self.files: + self.validate_file(f) + + + # ----------------------------------------------------------------------------- + # validate_file() + # + # This checks to see if there are duplicated t_rulename() functions or strings + # in the parser input file. This is done using a simple regular expression + # match on each line in the given file. + # ----------------------------------------------------------------------------- + + def validate_file(self,filename): + import os.path + base,ext = os.path.splitext(filename) + if ext != '.py': return # No idea what the file is. Return OK + + try: + f = open(filename) + lines = f.readlines() + f.close() + except IOError: + return # Couldn't find the file. Don't worry about it + + fre = re.compile(r'\s*def\s+(t_[a-zA-Z_0-9]*)\(') + sre = re.compile(r'\s*(t_[a-zA-Z_0-9]*)\s*=') + + counthash = { } + linen = 1 + for l in lines: + m = fre.match(l) + if not m: + m = sre.match(l) + if m: + name = m.group(1) + prev = counthash.get(name) + if not prev: + counthash[name] = linen + else: + self.log.error("%s:%d: Rule %s redefined. Previously defined on line %d",filename,linen,name,prev) + self.error = 1 + linen += 1 + +# ----------------------------------------------------------------------------- +# lex(module) +# +# Build all of the regular expression rules from definitions in the supplied module +# ----------------------------------------------------------------------------- +def lex(module=None,object=None,debug=0,optimize=0,lextab="lextab",reflags=0,nowarn=0,outputdir="", debuglog=None, errorlog=None): + global lexer + ldict = None + stateinfo = { 'INITIAL' : 'inclusive'} + lexobj = Lexer() + lexobj.lexoptimize = optimize + global token,input + + if errorlog is None: + errorlog = PlyLogger(sys.stderr) + + if debug: + if debuglog is None: + debuglog = PlyLogger(sys.stderr) + + # Get the module dictionary used for the lexer + if object: module = object + + if module: + _items = [(k,getattr(module,k)) for k in dir(module)] + ldict = dict(_items) + else: + ldict = get_caller_module_dict(2) + + # Collect parser information from the dictionary + linfo = LexerReflect(ldict,log=errorlog,reflags=reflags) + linfo.get_all() + if not optimize: + if linfo.validate_all(): + raise SyntaxError("Can't build lexer") + + if optimize and lextab: + try: + lexobj.readtab(lextab,ldict) + token = lexobj.token + input = lexobj.input + lexer = lexobj + return lexobj + + except ImportError: + pass + + # Dump some basic debugging information + if debug: + debuglog.info("lex: tokens = %r", linfo.tokens) + debuglog.info("lex: literals = %r", linfo.literals) + debuglog.info("lex: states = %r", linfo.stateinfo) + + # Build a dictionary of valid token names + lexobj.lextokens = { } + for n in linfo.tokens: + lexobj.lextokens[n] = 1 + + # Get literals specification + if isinstance(linfo.literals,(list,tuple)): + lexobj.lexliterals = type(linfo.literals[0])().join(linfo.literals) + else: + lexobj.lexliterals = linfo.literals + + # Get the stateinfo dictionary + stateinfo = linfo.stateinfo + + regexs = { } + # Build the master regular expressions + for state in stateinfo: + regex_list = [] + + # Add rules defined by functions first + for fname, f in linfo.funcsym[state]: + line = func_code(f).co_firstlineno + file = func_code(f).co_filename + regex_list.append("(?P<%s>%s)" % (fname,f.__doc__)) + if debug: + debuglog.info("lex: Adding rule %s -> '%s' (state '%s')",fname,f.__doc__, state) + + # Now add all of the simple rules + for name,r in linfo.strsym[state]: + regex_list.append("(?P<%s>%s)" % (name,r)) + if debug: + debuglog.info("lex: Adding rule %s -> '%s' (state '%s')",name,r, state) + + regexs[state] = regex_list + + # Build the master regular expressions + + if debug: + debuglog.info("lex: ==== MASTER REGEXS FOLLOW ====") + + for state in regexs: + lexre, re_text, re_names = _form_master_re(regexs[state],reflags,ldict,linfo.toknames) + lexobj.lexstatere[state] = lexre + lexobj.lexstateretext[state] = re_text + lexobj.lexstaterenames[state] = re_names + if debug: + for i in range(len(re_text)): + debuglog.info("lex: state '%s' : regex[%d] = '%s'",state, i, re_text[i]) + + # For inclusive states, we need to add the regular expressions from the INITIAL state + for state,stype in stateinfo.items(): + if state != "INITIAL" and stype == 'inclusive': + lexobj.lexstatere[state].extend(lexobj.lexstatere['INITIAL']) + lexobj.lexstateretext[state].extend(lexobj.lexstateretext['INITIAL']) + lexobj.lexstaterenames[state].extend(lexobj.lexstaterenames['INITIAL']) + + lexobj.lexstateinfo = stateinfo + lexobj.lexre = lexobj.lexstatere["INITIAL"] + lexobj.lexretext = lexobj.lexstateretext["INITIAL"] + lexobj.lexreflags = reflags + + # Set up ignore variables + lexobj.lexstateignore = linfo.ignore + lexobj.lexignore = lexobj.lexstateignore.get("INITIAL","") + + # Set up error functions + lexobj.lexstateerrorf = linfo.errorf + lexobj.lexerrorf = linfo.errorf.get("INITIAL",None) + if not lexobj.lexerrorf: + errorlog.warning("No t_error rule is defined") + + # Check state information for ignore and error rules + for s,stype in stateinfo.items(): + if stype == 'exclusive': + if not s in linfo.errorf: + errorlog.warning("No error rule is defined for exclusive state '%s'", s) + if not s in linfo.ignore and lexobj.lexignore: + errorlog.warning("No ignore rule is defined for exclusive state '%s'", s) + elif stype == 'inclusive': + if not s in linfo.errorf: + linfo.errorf[s] = linfo.errorf.get("INITIAL",None) + if not s in linfo.ignore: + linfo.ignore[s] = linfo.ignore.get("INITIAL","") + + # Create global versions of the token() and input() functions + token = lexobj.token + input = lexobj.input + lexer = lexobj + + # If in optimize mode, we write the lextab + if lextab and optimize: + lexobj.writetab(lextab,outputdir) + + return lexobj + +# ----------------------------------------------------------------------------- +# runmain() +# +# This runs the lexer as a main program +# ----------------------------------------------------------------------------- + +def runmain(lexer=None,data=None): + if not data: + try: + filename = sys.argv[1] + f = open(filename) + data = f.read() + f.close() + except IndexError: + sys.stdout.write("Reading from standard input (type EOF to end):\n") + data = sys.stdin.read() + + if lexer: + _input = lexer.input + else: + _input = input + _input(data) + if lexer: + _token = lexer.token + else: + _token = token + + while 1: + tok = _token() + if not tok: break + sys.stdout.write("(%s,%r,%d,%d)\n" % (tok.type, tok.value, tok.lineno,tok.lexpos)) + +# ----------------------------------------------------------------------------- +# @TOKEN(regex) +# +# This decorator function can be used to set the regex expression on a function +# when its docstring might need to be set in an alternative way +# ----------------------------------------------------------------------------- + +def TOKEN(r): + def set_doc(f): + if hasattr(r,"__call__"): + f.__doc__ = r.__doc__ + else: + f.__doc__ = r + return f + return set_doc + +# Alternative spelling of the TOKEN decorator +Token = TOKEN + diff --git a/components/script/dom/bindings/codegen/ply/ply/yacc.py b/components/script/dom/bindings/codegen/ply/ply/yacc.py new file mode 100644 index 00000000000..e9f5c657551 --- /dev/null +++ b/components/script/dom/bindings/codegen/ply/ply/yacc.py @@ -0,0 +1,3276 @@ +# ----------------------------------------------------------------------------- +# ply: yacc.py +# +# Copyright (C) 2001-2009, +# David M. Beazley (Dabeaz LLC) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the David Beazley or Dabeaz LLC may be used to +# endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------------------------- +# +# This implements an LR parser that is constructed from grammar rules defined +# as Python functions. The grammer is specified by supplying the BNF inside +# Python documentation strings. The inspiration for this technique was borrowed +# from John Aycock's Spark parsing system. PLY might be viewed as cross between +# Spark and the GNU bison utility. +# +# The current implementation is only somewhat object-oriented. The +# LR parser itself is defined in terms of an object (which allows multiple +# parsers to co-exist). However, most of the variables used during table +# construction are defined in terms of global variables. Users shouldn't +# notice unless they are trying to define multiple parsers at the same +# time using threads (in which case they should have their head examined). +# +# This implementation supports both SLR and LALR(1) parsing. LALR(1) +# support was originally implemented by Elias Ioup (ezioup@alumni.uchicago.edu), +# using the algorithm found in Aho, Sethi, and Ullman "Compilers: Principles, +# Techniques, and Tools" (The Dragon Book). LALR(1) has since been replaced +# by the more efficient DeRemer and Pennello algorithm. +# +# :::::::: WARNING ::::::: +# +# Construction of LR parsing tables is fairly complicated and expensive. +# To make this module run fast, a *LOT* of work has been put into +# optimization---often at the expensive of readability and what might +# consider to be good Python "coding style." Modify the code at your +# own risk! +# ---------------------------------------------------------------------------- + +__version__ = "3.3" +__tabversion__ = "3.2" # Table version + +#----------------------------------------------------------------------------- +# === User configurable parameters === +# +# Change these to modify the default behavior of yacc (if you wish) +#----------------------------------------------------------------------------- + +yaccdebug = 1 # Debugging mode. If set, yacc generates a + # a 'parser.out' file in the current directory + +debug_file = 'parser.out' # Default name of the debugging file +tab_module = 'parsetab' # Default name of the table module +default_lr = 'LALR' # Default LR table generation method + +error_count = 3 # Number of symbols that must be shifted to leave recovery mode + +yaccdevel = 0 # Set to True if developing yacc. This turns off optimized + # implementations of certain functions. + +resultlimit = 40 # Size limit of results when running in debug mode. + +pickle_protocol = 0 # Protocol to use when writing pickle files + +import re, types, sys, os.path + +# Compatibility function for python 2.6/3.0 +if sys.version_info[0] < 3: + def func_code(f): + return f.func_code +else: + def func_code(f): + return f.__code__ + +# Compatibility +try: + MAXINT = sys.maxint +except AttributeError: + MAXINT = sys.maxsize + +# Python 2.x/3.0 compatibility. +def load_ply_lex(): + if sys.version_info[0] < 3: + import lex + else: + import ply.lex as lex + return lex + +# This object is a stand-in for a logging object created by the +# logging module. PLY will use this by default to create things +# such as the parser.out file. If a user wants more detailed +# information, they can create their own logging object and pass +# it into PLY. + +class PlyLogger(object): + def __init__(self,f): + self.f = f + def debug(self,msg,*args,**kwargs): + self.f.write((msg % args) + "\n") + info = debug + + def warning(self,msg,*args,**kwargs): + self.f.write("WARNING: "+ (msg % args) + "\n") + + def error(self,msg,*args,**kwargs): + self.f.write("ERROR: " + (msg % args) + "\n") + + critical = debug + +# Null logger is used when no output is generated. Does nothing. +class NullLogger(object): + def __getattribute__(self,name): + return self + def __call__(self,*args,**kwargs): + return self + +# Exception raised for yacc-related errors +class YaccError(Exception): pass + +# Format the result message that the parser produces when running in debug mode. +def format_result(r): + repr_str = repr(r) + if '\n' in repr_str: repr_str = repr(repr_str) + if len(repr_str) > resultlimit: + repr_str = repr_str[:resultlimit]+" ..." + result = "<%s @ 0x%x> (%s)" % (type(r).__name__,id(r),repr_str) + return result + + +# Format stack entries when the parser is running in debug mode +def format_stack_entry(r): + repr_str = repr(r) + if '\n' in repr_str: repr_str = repr(repr_str) + if len(repr_str) < 16: + return repr_str + else: + return "<%s @ 0x%x>" % (type(r).__name__,id(r)) + +#----------------------------------------------------------------------------- +# === LR Parsing Engine === +# +# The following classes are used for the LR parser itself. These are not +# used during table construction and are independent of the actual LR +# table generation algorithm +#----------------------------------------------------------------------------- + +# This class is used to hold non-terminal grammar symbols during parsing. +# It normally has the following attributes set: +# .type = Grammar symbol type +# .value = Symbol value +# .lineno = Starting line number +# .endlineno = Ending line number (optional, set automatically) +# .lexpos = Starting lex position +# .endlexpos = Ending lex position (optional, set automatically) + +class YaccSymbol: + def __str__(self): return self.type + def __repr__(self): return str(self) + +# This class is a wrapper around the objects actually passed to each +# grammar rule. Index lookup and assignment actually assign the +# .value attribute of the underlying YaccSymbol object. +# The lineno() method returns the line number of a given +# item (or 0 if not defined). The linespan() method returns +# a tuple of (startline,endline) representing the range of lines +# for a symbol. The lexspan() method returns a tuple (lexpos,endlexpos) +# representing the range of positional information for a symbol. + +class YaccProduction: + def __init__(self,s,stack=None): + self.slice = s + self.stack = stack + self.lexer = None + self.parser= None + def __getitem__(self,n): + if n >= 0: return self.slice[n].value + else: return self.stack[n].value + + def __setitem__(self,n,v): + self.slice[n].value = v + + def __getslice__(self,i,j): + return [s.value for s in self.slice[i:j]] + + def __len__(self): + return len(self.slice) + + def lineno(self,n): + return getattr(self.slice[n],"lineno",0) + + def set_lineno(self,n,lineno): + self.slice[n].lineno = lineno + + def linespan(self,n): + startline = getattr(self.slice[n],"lineno",0) + endline = getattr(self.slice[n],"endlineno",startline) + return startline,endline + + def lexpos(self,n): + return getattr(self.slice[n],"lexpos",0) + + def lexspan(self,n): + startpos = getattr(self.slice[n],"lexpos",0) + endpos = getattr(self.slice[n],"endlexpos",startpos) + return startpos,endpos + + def error(self): + raise SyntaxError + + +# ----------------------------------------------------------------------------- +# == LRParser == +# +# The LR Parsing engine. +# ----------------------------------------------------------------------------- + +class LRParser: + def __init__(self,lrtab,errorf): + self.productions = lrtab.lr_productions + self.action = lrtab.lr_action + self.goto = lrtab.lr_goto + self.errorfunc = errorf + + def errok(self): + self.errorok = 1 + + def restart(self): + del self.statestack[:] + del self.symstack[:] + sym = YaccSymbol() + sym.type = '$end' + self.symstack.append(sym) + self.statestack.append(0) + + def parse(self,input=None,lexer=None,debug=0,tracking=0,tokenfunc=None): + if debug or yaccdevel: + if isinstance(debug,int): + debug = PlyLogger(sys.stderr) + return self.parsedebug(input,lexer,debug,tracking,tokenfunc) + elif tracking: + return self.parseopt(input,lexer,debug,tracking,tokenfunc) + else: + return self.parseopt_notrack(input,lexer,debug,tracking,tokenfunc) + + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # parsedebug(). + # + # This is the debugging enabled version of parse(). All changes made to the + # parsing engine should be made here. For the non-debugging version, + # copy this code to a method parseopt() and delete all of the sections + # enclosed in: + # + # #--! DEBUG + # statements + # #--! DEBUG + # + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + def parsedebug(self,input=None,lexer=None,debug=None,tracking=0,tokenfunc=None): + lookahead = None # Current lookahead symbol + lookaheadstack = [ ] # Stack of lookahead symbols + actions = self.action # Local reference to action table (to avoid lookup on self.) + goto = self.goto # Local reference to goto table (to avoid lookup on self.) + prod = self.productions # Local reference to production list (to avoid lookup on self.) + pslice = YaccProduction(None) # Production object passed to grammar rules + errorcount = 0 # Used during error recovery + + # --! DEBUG + debug.info("PLY: PARSE DEBUG START") + # --! DEBUG + + # If no lexer was given, we will try to use the lex module + if not lexer: + lex = load_ply_lex() + lexer = lex.lexer + + # Set up the lexer and parser objects on pslice + pslice.lexer = lexer + pslice.parser = self + + # If input was supplied, pass to lexer + if input is not None: + lexer.input(input) + + if tokenfunc is None: + # Tokenize function + get_token = lexer.token + else: + get_token = tokenfunc + + # Set up the state and symbol stacks + + statestack = [ ] # Stack of parsing states + self.statestack = statestack + symstack = [ ] # Stack of grammar symbols + self.symstack = symstack + + pslice.stack = symstack # Put in the production + errtoken = None # Err token + + # The start state is assumed to be (0,$end) + + statestack.append(0) + sym = YaccSymbol() + sym.type = "$end" + symstack.append(sym) + state = 0 + while 1: + # Get the next symbol on the input. If a lookahead symbol + # is already set, we just use that. Otherwise, we'll pull + # the next token off of the lookaheadstack or from the lexer + + # --! DEBUG + debug.debug('') + debug.debug('State : %s', state) + # --! DEBUG + + if not lookahead: + if not lookaheadstack: + lookahead = get_token() # Get the next token + else: + lookahead = lookaheadstack.pop() + if not lookahead: + lookahead = YaccSymbol() + lookahead.type = "$end" + + # --! DEBUG + debug.debug('Stack : %s', + ("%s . %s" % (" ".join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip()) + # --! DEBUG + + # Check the action table + ltype = lookahead.type + t = actions[state].get(ltype) + + if t is not None: + if t > 0: + # shift a symbol on the stack + statestack.append(t) + state = t + + # --! DEBUG + debug.debug("Action : Shift and goto state %s", t) + # --! DEBUG + + symstack.append(lookahead) + lookahead = None + + # Decrease error count on successful shift + if errorcount: errorcount -=1 + continue + + if t < 0: + # reduce a symbol on the stack, emit a production + p = prod[-t] + pname = p.name + plen = p.len + + # Get production function + sym = YaccSymbol() + sym.type = pname # Production name + sym.value = None + + # --! DEBUG + if plen: + debug.info("Action : Reduce rule [%s] with %s and goto state %d", p.str, "["+",".join([format_stack_entry(_v.value) for _v in symstack[-plen:]])+"]",-t) + else: + debug.info("Action : Reduce rule [%s] with %s and goto state %d", p.str, [],-t) + + # --! DEBUG + + if plen: + targ = symstack[-plen-1:] + targ[0] = sym + + # --! TRACKING + if tracking: + t1 = targ[1] + sym.lineno = t1.lineno + sym.lexpos = t1.lexpos + t1 = targ[-1] + sym.endlineno = getattr(t1,"endlineno",t1.lineno) + sym.endlexpos = getattr(t1,"endlexpos",t1.lexpos) + + # --! TRACKING + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # The code enclosed in this section is duplicated + # below as a performance optimization. Make sure + # changes get made in both locations. + + pslice.slice = targ + + try: + # Call the grammar rule with our special slice object + del symstack[-plen:] + del statestack[-plen:] + p.callable(pslice) + # --! DEBUG + debug.info("Result : %s", format_result(pslice[0])) + # --! DEBUG + symstack.append(sym) + state = goto[statestack[-1]][pname] + statestack.append(state) + except SyntaxError: + # If an error was set. Enter error recovery state + lookaheadstack.append(lookahead) + symstack.pop() + statestack.pop() + state = statestack[-1] + sym.type = 'error' + lookahead = sym + errorcount = error_count + self.errorok = 0 + continue + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + else: + + # --! TRACKING + if tracking: + sym.lineno = lexer.lineno + sym.lexpos = lexer.lexpos + # --! TRACKING + + targ = [ sym ] + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # The code enclosed in this section is duplicated + # above as a performance optimization. Make sure + # changes get made in both locations. + + pslice.slice = targ + + try: + # Call the grammar rule with our special slice object + p.callable(pslice) + # --! DEBUG + debug.info("Result : %s", format_result(pslice[0])) + # --! DEBUG + symstack.append(sym) + state = goto[statestack[-1]][pname] + statestack.append(state) + except SyntaxError: + # If an error was set. Enter error recovery state + lookaheadstack.append(lookahead) + symstack.pop() + statestack.pop() + state = statestack[-1] + sym.type = 'error' + lookahead = sym + errorcount = error_count + self.errorok = 0 + continue + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + if t == 0: + n = symstack[-1] + result = getattr(n,"value",None) + # --! DEBUG + debug.info("Done : Returning %s", format_result(result)) + debug.info("PLY: PARSE DEBUG END") + # --! DEBUG + return result + + if t == None: + + # --! DEBUG + debug.error('Error : %s', + ("%s . %s" % (" ".join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip()) + # --! DEBUG + + # We have some kind of parsing error here. To handle + # this, we are going to push the current token onto + # the tokenstack and replace it with an 'error' token. + # If there are any synchronization rules, they may + # catch it. + # + # In addition to pushing the error token, we call call + # the user defined p_error() function if this is the + # first syntax error. This function is only called if + # errorcount == 0. + if errorcount == 0 or self.errorok: + errorcount = error_count + self.errorok = 0 + errtoken = lookahead + if errtoken.type == "$end": + errtoken = None # End of file! + if self.errorfunc: + global errok,token,restart + errok = self.errok # Set some special functions available in error recovery + token = get_token + restart = self.restart + if errtoken and not hasattr(errtoken,'lexer'): + errtoken.lexer = lexer + tok = self.errorfunc(errtoken) + del errok, token, restart # Delete special functions + + if self.errorok: + # User must have done some kind of panic + # mode recovery on their own. The + # returned token is the next lookahead + lookahead = tok + errtoken = None + continue + else: + if errtoken: + if hasattr(errtoken,"lineno"): lineno = lookahead.lineno + else: lineno = 0 + if lineno: + sys.stderr.write("yacc: Syntax error at line %d, token=%s\n" % (lineno, errtoken.type)) + else: + sys.stderr.write("yacc: Syntax error, token=%s" % errtoken.type) + else: + sys.stderr.write("yacc: Parse error in input. EOF\n") + return + + else: + errorcount = error_count + + # case 1: the statestack only has 1 entry on it. If we're in this state, the + # entire parse has been rolled back and we're completely hosed. The token is + # discarded and we just keep going. + + if len(statestack) <= 1 and lookahead.type != "$end": + lookahead = None + errtoken = None + state = 0 + # Nuke the pushback stack + del lookaheadstack[:] + continue + + # case 2: the statestack has a couple of entries on it, but we're + # at the end of the file. nuke the top entry and generate an error token + + # Start nuking entries on the stack + if lookahead.type == "$end": + # Whoa. We're really hosed here. Bail out + return + + if lookahead.type != 'error': + sym = symstack[-1] + if sym.type == 'error': + # Hmmm. Error is on top of stack, we'll just nuke input + # symbol and continue + lookahead = None + continue + t = YaccSymbol() + t.type = 'error' + if hasattr(lookahead,"lineno"): + t.lineno = lookahead.lineno + t.value = lookahead + lookaheadstack.append(lookahead) + lookahead = t + else: + symstack.pop() + statestack.pop() + state = statestack[-1] # Potential bug fix + + continue + + # Call an error function here + raise RuntimeError("yacc: internal parser error!!!\n") + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # parseopt(). + # + # Optimized version of parse() method. DO NOT EDIT THIS CODE DIRECTLY. + # Edit the debug version above, then copy any modifications to the method + # below while removing #--! DEBUG sections. + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + + def parseopt(self,input=None,lexer=None,debug=0,tracking=0,tokenfunc=None): + lookahead = None # Current lookahead symbol + lookaheadstack = [ ] # Stack of lookahead symbols + actions = self.action # Local reference to action table (to avoid lookup on self.) + goto = self.goto # Local reference to goto table (to avoid lookup on self.) + prod = self.productions # Local reference to production list (to avoid lookup on self.) + pslice = YaccProduction(None) # Production object passed to grammar rules + errorcount = 0 # Used during error recovery + + # If no lexer was given, we will try to use the lex module + if not lexer: + lex = load_ply_lex() + lexer = lex.lexer + + # Set up the lexer and parser objects on pslice + pslice.lexer = lexer + pslice.parser = self + + # If input was supplied, pass to lexer + if input is not None: + lexer.input(input) + + if tokenfunc is None: + # Tokenize function + get_token = lexer.token + else: + get_token = tokenfunc + + # Set up the state and symbol stacks + + statestack = [ ] # Stack of parsing states + self.statestack = statestack + symstack = [ ] # Stack of grammar symbols + self.symstack = symstack + + pslice.stack = symstack # Put in the production + errtoken = None # Err token + + # The start state is assumed to be (0,$end) + + statestack.append(0) + sym = YaccSymbol() + sym.type = '$end' + symstack.append(sym) + state = 0 + while 1: + # Get the next symbol on the input. If a lookahead symbol + # is already set, we just use that. Otherwise, we'll pull + # the next token off of the lookaheadstack or from the lexer + + if not lookahead: + if not lookaheadstack: + lookahead = get_token() # Get the next token + else: + lookahead = lookaheadstack.pop() + if not lookahead: + lookahead = YaccSymbol() + lookahead.type = '$end' + + # Check the action table + ltype = lookahead.type + t = actions[state].get(ltype) + + if t is not None: + if t > 0: + # shift a symbol on the stack + statestack.append(t) + state = t + + symstack.append(lookahead) + lookahead = None + + # Decrease error count on successful shift + if errorcount: errorcount -=1 + continue + + if t < 0: + # reduce a symbol on the stack, emit a production + p = prod[-t] + pname = p.name + plen = p.len + + # Get production function + sym = YaccSymbol() + sym.type = pname # Production name + sym.value = None + + if plen: + targ = symstack[-plen-1:] + targ[0] = sym + + # --! TRACKING + if tracking: + t1 = targ[1] + sym.lineno = t1.lineno + sym.lexpos = t1.lexpos + t1 = targ[-1] + sym.endlineno = getattr(t1,"endlineno",t1.lineno) + sym.endlexpos = getattr(t1,"endlexpos",t1.lexpos) + + # --! TRACKING + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # The code enclosed in this section is duplicated + # below as a performance optimization. Make sure + # changes get made in both locations. + + pslice.slice = targ + + try: + # Call the grammar rule with our special slice object + del symstack[-plen:] + del statestack[-plen:] + p.callable(pslice) + symstack.append(sym) + state = goto[statestack[-1]][pname] + statestack.append(state) + except SyntaxError: + # If an error was set. Enter error recovery state + lookaheadstack.append(lookahead) + symstack.pop() + statestack.pop() + state = statestack[-1] + sym.type = 'error' + lookahead = sym + errorcount = error_count + self.errorok = 0 + continue + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + else: + + # --! TRACKING + if tracking: + sym.lineno = lexer.lineno + sym.lexpos = lexer.lexpos + # --! TRACKING + + targ = [ sym ] + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # The code enclosed in this section is duplicated + # above as a performance optimization. Make sure + # changes get made in both locations. + + pslice.slice = targ + + try: + # Call the grammar rule with our special slice object + p.callable(pslice) + symstack.append(sym) + state = goto[statestack[-1]][pname] + statestack.append(state) + except SyntaxError: + # If an error was set. Enter error recovery state + lookaheadstack.append(lookahead) + symstack.pop() + statestack.pop() + state = statestack[-1] + sym.type = 'error' + lookahead = sym + errorcount = error_count + self.errorok = 0 + continue + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + if t == 0: + n = symstack[-1] + return getattr(n,"value",None) + + if t == None: + + # We have some kind of parsing error here. To handle + # this, we are going to push the current token onto + # the tokenstack and replace it with an 'error' token. + # If there are any synchronization rules, they may + # catch it. + # + # In addition to pushing the error token, we call call + # the user defined p_error() function if this is the + # first syntax error. This function is only called if + # errorcount == 0. + if errorcount == 0 or self.errorok: + errorcount = error_count + self.errorok = 0 + errtoken = lookahead + if errtoken.type == '$end': + errtoken = None # End of file! + if self.errorfunc: + global errok,token,restart + errok = self.errok # Set some special functions available in error recovery + token = get_token + restart = self.restart + if errtoken and not hasattr(errtoken,'lexer'): + errtoken.lexer = lexer + tok = self.errorfunc(errtoken) + del errok, token, restart # Delete special functions + + if self.errorok: + # User must have done some kind of panic + # mode recovery on their own. The + # returned token is the next lookahead + lookahead = tok + errtoken = None + continue + else: + if errtoken: + if hasattr(errtoken,"lineno"): lineno = lookahead.lineno + else: lineno = 0 + if lineno: + sys.stderr.write("yacc: Syntax error at line %d, token=%s\n" % (lineno, errtoken.type)) + else: + sys.stderr.write("yacc: Syntax error, token=%s" % errtoken.type) + else: + sys.stderr.write("yacc: Parse error in input. EOF\n") + return + + else: + errorcount = error_count + + # case 1: the statestack only has 1 entry on it. If we're in this state, the + # entire parse has been rolled back and we're completely hosed. The token is + # discarded and we just keep going. + + if len(statestack) <= 1 and lookahead.type != '$end': + lookahead = None + errtoken = None + state = 0 + # Nuke the pushback stack + del lookaheadstack[:] + continue + + # case 2: the statestack has a couple of entries on it, but we're + # at the end of the file. nuke the top entry and generate an error token + + # Start nuking entries on the stack + if lookahead.type == '$end': + # Whoa. We're really hosed here. Bail out + return + + if lookahead.type != 'error': + sym = symstack[-1] + if sym.type == 'error': + # Hmmm. Error is on top of stack, we'll just nuke input + # symbol and continue + lookahead = None + continue + t = YaccSymbol() + t.type = 'error' + if hasattr(lookahead,"lineno"): + t.lineno = lookahead.lineno + t.value = lookahead + lookaheadstack.append(lookahead) + lookahead = t + else: + symstack.pop() + statestack.pop() + state = statestack[-1] # Potential bug fix + + continue + + # Call an error function here + raise RuntimeError("yacc: internal parser error!!!\n") + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # parseopt_notrack(). + # + # Optimized version of parseopt() with line number tracking removed. + # DO NOT EDIT THIS CODE DIRECTLY. Copy the optimized version and remove + # code in the #--! TRACKING sections + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + def parseopt_notrack(self,input=None,lexer=None,debug=0,tracking=0,tokenfunc=None): + lookahead = None # Current lookahead symbol + lookaheadstack = [ ] # Stack of lookahead symbols + actions = self.action # Local reference to action table (to avoid lookup on self.) + goto = self.goto # Local reference to goto table (to avoid lookup on self.) + prod = self.productions # Local reference to production list (to avoid lookup on self.) + pslice = YaccProduction(None) # Production object passed to grammar rules + errorcount = 0 # Used during error recovery + + # If no lexer was given, we will try to use the lex module + if not lexer: + lex = load_ply_lex() + lexer = lex.lexer + + # Set up the lexer and parser objects on pslice + pslice.lexer = lexer + pslice.parser = self + + # If input was supplied, pass to lexer + if input is not None: + lexer.input(input) + + if tokenfunc is None: + # Tokenize function + get_token = lexer.token + else: + get_token = tokenfunc + + # Set up the state and symbol stacks + + statestack = [ ] # Stack of parsing states + self.statestack = statestack + symstack = [ ] # Stack of grammar symbols + self.symstack = symstack + + pslice.stack = symstack # Put in the production + errtoken = None # Err token + + # The start state is assumed to be (0,$end) + + statestack.append(0) + sym = YaccSymbol() + sym.type = '$end' + symstack.append(sym) + state = 0 + while 1: + # Get the next symbol on the input. If a lookahead symbol + # is already set, we just use that. Otherwise, we'll pull + # the next token off of the lookaheadstack or from the lexer + + if not lookahead: + if not lookaheadstack: + lookahead = get_token() # Get the next token + else: + lookahead = lookaheadstack.pop() + if not lookahead: + lookahead = YaccSymbol() + lookahead.type = '$end' + + # Check the action table + ltype = lookahead.type + t = actions[state].get(ltype) + + if t is not None: + if t > 0: + # shift a symbol on the stack + statestack.append(t) + state = t + + symstack.append(lookahead) + lookahead = None + + # Decrease error count on successful shift + if errorcount: errorcount -=1 + continue + + if t < 0: + # reduce a symbol on the stack, emit a production + p = prod[-t] + pname = p.name + plen = p.len + + # Get production function + sym = YaccSymbol() + sym.type = pname # Production name + sym.value = None + + if plen: + targ = symstack[-plen-1:] + targ[0] = sym + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # The code enclosed in this section is duplicated + # below as a performance optimization. Make sure + # changes get made in both locations. + + pslice.slice = targ + + try: + # Call the grammar rule with our special slice object + del symstack[-plen:] + del statestack[-plen:] + p.callable(pslice) + symstack.append(sym) + state = goto[statestack[-1]][pname] + statestack.append(state) + except SyntaxError: + # If an error was set. Enter error recovery state + lookaheadstack.append(lookahead) + symstack.pop() + statestack.pop() + state = statestack[-1] + sym.type = 'error' + lookahead = sym + errorcount = error_count + self.errorok = 0 + continue + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + else: + + targ = [ sym ] + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # The code enclosed in this section is duplicated + # above as a performance optimization. Make sure + # changes get made in both locations. + + pslice.slice = targ + + try: + # Call the grammar rule with our special slice object + p.callable(pslice) + symstack.append(sym) + state = goto[statestack[-1]][pname] + statestack.append(state) + except SyntaxError: + # If an error was set. Enter error recovery state + lookaheadstack.append(lookahead) + symstack.pop() + statestack.pop() + state = statestack[-1] + sym.type = 'error' + lookahead = sym + errorcount = error_count + self.errorok = 0 + continue + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + if t == 0: + n = symstack[-1] + return getattr(n,"value",None) + + if t == None: + + # We have some kind of parsing error here. To handle + # this, we are going to push the current token onto + # the tokenstack and replace it with an 'error' token. + # If there are any synchronization rules, they may + # catch it. + # + # In addition to pushing the error token, we call call + # the user defined p_error() function if this is the + # first syntax error. This function is only called if + # errorcount == 0. + if errorcount == 0 or self.errorok: + errorcount = error_count + self.errorok = 0 + errtoken = lookahead + if errtoken.type == '$end': + errtoken = None # End of file! + if self.errorfunc: + global errok,token,restart + errok = self.errok # Set some special functions available in error recovery + token = get_token + restart = self.restart + if errtoken and not hasattr(errtoken,'lexer'): + errtoken.lexer = lexer + tok = self.errorfunc(errtoken) + del errok, token, restart # Delete special functions + + if self.errorok: + # User must have done some kind of panic + # mode recovery on their own. The + # returned token is the next lookahead + lookahead = tok + errtoken = None + continue + else: + if errtoken: + if hasattr(errtoken,"lineno"): lineno = lookahead.lineno + else: lineno = 0 + if lineno: + sys.stderr.write("yacc: Syntax error at line %d, token=%s\n" % (lineno, errtoken.type)) + else: + sys.stderr.write("yacc: Syntax error, token=%s" % errtoken.type) + else: + sys.stderr.write("yacc: Parse error in input. EOF\n") + return + + else: + errorcount = error_count + + # case 1: the statestack only has 1 entry on it. If we're in this state, the + # entire parse has been rolled back and we're completely hosed. The token is + # discarded and we just keep going. + + if len(statestack) <= 1 and lookahead.type != '$end': + lookahead = None + errtoken = None + state = 0 + # Nuke the pushback stack + del lookaheadstack[:] + continue + + # case 2: the statestack has a couple of entries on it, but we're + # at the end of the file. nuke the top entry and generate an error token + + # Start nuking entries on the stack + if lookahead.type == '$end': + # Whoa. We're really hosed here. Bail out + return + + if lookahead.type != 'error': + sym = symstack[-1] + if sym.type == 'error': + # Hmmm. Error is on top of stack, we'll just nuke input + # symbol and continue + lookahead = None + continue + t = YaccSymbol() + t.type = 'error' + if hasattr(lookahead,"lineno"): + t.lineno = lookahead.lineno + t.value = lookahead + lookaheadstack.append(lookahead) + lookahead = t + else: + symstack.pop() + statestack.pop() + state = statestack[-1] # Potential bug fix + + continue + + # Call an error function here + raise RuntimeError("yacc: internal parser error!!!\n") + +# ----------------------------------------------------------------------------- +# === Grammar Representation === +# +# The following functions, classes, and variables are used to represent and +# manipulate the rules that make up a grammar. +# ----------------------------------------------------------------------------- + +import re + +# regex matching identifiers +_is_identifier = re.compile(r'^[a-zA-Z0-9_-]+$') + +# ----------------------------------------------------------------------------- +# class Production: +# +# This class stores the raw information about a single production or grammar rule. +# A grammar rule refers to a specification such as this: +# +# expr : expr PLUS term +# +# Here are the basic attributes defined on all productions +# +# name - Name of the production. For example 'expr' +# prod - A list of symbols on the right side ['expr','PLUS','term'] +# prec - Production precedence level +# number - Production number. +# func - Function that executes on reduce +# file - File where production function is defined +# lineno - Line number where production function is defined +# +# The following attributes are defined or optional. +# +# len - Length of the production (number of symbols on right hand side) +# usyms - Set of unique symbols found in the production +# ----------------------------------------------------------------------------- + +class Production(object): + reduced = 0 + def __init__(self,number,name,prod,precedence=('right',0),func=None,file='',line=0): + self.name = name + self.prod = tuple(prod) + self.number = number + self.func = func + self.callable = None + self.file = file + self.line = line + self.prec = precedence + + # Internal settings used during table construction + + self.len = len(self.prod) # Length of the production + + # Create a list of unique production symbols used in the production + self.usyms = [ ] + for s in self.prod: + if s not in self.usyms: + self.usyms.append(s) + + # List of all LR items for the production + self.lr_items = [] + self.lr_next = None + + # Create a string representation + if self.prod: + self.str = "%s -> %s" % (self.name," ".join(self.prod)) + else: + self.str = "%s -> <empty>" % self.name + + def __str__(self): + return self.str + + def __repr__(self): + return "Production("+str(self)+")" + + def __len__(self): + return len(self.prod) + + def __nonzero__(self): + return 1 + + def __getitem__(self,index): + return self.prod[index] + + # Return the nth lr_item from the production (or None if at the end) + def lr_item(self,n): + if n > len(self.prod): return None + p = LRItem(self,n) + + # Precompute the list of productions immediately following. Hack. Remove later + try: + p.lr_after = Prodnames[p.prod[n+1]] + except (IndexError,KeyError): + p.lr_after = [] + try: + p.lr_before = p.prod[n-1] + except IndexError: + p.lr_before = None + + return p + + # Bind the production function name to a callable + def bind(self,pdict): + if self.func: + self.callable = pdict[self.func] + +# This class serves as a minimal standin for Production objects when +# reading table data from files. It only contains information +# actually used by the LR parsing engine, plus some additional +# debugging information. +class MiniProduction(object): + def __init__(self,str,name,len,func,file,line): + self.name = name + self.len = len + self.func = func + self.callable = None + self.file = file + self.line = line + self.str = str + def __str__(self): + return self.str + def __repr__(self): + return "MiniProduction(%s)" % self.str + + # Bind the production function name to a callable + def bind(self,pdict): + if self.func: + self.callable = pdict[self.func] + + +# ----------------------------------------------------------------------------- +# class LRItem +# +# This class represents a specific stage of parsing a production rule. For +# example: +# +# expr : expr . PLUS term +# +# In the above, the "." represents the current location of the parse. Here +# basic attributes: +# +# name - Name of the production. For example 'expr' +# prod - A list of symbols on the right side ['expr','.', 'PLUS','term'] +# number - Production number. +# +# lr_next Next LR item. Example, if we are ' expr -> expr . PLUS term' +# then lr_next refers to 'expr -> expr PLUS . term' +# lr_index - LR item index (location of the ".") in the prod list. +# lookaheads - LALR lookahead symbols for this item +# len - Length of the production (number of symbols on right hand side) +# lr_after - List of all productions that immediately follow +# lr_before - Grammar symbol immediately before +# ----------------------------------------------------------------------------- + +class LRItem(object): + def __init__(self,p,n): + self.name = p.name + self.prod = list(p.prod) + self.number = p.number + self.lr_index = n + self.lookaheads = { } + self.prod.insert(n,".") + self.prod = tuple(self.prod) + self.len = len(self.prod) + self.usyms = p.usyms + + def __str__(self): + if self.prod: + s = "%s -> %s" % (self.name," ".join(self.prod)) + else: + s = "%s -> <empty>" % self.name + return s + + def __repr__(self): + return "LRItem("+str(self)+")" + +# ----------------------------------------------------------------------------- +# rightmost_terminal() +# +# Return the rightmost terminal from a list of symbols. Used in add_production() +# ----------------------------------------------------------------------------- +def rightmost_terminal(symbols, terminals): + i = len(symbols) - 1 + while i >= 0: + if symbols[i] in terminals: + return symbols[i] + i -= 1 + return None + +# ----------------------------------------------------------------------------- +# === GRAMMAR CLASS === +# +# The following class represents the contents of the specified grammar along +# with various computed properties such as first sets, follow sets, LR items, etc. +# This data is used for critical parts of the table generation process later. +# ----------------------------------------------------------------------------- + +class GrammarError(YaccError): pass + +class Grammar(object): + def __init__(self,terminals): + self.Productions = [None] # A list of all of the productions. The first + # entry is always reserved for the purpose of + # building an augmented grammar + + self.Prodnames = { } # A dictionary mapping the names of nonterminals to a list of all + # productions of that nonterminal. + + self.Prodmap = { } # A dictionary that is only used to detect duplicate + # productions. + + self.Terminals = { } # A dictionary mapping the names of terminal symbols to a + # list of the rules where they are used. + + for term in terminals: + self.Terminals[term] = [] + + self.Terminals['error'] = [] + + self.Nonterminals = { } # A dictionary mapping names of nonterminals to a list + # of rule numbers where they are used. + + self.First = { } # A dictionary of precomputed FIRST(x) symbols + + self.Follow = { } # A dictionary of precomputed FOLLOW(x) symbols + + self.Precedence = { } # Precedence rules for each terminal. Contains tuples of the + # form ('right',level) or ('nonassoc', level) or ('left',level) + + self.UsedPrecedence = { } # Precedence rules that were actually used by the grammer. + # This is only used to provide error checking and to generate + # a warning about unused precedence rules. + + self.Start = None # Starting symbol for the grammar + + + def __len__(self): + return len(self.Productions) + + def __getitem__(self,index): + return self.Productions[index] + + # ----------------------------------------------------------------------------- + # set_precedence() + # + # Sets the precedence for a given terminal. assoc is the associativity such as + # 'left','right', or 'nonassoc'. level is a numeric level. + # + # ----------------------------------------------------------------------------- + + def set_precedence(self,term,assoc,level): + assert self.Productions == [None],"Must call set_precedence() before add_production()" + if term in self.Precedence: + raise GrammarError("Precedence already specified for terminal '%s'" % term) + if assoc not in ['left','right','nonassoc']: + raise GrammarError("Associativity must be one of 'left','right', or 'nonassoc'") + self.Precedence[term] = (assoc,level) + + # ----------------------------------------------------------------------------- + # add_production() + # + # Given an action function, this function assembles a production rule and + # computes its precedence level. + # + # The production rule is supplied as a list of symbols. For example, + # a rule such as 'expr : expr PLUS term' has a production name of 'expr' and + # symbols ['expr','PLUS','term']. + # + # Precedence is determined by the precedence of the right-most non-terminal + # or the precedence of a terminal specified by %prec. + # + # A variety of error checks are performed to make sure production symbols + # are valid and that %prec is used correctly. + # ----------------------------------------------------------------------------- + + def add_production(self,prodname,syms,func=None,file='',line=0): + + if prodname in self.Terminals: + raise GrammarError("%s:%d: Illegal rule name '%s'. Already defined as a token" % (file,line,prodname)) + if prodname == 'error': + raise GrammarError("%s:%d: Illegal rule name '%s'. error is a reserved word" % (file,line,prodname)) + if not _is_identifier.match(prodname): + raise GrammarError("%s:%d: Illegal rule name '%s'" % (file,line,prodname)) + + # Look for literal tokens + for n,s in enumerate(syms): + if s[0] in "'\"": + try: + c = eval(s) + if (len(c) > 1): + raise GrammarError("%s:%d: Literal token %s in rule '%s' may only be a single character" % (file,line,s, prodname)) + if not c in self.Terminals: + self.Terminals[c] = [] + syms[n] = c + continue + except SyntaxError: + pass + if not _is_identifier.match(s) and s != '%prec': + raise GrammarError("%s:%d: Illegal name '%s' in rule '%s'" % (file,line,s, prodname)) + + # Determine the precedence level + if '%prec' in syms: + if syms[-1] == '%prec': + raise GrammarError("%s:%d: Syntax error. Nothing follows %%prec" % (file,line)) + if syms[-2] != '%prec': + raise GrammarError("%s:%d: Syntax error. %%prec can only appear at the end of a grammar rule" % (file,line)) + precname = syms[-1] + prodprec = self.Precedence.get(precname,None) + if not prodprec: + raise GrammarError("%s:%d: Nothing known about the precedence of '%s'" % (file,line,precname)) + else: + self.UsedPrecedence[precname] = 1 + del syms[-2:] # Drop %prec from the rule + else: + # If no %prec, precedence is determined by the rightmost terminal symbol + precname = rightmost_terminal(syms,self.Terminals) + prodprec = self.Precedence.get(precname,('right',0)) + + # See if the rule is already in the rulemap + map = "%s -> %s" % (prodname,syms) + if map in self.Prodmap: + m = self.Prodmap[map] + raise GrammarError("%s:%d: Duplicate rule %s. " % (file,line, m) + + "Previous definition at %s:%d" % (m.file, m.line)) + + # From this point on, everything is valid. Create a new Production instance + pnumber = len(self.Productions) + if not prodname in self.Nonterminals: + self.Nonterminals[prodname] = [ ] + + # Add the production number to Terminals and Nonterminals + for t in syms: + if t in self.Terminals: + self.Terminals[t].append(pnumber) + else: + if not t in self.Nonterminals: + self.Nonterminals[t] = [ ] + self.Nonterminals[t].append(pnumber) + + # Create a production and add it to the list of productions + p = Production(pnumber,prodname,syms,prodprec,func,file,line) + self.Productions.append(p) + self.Prodmap[map] = p + + # Add to the global productions list + try: + self.Prodnames[prodname].append(p) + except KeyError: + self.Prodnames[prodname] = [ p ] + return 0 + + # ----------------------------------------------------------------------------- + # set_start() + # + # Sets the starting symbol and creates the augmented grammar. Production + # rule 0 is S' -> start where start is the start symbol. + # ----------------------------------------------------------------------------- + + def set_start(self,start=None): + if not start: + start = self.Productions[1].name + if start not in self.Nonterminals: + raise GrammarError("start symbol %s undefined" % start) + self.Productions[0] = Production(0,"S'",[start]) + self.Nonterminals[start].append(0) + self.Start = start + + # ----------------------------------------------------------------------------- + # find_unreachable() + # + # Find all of the nonterminal symbols that can't be reached from the starting + # symbol. Returns a list of nonterminals that can't be reached. + # ----------------------------------------------------------------------------- + + def find_unreachable(self): + + # Mark all symbols that are reachable from a symbol s + def mark_reachable_from(s): + if reachable[s]: + # We've already reached symbol s. + return + reachable[s] = 1 + for p in self.Prodnames.get(s,[]): + for r in p.prod: + mark_reachable_from(r) + + reachable = { } + for s in list(self.Terminals) + list(self.Nonterminals): + reachable[s] = 0 + + mark_reachable_from( self.Productions[0].prod[0] ) + + return [s for s in list(self.Nonterminals) + if not reachable[s]] + + # ----------------------------------------------------------------------------- + # infinite_cycles() + # + # This function looks at the various parsing rules and tries to detect + # infinite recursion cycles (grammar rules where there is no possible way + # to derive a string of only terminals). + # ----------------------------------------------------------------------------- + + def infinite_cycles(self): + terminates = {} + + # Terminals: + for t in self.Terminals: + terminates[t] = 1 + + terminates['$end'] = 1 + + # Nonterminals: + + # Initialize to false: + for n in self.Nonterminals: + terminates[n] = 0 + + # Then propagate termination until no change: + while 1: + some_change = 0 + for (n,pl) in self.Prodnames.items(): + # Nonterminal n terminates iff any of its productions terminates. + for p in pl: + # Production p terminates iff all of its rhs symbols terminate. + for s in p.prod: + if not terminates[s]: + # The symbol s does not terminate, + # so production p does not terminate. + p_terminates = 0 + break + else: + # didn't break from the loop, + # so every symbol s terminates + # so production p terminates. + p_terminates = 1 + + if p_terminates: + # symbol n terminates! + if not terminates[n]: + terminates[n] = 1 + some_change = 1 + # Don't need to consider any more productions for this n. + break + + if not some_change: + break + + infinite = [] + for (s,term) in terminates.items(): + if not term: + if not s in self.Prodnames and not s in self.Terminals and s != 'error': + # s is used-but-not-defined, and we've already warned of that, + # so it would be overkill to say that it's also non-terminating. + pass + else: + infinite.append(s) + + return infinite + + + # ----------------------------------------------------------------------------- + # undefined_symbols() + # + # Find all symbols that were used the grammar, but not defined as tokens or + # grammar rules. Returns a list of tuples (sym, prod) where sym in the symbol + # and prod is the production where the symbol was used. + # ----------------------------------------------------------------------------- + def undefined_symbols(self): + result = [] + for p in self.Productions: + if not p: continue + + for s in p.prod: + if not s in self.Prodnames and not s in self.Terminals and s != 'error': + result.append((s,p)) + return result + + # ----------------------------------------------------------------------------- + # unused_terminals() + # + # Find all terminals that were defined, but not used by the grammar. Returns + # a list of all symbols. + # ----------------------------------------------------------------------------- + def unused_terminals(self): + unused_tok = [] + for s,v in self.Terminals.items(): + if s != 'error' and not v: + unused_tok.append(s) + + return unused_tok + + # ------------------------------------------------------------------------------ + # unused_rules() + # + # Find all grammar rules that were defined, but not used (maybe not reachable) + # Returns a list of productions. + # ------------------------------------------------------------------------------ + + def unused_rules(self): + unused_prod = [] + for s,v in self.Nonterminals.items(): + if not v: + p = self.Prodnames[s][0] + unused_prod.append(p) + return unused_prod + + # ----------------------------------------------------------------------------- + # unused_precedence() + # + # Returns a list of tuples (term,precedence) corresponding to precedence + # rules that were never used by the grammar. term is the name of the terminal + # on which precedence was applied and precedence is a string such as 'left' or + # 'right' corresponding to the type of precedence. + # ----------------------------------------------------------------------------- + + def unused_precedence(self): + unused = [] + for termname in self.Precedence: + if not (termname in self.Terminals or termname in self.UsedPrecedence): + unused.append((termname,self.Precedence[termname][0])) + + return unused + + # ------------------------------------------------------------------------- + # _first() + # + # Compute the value of FIRST1(beta) where beta is a tuple of symbols. + # + # During execution of compute_first1, the result may be incomplete. + # Afterward (e.g., when called from compute_follow()), it will be complete. + # ------------------------------------------------------------------------- + def _first(self,beta): + + # We are computing First(x1,x2,x3,...,xn) + result = [ ] + for x in beta: + x_produces_empty = 0 + + # Add all the non-<empty> symbols of First[x] to the result. + for f in self.First[x]: + if f == '<empty>': + x_produces_empty = 1 + else: + if f not in result: result.append(f) + + if x_produces_empty: + # We have to consider the next x in beta, + # i.e. stay in the loop. + pass + else: + # We don't have to consider any further symbols in beta. + break + else: + # There was no 'break' from the loop, + # so x_produces_empty was true for all x in beta, + # so beta produces empty as well. + result.append('<empty>') + + return result + + # ------------------------------------------------------------------------- + # compute_first() + # + # Compute the value of FIRST1(X) for all symbols + # ------------------------------------------------------------------------- + def compute_first(self): + if self.First: + return self.First + + # Terminals: + for t in self.Terminals: + self.First[t] = [t] + + self.First['$end'] = ['$end'] + + # Nonterminals: + + # Initialize to the empty set: + for n in self.Nonterminals: + self.First[n] = [] + + # Then propagate symbols until no change: + while 1: + some_change = 0 + for n in self.Nonterminals: + for p in self.Prodnames[n]: + for f in self._first(p.prod): + if f not in self.First[n]: + self.First[n].append( f ) + some_change = 1 + if not some_change: + break + + return self.First + + # --------------------------------------------------------------------- + # compute_follow() + # + # Computes all of the follow sets for every non-terminal symbol. The + # follow set is the set of all symbols that might follow a given + # non-terminal. See the Dragon book, 2nd Ed. p. 189. + # --------------------------------------------------------------------- + def compute_follow(self,start=None): + # If already computed, return the result + if self.Follow: + return self.Follow + + # If first sets not computed yet, do that first. + if not self.First: + self.compute_first() + + # Add '$end' to the follow list of the start symbol + for k in self.Nonterminals: + self.Follow[k] = [ ] + + if not start: + start = self.Productions[1].name + + self.Follow[start] = [ '$end' ] + + while 1: + didadd = 0 + for p in self.Productions[1:]: + # Here is the production set + for i in range(len(p.prod)): + B = p.prod[i] + if B in self.Nonterminals: + # Okay. We got a non-terminal in a production + fst = self._first(p.prod[i+1:]) + hasempty = 0 + for f in fst: + if f != '<empty>' and f not in self.Follow[B]: + self.Follow[B].append(f) + didadd = 1 + if f == '<empty>': + hasempty = 1 + if hasempty or i == (len(p.prod)-1): + # Add elements of follow(a) to follow(b) + for f in self.Follow[p.name]: + if f not in self.Follow[B]: + self.Follow[B].append(f) + didadd = 1 + if not didadd: break + return self.Follow + + + # ----------------------------------------------------------------------------- + # build_lritems() + # + # This function walks the list of productions and builds a complete set of the + # LR items. The LR items are stored in two ways: First, they are uniquely + # numbered and placed in the list _lritems. Second, a linked list of LR items + # is built for each production. For example: + # + # E -> E PLUS E + # + # Creates the list + # + # [E -> . E PLUS E, E -> E . PLUS E, E -> E PLUS . E, E -> E PLUS E . ] + # ----------------------------------------------------------------------------- + + def build_lritems(self): + for p in self.Productions: + lastlri = p + i = 0 + lr_items = [] + while 1: + if i > len(p): + lri = None + else: + lri = LRItem(p,i) + # Precompute the list of productions immediately following + try: + lri.lr_after = self.Prodnames[lri.prod[i+1]] + except (IndexError,KeyError): + lri.lr_after = [] + try: + lri.lr_before = lri.prod[i-1] + except IndexError: + lri.lr_before = None + + lastlri.lr_next = lri + if not lri: break + lr_items.append(lri) + lastlri = lri + i += 1 + p.lr_items = lr_items + +# ----------------------------------------------------------------------------- +# == Class LRTable == +# +# This basic class represents a basic table of LR parsing information. +# Methods for generating the tables are not defined here. They are defined +# in the derived class LRGeneratedTable. +# ----------------------------------------------------------------------------- + +class VersionError(YaccError): pass + +class LRTable(object): + def __init__(self): + self.lr_action = None + self.lr_goto = None + self.lr_productions = None + self.lr_method = None + + def read_table(self,module): + if isinstance(module,types.ModuleType): + parsetab = module + else: + if sys.version_info[0] < 3: + exec("import %s as parsetab" % module) + else: + env = { } + exec("import %s as parsetab" % module, env, env) + parsetab = env['parsetab'] + + if parsetab._tabversion != __tabversion__: + raise VersionError("yacc table file version is out of date") + + self.lr_action = parsetab._lr_action + self.lr_goto = parsetab._lr_goto + + self.lr_productions = [] + for p in parsetab._lr_productions: + self.lr_productions.append(MiniProduction(*p)) + + self.lr_method = parsetab._lr_method + return parsetab._lr_signature + + def read_pickle(self,filename): + try: + import cPickle as pickle + except ImportError: + import pickle + + in_f = open(filename,"rb") + + tabversion = pickle.load(in_f) + if tabversion != __tabversion__: + raise VersionError("yacc table file version is out of date") + self.lr_method = pickle.load(in_f) + signature = pickle.load(in_f) + self.lr_action = pickle.load(in_f) + self.lr_goto = pickle.load(in_f) + productions = pickle.load(in_f) + + self.lr_productions = [] + for p in productions: + self.lr_productions.append(MiniProduction(*p)) + + in_f.close() + return signature + + # Bind all production function names to callable objects in pdict + def bind_callables(self,pdict): + for p in self.lr_productions: + p.bind(pdict) + +# ----------------------------------------------------------------------------- +# === LR Generator === +# +# The following classes and functions are used to generate LR parsing tables on +# a grammar. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# digraph() +# traverse() +# +# The following two functions are used to compute set valued functions +# of the form: +# +# F(x) = F'(x) U U{F(y) | x R y} +# +# This is used to compute the values of Read() sets as well as FOLLOW sets +# in LALR(1) generation. +# +# Inputs: X - An input set +# R - A relation +# FP - Set-valued function +# ------------------------------------------------------------------------------ + +def digraph(X,R,FP): + N = { } + for x in X: + N[x] = 0 + stack = [] + F = { } + for x in X: + if N[x] == 0: traverse(x,N,stack,F,X,R,FP) + return F + +def traverse(x,N,stack,F,X,R,FP): + stack.append(x) + d = len(stack) + N[x] = d + F[x] = FP(x) # F(X) <- F'(x) + + rel = R(x) # Get y's related to x + for y in rel: + if N[y] == 0: + traverse(y,N,stack,F,X,R,FP) + N[x] = min(N[x],N[y]) + for a in F.get(y,[]): + if a not in F[x]: F[x].append(a) + if N[x] == d: + N[stack[-1]] = MAXINT + F[stack[-1]] = F[x] + element = stack.pop() + while element != x: + N[stack[-1]] = MAXINT + F[stack[-1]] = F[x] + element = stack.pop() + +class LALRError(YaccError): pass + +# ----------------------------------------------------------------------------- +# == LRGeneratedTable == +# +# This class implements the LR table generation algorithm. There are no +# public methods except for write() +# ----------------------------------------------------------------------------- + +class LRGeneratedTable(LRTable): + def __init__(self,grammar,method='LALR',log=None): + if method not in ['SLR','LALR']: + raise LALRError("Unsupported method %s" % method) + + self.grammar = grammar + self.lr_method = method + + # Set up the logger + if not log: + log = NullLogger() + self.log = log + + # Internal attributes + self.lr_action = {} # Action table + self.lr_goto = {} # Goto table + self.lr_productions = grammar.Productions # Copy of grammar Production array + self.lr_goto_cache = {} # Cache of computed gotos + self.lr0_cidhash = {} # Cache of closures + + self._add_count = 0 # Internal counter used to detect cycles + + # Diagonistic information filled in by the table generator + self.sr_conflict = 0 + self.rr_conflict = 0 + self.conflicts = [] # List of conflicts + + self.sr_conflicts = [] + self.rr_conflicts = [] + + # Build the tables + self.grammar.build_lritems() + self.grammar.compute_first() + self.grammar.compute_follow() + self.lr_parse_table() + + # Compute the LR(0) closure operation on I, where I is a set of LR(0) items. + + def lr0_closure(self,I): + self._add_count += 1 + + # Add everything in I to J + J = I[:] + didadd = 1 + while didadd: + didadd = 0 + for j in J: + for x in j.lr_after: + if getattr(x,"lr0_added",0) == self._add_count: continue + # Add B --> .G to J + J.append(x.lr_next) + x.lr0_added = self._add_count + didadd = 1 + + return J + + # Compute the LR(0) goto function goto(I,X) where I is a set + # of LR(0) items and X is a grammar symbol. This function is written + # in a way that guarantees uniqueness of the generated goto sets + # (i.e. the same goto set will never be returned as two different Python + # objects). With uniqueness, we can later do fast set comparisons using + # id(obj) instead of element-wise comparison. + + def lr0_goto(self,I,x): + # First we look for a previously cached entry + g = self.lr_goto_cache.get((id(I),x),None) + if g: return g + + # Now we generate the goto set in a way that guarantees uniqueness + # of the result + + s = self.lr_goto_cache.get(x,None) + if not s: + s = { } + self.lr_goto_cache[x] = s + + gs = [ ] + for p in I: + n = p.lr_next + if n and n.lr_before == x: + s1 = s.get(id(n),None) + if not s1: + s1 = { } + s[id(n)] = s1 + gs.append(n) + s = s1 + g = s.get('$end',None) + if not g: + if gs: + g = self.lr0_closure(gs) + s['$end'] = g + else: + s['$end'] = gs + self.lr_goto_cache[(id(I),x)] = g + return g + + # Compute the LR(0) sets of item function + def lr0_items(self): + + C = [ self.lr0_closure([self.grammar.Productions[0].lr_next]) ] + i = 0 + for I in C: + self.lr0_cidhash[id(I)] = i + i += 1 + + # Loop over the items in C and each grammar symbols + i = 0 + while i < len(C): + I = C[i] + i += 1 + + # Collect all of the symbols that could possibly be in the goto(I,X) sets + asyms = { } + for ii in I: + for s in ii.usyms: + asyms[s] = None + + for x in asyms: + g = self.lr0_goto(I,x) + if not g: continue + if id(g) in self.lr0_cidhash: continue + self.lr0_cidhash[id(g)] = len(C) + C.append(g) + + return C + + # ----------------------------------------------------------------------------- + # ==== LALR(1) Parsing ==== + # + # LALR(1) parsing is almost exactly the same as SLR except that instead of + # relying upon Follow() sets when performing reductions, a more selective + # lookahead set that incorporates the state of the LR(0) machine is utilized. + # Thus, we mainly just have to focus on calculating the lookahead sets. + # + # The method used here is due to DeRemer and Pennelo (1982). + # + # DeRemer, F. L., and T. J. Pennelo: "Efficient Computation of LALR(1) + # Lookahead Sets", ACM Transactions on Programming Languages and Systems, + # Vol. 4, No. 4, Oct. 1982, pp. 615-649 + # + # Further details can also be found in: + # + # J. Tremblay and P. Sorenson, "The Theory and Practice of Compiler Writing", + # McGraw-Hill Book Company, (1985). + # + # ----------------------------------------------------------------------------- + + # ----------------------------------------------------------------------------- + # compute_nullable_nonterminals() + # + # Creates a dictionary containing all of the non-terminals that might produce + # an empty production. + # ----------------------------------------------------------------------------- + + def compute_nullable_nonterminals(self): + nullable = {} + num_nullable = 0 + while 1: + for p in self.grammar.Productions[1:]: + if p.len == 0: + nullable[p.name] = 1 + continue + for t in p.prod: + if not t in nullable: break + else: + nullable[p.name] = 1 + if len(nullable) == num_nullable: break + num_nullable = len(nullable) + return nullable + + # ----------------------------------------------------------------------------- + # find_nonterminal_trans(C) + # + # Given a set of LR(0) items, this functions finds all of the non-terminal + # transitions. These are transitions in which a dot appears immediately before + # a non-terminal. Returns a list of tuples of the form (state,N) where state + # is the state number and N is the nonterminal symbol. + # + # The input C is the set of LR(0) items. + # ----------------------------------------------------------------------------- + + def find_nonterminal_transitions(self,C): + trans = [] + for state in range(len(C)): + for p in C[state]: + if p.lr_index < p.len - 1: + t = (state,p.prod[p.lr_index+1]) + if t[1] in self.grammar.Nonterminals: + if t not in trans: trans.append(t) + state = state + 1 + return trans + + # ----------------------------------------------------------------------------- + # dr_relation() + # + # Computes the DR(p,A) relationships for non-terminal transitions. The input + # is a tuple (state,N) where state is a number and N is a nonterminal symbol. + # + # Returns a list of terminals. + # ----------------------------------------------------------------------------- + + def dr_relation(self,C,trans,nullable): + dr_set = { } + state,N = trans + terms = [] + + g = self.lr0_goto(C[state],N) + for p in g: + if p.lr_index < p.len - 1: + a = p.prod[p.lr_index+1] + if a in self.grammar.Terminals: + if a not in terms: terms.append(a) + + # This extra bit is to handle the start state + if state == 0 and N == self.grammar.Productions[0].prod[0]: + terms.append('$end') + + return terms + + # ----------------------------------------------------------------------------- + # reads_relation() + # + # Computes the READS() relation (p,A) READS (t,C). + # ----------------------------------------------------------------------------- + + def reads_relation(self,C, trans, empty): + # Look for empty transitions + rel = [] + state, N = trans + + g = self.lr0_goto(C[state],N) + j = self.lr0_cidhash.get(id(g),-1) + for p in g: + if p.lr_index < p.len - 1: + a = p.prod[p.lr_index + 1] + if a in empty: + rel.append((j,a)) + + return rel + + # ----------------------------------------------------------------------------- + # compute_lookback_includes() + # + # Determines the lookback and includes relations + # + # LOOKBACK: + # + # This relation is determined by running the LR(0) state machine forward. + # For example, starting with a production "N : . A B C", we run it forward + # to obtain "N : A B C ." We then build a relationship between this final + # state and the starting state. These relationships are stored in a dictionary + # lookdict. + # + # INCLUDES: + # + # Computes the INCLUDE() relation (p,A) INCLUDES (p',B). + # + # This relation is used to determine non-terminal transitions that occur + # inside of other non-terminal transition states. (p,A) INCLUDES (p', B) + # if the following holds: + # + # B -> LAT, where T -> epsilon and p' -L-> p + # + # L is essentially a prefix (which may be empty), T is a suffix that must be + # able to derive an empty string. State p' must lead to state p with the string L. + # + # ----------------------------------------------------------------------------- + + def compute_lookback_includes(self,C,trans,nullable): + + lookdict = {} # Dictionary of lookback relations + includedict = {} # Dictionary of include relations + + # Make a dictionary of non-terminal transitions + dtrans = {} + for t in trans: + dtrans[t] = 1 + + # Loop over all transitions and compute lookbacks and includes + for state,N in trans: + lookb = [] + includes = [] + for p in C[state]: + if p.name != N: continue + + # Okay, we have a name match. We now follow the production all the way + # through the state machine until we get the . on the right hand side + + lr_index = p.lr_index + j = state + while lr_index < p.len - 1: + lr_index = lr_index + 1 + t = p.prod[lr_index] + + # Check to see if this symbol and state are a non-terminal transition + if (j,t) in dtrans: + # Yes. Okay, there is some chance that this is an includes relation + # the only way to know for certain is whether the rest of the + # production derives empty + + li = lr_index + 1 + while li < p.len: + if p.prod[li] in self.grammar.Terminals: break # No forget it + if not p.prod[li] in nullable: break + li = li + 1 + else: + # Appears to be a relation between (j,t) and (state,N) + includes.append((j,t)) + + g = self.lr0_goto(C[j],t) # Go to next set + j = self.lr0_cidhash.get(id(g),-1) # Go to next state + + # When we get here, j is the final state, now we have to locate the production + for r in C[j]: + if r.name != p.name: continue + if r.len != p.len: continue + i = 0 + # This look is comparing a production ". A B C" with "A B C ." + while i < r.lr_index: + if r.prod[i] != p.prod[i+1]: break + i = i + 1 + else: + lookb.append((j,r)) + for i in includes: + if not i in includedict: includedict[i] = [] + includedict[i].append((state,N)) + lookdict[(state,N)] = lookb + + return lookdict,includedict + + # ----------------------------------------------------------------------------- + # compute_read_sets() + # + # Given a set of LR(0) items, this function computes the read sets. + # + # Inputs: C = Set of LR(0) items + # ntrans = Set of nonterminal transitions + # nullable = Set of empty transitions + # + # Returns a set containing the read sets + # ----------------------------------------------------------------------------- + + def compute_read_sets(self,C, ntrans, nullable): + FP = lambda x: self.dr_relation(C,x,nullable) + R = lambda x: self.reads_relation(C,x,nullable) + F = digraph(ntrans,R,FP) + return F + + # ----------------------------------------------------------------------------- + # compute_follow_sets() + # + # Given a set of LR(0) items, a set of non-terminal transitions, a readset, + # and an include set, this function computes the follow sets + # + # Follow(p,A) = Read(p,A) U U {Follow(p',B) | (p,A) INCLUDES (p',B)} + # + # Inputs: + # ntrans = Set of nonterminal transitions + # readsets = Readset (previously computed) + # inclsets = Include sets (previously computed) + # + # Returns a set containing the follow sets + # ----------------------------------------------------------------------------- + + def compute_follow_sets(self,ntrans,readsets,inclsets): + FP = lambda x: readsets[x] + R = lambda x: inclsets.get(x,[]) + F = digraph(ntrans,R,FP) + return F + + # ----------------------------------------------------------------------------- + # add_lookaheads() + # + # Attaches the lookahead symbols to grammar rules. + # + # Inputs: lookbacks - Set of lookback relations + # followset - Computed follow set + # + # This function directly attaches the lookaheads to productions contained + # in the lookbacks set + # ----------------------------------------------------------------------------- + + def add_lookaheads(self,lookbacks,followset): + for trans,lb in lookbacks.items(): + # Loop over productions in lookback + for state,p in lb: + if not state in p.lookaheads: + p.lookaheads[state] = [] + f = followset.get(trans,[]) + for a in f: + if a not in p.lookaheads[state]: p.lookaheads[state].append(a) + + # ----------------------------------------------------------------------------- + # add_lalr_lookaheads() + # + # This function does all of the work of adding lookahead information for use + # with LALR parsing + # ----------------------------------------------------------------------------- + + def add_lalr_lookaheads(self,C): + # Determine all of the nullable nonterminals + nullable = self.compute_nullable_nonterminals() + + # Find all non-terminal transitions + trans = self.find_nonterminal_transitions(C) + + # Compute read sets + readsets = self.compute_read_sets(C,trans,nullable) + + # Compute lookback/includes relations + lookd, included = self.compute_lookback_includes(C,trans,nullable) + + # Compute LALR FOLLOW sets + followsets = self.compute_follow_sets(trans,readsets,included) + + # Add all of the lookaheads + self.add_lookaheads(lookd,followsets) + + # ----------------------------------------------------------------------------- + # lr_parse_table() + # + # This function constructs the parse tables for SLR or LALR + # ----------------------------------------------------------------------------- + def lr_parse_table(self): + Productions = self.grammar.Productions + Precedence = self.grammar.Precedence + goto = self.lr_goto # Goto array + action = self.lr_action # Action array + log = self.log # Logger for output + + actionp = { } # Action production array (temporary) + + log.info("Parsing method: %s", self.lr_method) + + # Step 1: Construct C = { I0, I1, ... IN}, collection of LR(0) items + # This determines the number of states + + C = self.lr0_items() + + if self.lr_method == 'LALR': + self.add_lalr_lookaheads(C) + + # Build the parser table, state by state + st = 0 + for I in C: + # Loop over each production in I + actlist = [ ] # List of actions + st_action = { } + st_actionp = { } + st_goto = { } + log.info("") + log.info("state %d", st) + log.info("") + for p in I: + log.info(" (%d) %s", p.number, str(p)) + log.info("") + + for p in I: + if p.len == p.lr_index + 1: + if p.name == "S'": + # Start symbol. Accept! + st_action["$end"] = 0 + st_actionp["$end"] = p + else: + # We are at the end of a production. Reduce! + if self.lr_method == 'LALR': + laheads = p.lookaheads[st] + else: + laheads = self.grammar.Follow[p.name] + for a in laheads: + actlist.append((a,p,"reduce using rule %d (%s)" % (p.number,p))) + r = st_action.get(a,None) + if r is not None: + # Whoa. Have a shift/reduce or reduce/reduce conflict + if r > 0: + # Need to decide on shift or reduce here + # By default we favor shifting. Need to add + # some precedence rules here. + sprec,slevel = Productions[st_actionp[a].number].prec + rprec,rlevel = Precedence.get(a,('right',0)) + if (slevel < rlevel) or ((slevel == rlevel) and (rprec == 'left')): + # We really need to reduce here. + st_action[a] = -p.number + st_actionp[a] = p + if not slevel and not rlevel: + log.info(" ! shift/reduce conflict for %s resolved as reduce",a) + self.sr_conflicts.append((st,a,'reduce')) + Productions[p.number].reduced += 1 + elif (slevel == rlevel) and (rprec == 'nonassoc'): + st_action[a] = None + else: + # Hmmm. Guess we'll keep the shift + if not rlevel: + log.info(" ! shift/reduce conflict for %s resolved as shift",a) + self.sr_conflicts.append((st,a,'shift')) + elif r < 0: + # Reduce/reduce conflict. In this case, we favor the rule + # that was defined first in the grammar file + oldp = Productions[-r] + pp = Productions[p.number] + if oldp.line > pp.line: + st_action[a] = -p.number + st_actionp[a] = p + chosenp,rejectp = pp,oldp + Productions[p.number].reduced += 1 + Productions[oldp.number].reduced -= 1 + else: + chosenp,rejectp = oldp,pp + self.rr_conflicts.append((st,chosenp,rejectp)) + log.info(" ! reduce/reduce conflict for %s resolved using rule %d (%s)", a,st_actionp[a].number, st_actionp[a]) + else: + raise LALRError("Unknown conflict in state %d" % st) + else: + st_action[a] = -p.number + st_actionp[a] = p + Productions[p.number].reduced += 1 + else: + i = p.lr_index + a = p.prod[i+1] # Get symbol right after the "." + if a in self.grammar.Terminals: + g = self.lr0_goto(I,a) + j = self.lr0_cidhash.get(id(g),-1) + if j >= 0: + # We are in a shift state + actlist.append((a,p,"shift and go to state %d" % j)) + r = st_action.get(a,None) + if r is not None: + # Whoa have a shift/reduce or shift/shift conflict + if r > 0: + if r != j: + raise LALRError("Shift/shift conflict in state %d" % st) + elif r < 0: + # Do a precedence check. + # - if precedence of reduce rule is higher, we reduce. + # - if precedence of reduce is same and left assoc, we reduce. + # - otherwise we shift + rprec,rlevel = Productions[st_actionp[a].number].prec + sprec,slevel = Precedence.get(a,('right',0)) + if (slevel > rlevel) or ((slevel == rlevel) and (rprec == 'right')): + # We decide to shift here... highest precedence to shift + Productions[st_actionp[a].number].reduced -= 1 + st_action[a] = j + st_actionp[a] = p + if not rlevel: + log.info(" ! shift/reduce conflict for %s resolved as shift",a) + self.sr_conflicts.append((st,a,'shift')) + elif (slevel == rlevel) and (rprec == 'nonassoc'): + st_action[a] = None + else: + # Hmmm. Guess we'll keep the reduce + if not slevel and not rlevel: + log.info(" ! shift/reduce conflict for %s resolved as reduce",a) + self.sr_conflicts.append((st,a,'reduce')) + + else: + raise LALRError("Unknown conflict in state %d" % st) + else: + st_action[a] = j + st_actionp[a] = p + + # Print the actions associated with each terminal + _actprint = { } + for a,p,m in actlist: + if a in st_action: + if p is st_actionp[a]: + log.info(" %-15s %s",a,m) + _actprint[(a,m)] = 1 + log.info("") + # Print the actions that were not used. (debugging) + not_used = 0 + for a,p,m in actlist: + if a in st_action: + if p is not st_actionp[a]: + if not (a,m) in _actprint: + log.debug(" ! %-15s [ %s ]",a,m) + not_used = 1 + _actprint[(a,m)] = 1 + if not_used: + log.debug("") + + # Construct the goto table for this state + + nkeys = { } + for ii in I: + for s in ii.usyms: + if s in self.grammar.Nonterminals: + nkeys[s] = None + for n in nkeys: + g = self.lr0_goto(I,n) + j = self.lr0_cidhash.get(id(g),-1) + if j >= 0: + st_goto[n] = j + log.info(" %-30s shift and go to state %d",n,j) + + action[st] = st_action + actionp[st] = st_actionp + goto[st] = st_goto + st += 1 + + + # ----------------------------------------------------------------------------- + # write() + # + # This function writes the LR parsing tables to a file + # ----------------------------------------------------------------------------- + + def write_table(self,modulename,outputdir='',signature=""): + basemodulename = modulename.split(".")[-1] + filename = os.path.join(outputdir,basemodulename) + ".py" + try: + f = open(filename,"w") + + f.write(""" +# %s +# This file is automatically generated. Do not edit. +_tabversion = %r + +_lr_method = %r + +_lr_signature = %r + """ % (filename, __tabversion__, self.lr_method, signature)) + + # Change smaller to 0 to go back to original tables + smaller = 1 + + # Factor out names to try and make smaller + if smaller: + items = { } + + for s,nd in self.lr_action.items(): + for name,v in nd.items(): + i = items.get(name) + if not i: + i = ([],[]) + items[name] = i + i[0].append(s) + i[1].append(v) + + f.write("\n_lr_action_items = {") + for k,v in items.items(): + f.write("%r:([" % k) + for i in v[0]: + f.write("%r," % i) + f.write("],[") + for i in v[1]: + f.write("%r," % i) + + f.write("]),") + f.write("}\n") + + f.write(""" +_lr_action = { } +for _k, _v in _lr_action_items.items(): + for _x,_y in zip(_v[0],_v[1]): + if not _x in _lr_action: _lr_action[_x] = { } + _lr_action[_x][_k] = _y +del _lr_action_items +""") + + else: + f.write("\n_lr_action = { "); + for k,v in self.lr_action.items(): + f.write("(%r,%r):%r," % (k[0],k[1],v)) + f.write("}\n"); + + if smaller: + # Factor out names to try and make smaller + items = { } + + for s,nd in self.lr_goto.items(): + for name,v in nd.items(): + i = items.get(name) + if not i: + i = ([],[]) + items[name] = i + i[0].append(s) + i[1].append(v) + + f.write("\n_lr_goto_items = {") + for k,v in items.items(): + f.write("%r:([" % k) + for i in v[0]: + f.write("%r," % i) + f.write("],[") + for i in v[1]: + f.write("%r," % i) + + f.write("]),") + f.write("}\n") + + f.write(""" +_lr_goto = { } +for _k, _v in _lr_goto_items.items(): + for _x,_y in zip(_v[0],_v[1]): + if not _x in _lr_goto: _lr_goto[_x] = { } + _lr_goto[_x][_k] = _y +del _lr_goto_items +""") + else: + f.write("\n_lr_goto = { "); + for k,v in self.lr_goto.items(): + f.write("(%r,%r):%r," % (k[0],k[1],v)) + f.write("}\n"); + + # Write production table + f.write("_lr_productions = [\n") + for p in self.lr_productions: + if p.func: + f.write(" (%r,%r,%d,%r,%r,%d),\n" % (p.str,p.name, p.len, p.func,p.file,p.line)) + else: + f.write(" (%r,%r,%d,None,None,None),\n" % (str(p),p.name, p.len)) + f.write("]\n") + f.close() + + except IOError: + e = sys.exc_info()[1] + sys.stderr.write("Unable to create '%s'\n" % filename) + sys.stderr.write(str(e)+"\n") + return + + + # ----------------------------------------------------------------------------- + # pickle_table() + # + # This function pickles the LR parsing tables to a supplied file object + # ----------------------------------------------------------------------------- + + def pickle_table(self,filename,signature=""): + try: + import cPickle as pickle + except ImportError: + import pickle + outf = open(filename,"wb") + pickle.dump(__tabversion__,outf,pickle_protocol) + pickle.dump(self.lr_method,outf,pickle_protocol) + pickle.dump(signature,outf,pickle_protocol) + pickle.dump(self.lr_action,outf,pickle_protocol) + pickle.dump(self.lr_goto,outf,pickle_protocol) + + outp = [] + for p in self.lr_productions: + if p.func: + outp.append((p.str,p.name, p.len, p.func,p.file,p.line)) + else: + outp.append((str(p),p.name,p.len,None,None,None)) + pickle.dump(outp,outf,pickle_protocol) + outf.close() + +# ----------------------------------------------------------------------------- +# === INTROSPECTION === +# +# The following functions and classes are used to implement the PLY +# introspection features followed by the yacc() function itself. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# get_caller_module_dict() +# +# This function returns a dictionary containing all of the symbols defined within +# a caller further down the call stack. This is used to get the environment +# associated with the yacc() call if none was provided. +# ----------------------------------------------------------------------------- + +def get_caller_module_dict(levels): + try: + raise RuntimeError + except RuntimeError: + e,b,t = sys.exc_info() + f = t.tb_frame + while levels > 0: + f = f.f_back + levels -= 1 + ldict = f.f_globals.copy() + if f.f_globals != f.f_locals: + ldict.update(f.f_locals) + + return ldict + +# ----------------------------------------------------------------------------- +# parse_grammar() +# +# This takes a raw grammar rule string and parses it into production data +# ----------------------------------------------------------------------------- +def parse_grammar(doc,file,line): + grammar = [] + # Split the doc string into lines + pstrings = doc.splitlines() + lastp = None + dline = line + for ps in pstrings: + dline += 1 + p = ps.split() + if not p: continue + try: + if p[0] == '|': + # This is a continuation of a previous rule + if not lastp: + raise SyntaxError("%s:%d: Misplaced '|'" % (file,dline)) + prodname = lastp + syms = p[1:] + else: + prodname = p[0] + lastp = prodname + syms = p[2:] + assign = p[1] + if assign != ':' and assign != '::=': + raise SyntaxError("%s:%d: Syntax error. Expected ':'" % (file,dline)) + + grammar.append((file,dline,prodname,syms)) + except SyntaxError: + raise + except Exception: + raise SyntaxError("%s:%d: Syntax error in rule '%s'" % (file,dline,ps.strip())) + + return grammar + +# ----------------------------------------------------------------------------- +# ParserReflect() +# +# This class represents information extracted for building a parser including +# start symbol, error function, tokens, precedence list, action functions, +# etc. +# ----------------------------------------------------------------------------- +class ParserReflect(object): + def __init__(self,pdict,log=None): + self.pdict = pdict + self.start = None + self.error_func = None + self.tokens = None + self.files = {} + self.grammar = [] + self.error = 0 + + if log is None: + self.log = PlyLogger(sys.stderr) + else: + self.log = log + + # Get all of the basic information + def get_all(self): + self.get_start() + self.get_error_func() + self.get_tokens() + self.get_precedence() + self.get_pfunctions() + + # Validate all of the information + def validate_all(self): + self.validate_start() + self.validate_error_func() + self.validate_tokens() + self.validate_precedence() + self.validate_pfunctions() + self.validate_files() + return self.error + + # Compute a signature over the grammar + def signature(self): + try: + from hashlib import md5 + except ImportError: + from md5 import md5 + try: + sig = md5() + if self.start: + sig.update(self.start.encode('latin-1')) + if self.prec: + sig.update("".join(["".join(p) for p in self.prec]).encode('latin-1')) + if self.tokens: + sig.update(" ".join(self.tokens).encode('latin-1')) + for f in self.pfuncs: + if f[3]: + sig.update(f[3].encode('latin-1')) + except (TypeError,ValueError): + pass + return sig.digest() + + # ----------------------------------------------------------------------------- + # validate_file() + # + # This method checks to see if there are duplicated p_rulename() functions + # in the parser module file. Without this function, it is really easy for + # users to make mistakes by cutting and pasting code fragments (and it's a real + # bugger to try and figure out why the resulting parser doesn't work). Therefore, + # we just do a little regular expression pattern matching of def statements + # to try and detect duplicates. + # ----------------------------------------------------------------------------- + + def validate_files(self): + # Match def p_funcname( + fre = re.compile(r'\s*def\s+(p_[a-zA-Z_0-9]*)\(') + + for filename in self.files.keys(): + base,ext = os.path.splitext(filename) + if ext != '.py': return 1 # No idea. Assume it's okay. + + try: + f = open(filename) + lines = f.readlines() + f.close() + except IOError: + continue + + counthash = { } + for linen,l in enumerate(lines): + linen += 1 + m = fre.match(l) + if m: + name = m.group(1) + prev = counthash.get(name) + if not prev: + counthash[name] = linen + else: + self.log.warning("%s:%d: Function %s redefined. Previously defined on line %d", filename,linen,name,prev) + + # Get the start symbol + def get_start(self): + self.start = self.pdict.get('start') + + # Validate the start symbol + def validate_start(self): + if self.start is not None: + if not isinstance(self.start,str): + self.log.error("'start' must be a string") + + # Look for error handler + def get_error_func(self): + self.error_func = self.pdict.get('p_error') + + # Validate the error function + def validate_error_func(self): + if self.error_func: + if isinstance(self.error_func,types.FunctionType): + ismethod = 0 + elif isinstance(self.error_func, types.MethodType): + ismethod = 1 + else: + self.log.error("'p_error' defined, but is not a function or method") + self.error = 1 + return + + eline = func_code(self.error_func).co_firstlineno + efile = func_code(self.error_func).co_filename + self.files[efile] = 1 + + if (func_code(self.error_func).co_argcount != 1+ismethod): + self.log.error("%s:%d: p_error() requires 1 argument",efile,eline) + self.error = 1 + + # Get the tokens map + def get_tokens(self): + tokens = self.pdict.get("tokens",None) + if not tokens: + self.log.error("No token list is defined") + self.error = 1 + return + + if not isinstance(tokens,(list, tuple)): + self.log.error("tokens must be a list or tuple") + self.error = 1 + return + + if not tokens: + self.log.error("tokens is empty") + self.error = 1 + return + + self.tokens = tokens + + # Validate the tokens + def validate_tokens(self): + # Validate the tokens. + if 'error' in self.tokens: + self.log.error("Illegal token name 'error'. Is a reserved word") + self.error = 1 + return + + terminals = {} + for n in self.tokens: + if n in terminals: + self.log.warning("Token '%s' multiply defined", n) + terminals[n] = 1 + + # Get the precedence map (if any) + def get_precedence(self): + self.prec = self.pdict.get("precedence",None) + + # Validate and parse the precedence map + def validate_precedence(self): + preclist = [] + if self.prec: + if not isinstance(self.prec,(list,tuple)): + self.log.error("precedence must be a list or tuple") + self.error = 1 + return + for level,p in enumerate(self.prec): + if not isinstance(p,(list,tuple)): + self.log.error("Bad precedence table") + self.error = 1 + return + + if len(p) < 2: + self.log.error("Malformed precedence entry %s. Must be (assoc, term, ..., term)",p) + self.error = 1 + return + assoc = p[0] + if not isinstance(assoc,str): + self.log.error("precedence associativity must be a string") + self.error = 1 + return + for term in p[1:]: + if not isinstance(term,str): + self.log.error("precedence items must be strings") + self.error = 1 + return + preclist.append((term,assoc,level+1)) + self.preclist = preclist + + # Get all p_functions from the grammar + def get_pfunctions(self): + p_functions = [] + for name, item in self.pdict.items(): + if name[:2] != 'p_': continue + if name == 'p_error': continue + if isinstance(item,(types.FunctionType,types.MethodType)): + line = func_code(item).co_firstlineno + file = func_code(item).co_filename + p_functions.append((line,file,name,item.__doc__)) + + # Sort all of the actions by line number + p_functions.sort() + self.pfuncs = p_functions + + + # Validate all of the p_functions + def validate_pfunctions(self): + grammar = [] + # Check for non-empty symbols + if len(self.pfuncs) == 0: + self.log.error("no rules of the form p_rulename are defined") + self.error = 1 + return + + for line, file, name, doc in self.pfuncs: + func = self.pdict[name] + if isinstance(func, types.MethodType): + reqargs = 2 + else: + reqargs = 1 + if func_code(func).co_argcount > reqargs: + self.log.error("%s:%d: Rule '%s' has too many arguments",file,line,func.__name__) + self.error = 1 + elif func_code(func).co_argcount < reqargs: + self.log.error("%s:%d: Rule '%s' requires an argument",file,line,func.__name__) + self.error = 1 + elif not func.__doc__: + self.log.warning("%s:%d: No documentation string specified in function '%s' (ignored)",file,line,func.__name__) + else: + try: + parsed_g = parse_grammar(doc,file,line) + for g in parsed_g: + grammar.append((name, g)) + except SyntaxError: + e = sys.exc_info()[1] + self.log.error(str(e)) + self.error = 1 + + # Looks like a valid grammar rule + # Mark the file in which defined. + self.files[file] = 1 + + # Secondary validation step that looks for p_ definitions that are not functions + # or functions that look like they might be grammar rules. + + for n,v in self.pdict.items(): + if n[0:2] == 'p_' and isinstance(v, (types.FunctionType, types.MethodType)): continue + if n[0:2] == 't_': continue + if n[0:2] == 'p_' and n != 'p_error': + self.log.warning("'%s' not defined as a function", n) + if ((isinstance(v,types.FunctionType) and func_code(v).co_argcount == 1) or + (isinstance(v,types.MethodType) and func_code(v).co_argcount == 2)): + try: + doc = v.__doc__.split(" ") + if doc[1] == ':': + self.log.warning("%s:%d: Possible grammar rule '%s' defined without p_ prefix", + func_code(v).co_filename, func_code(v).co_firstlineno,n) + except Exception: + pass + + self.grammar = grammar + +# ----------------------------------------------------------------------------- +# yacc(module) +# +# Build a parser +# ----------------------------------------------------------------------------- + +def yacc(method='LALR', debug=yaccdebug, module=None, tabmodule=tab_module, start=None, + check_recursion=1, optimize=0, write_tables=1, debugfile=debug_file,outputdir='', + debuglog=None, errorlog = None, picklefile=None): + + global parse # Reference to the parsing method of the last built parser + + # If pickling is enabled, table files are not created + + if picklefile: + write_tables = 0 + + if errorlog is None: + errorlog = PlyLogger(sys.stderr) + + # Get the module dictionary used for the parser + if module: + _items = [(k,getattr(module,k)) for k in dir(module)] + pdict = dict(_items) + else: + pdict = get_caller_module_dict(2) + + # Collect parser information from the dictionary + pinfo = ParserReflect(pdict,log=errorlog) + pinfo.get_all() + + if pinfo.error: + raise YaccError("Unable to build parser") + + # Check signature against table files (if any) + signature = pinfo.signature() + + # Read the tables + try: + lr = LRTable() + if picklefile: + read_signature = lr.read_pickle(picklefile) + else: + read_signature = lr.read_table(tabmodule) + if optimize or (read_signature == signature): + try: + lr.bind_callables(pinfo.pdict) + parser = LRParser(lr,pinfo.error_func) + parse = parser.parse + return parser + except Exception: + e = sys.exc_info()[1] + errorlog.warning("There was a problem loading the table file: %s", repr(e)) + except VersionError: + e = sys.exc_info() + errorlog.warning(str(e)) + except Exception: + pass + + if debuglog is None: + if debug: + debuglog = PlyLogger(open(debugfile,"w")) + else: + debuglog = NullLogger() + + debuglog.info("Created by PLY version %s (http://www.dabeaz.com/ply)", __version__) + + + errors = 0 + + # Validate the parser information + if pinfo.validate_all(): + raise YaccError("Unable to build parser") + + if not pinfo.error_func: + errorlog.warning("no p_error() function is defined") + + # Create a grammar object + grammar = Grammar(pinfo.tokens) + + # Set precedence level for terminals + for term, assoc, level in pinfo.preclist: + try: + grammar.set_precedence(term,assoc,level) + except GrammarError: + e = sys.exc_info()[1] + errorlog.warning("%s",str(e)) + + # Add productions to the grammar + for funcname, gram in pinfo.grammar: + file, line, prodname, syms = gram + try: + grammar.add_production(prodname,syms,funcname,file,line) + except GrammarError: + e = sys.exc_info()[1] + errorlog.error("%s",str(e)) + errors = 1 + + # Set the grammar start symbols + try: + if start is None: + grammar.set_start(pinfo.start) + else: + grammar.set_start(start) + except GrammarError: + e = sys.exc_info()[1] + errorlog.error(str(e)) + errors = 1 + + if errors: + raise YaccError("Unable to build parser") + + # Verify the grammar structure + undefined_symbols = grammar.undefined_symbols() + for sym, prod in undefined_symbols: + errorlog.error("%s:%d: Symbol '%s' used, but not defined as a token or a rule",prod.file,prod.line,sym) + errors = 1 + + unused_terminals = grammar.unused_terminals() + if unused_terminals: + debuglog.info("") + debuglog.info("Unused terminals:") + debuglog.info("") + for term in unused_terminals: + errorlog.warning("Token '%s' defined, but not used", term) + debuglog.info(" %s", term) + + # Print out all productions to the debug log + if debug: + debuglog.info("") + debuglog.info("Grammar") + debuglog.info("") + for n,p in enumerate(grammar.Productions): + debuglog.info("Rule %-5d %s", n, p) + + # Find unused non-terminals + unused_rules = grammar.unused_rules() + for prod in unused_rules: + errorlog.warning("%s:%d: Rule '%s' defined, but not used", prod.file, prod.line, prod.name) + + if len(unused_terminals) == 1: + errorlog.warning("There is 1 unused token") + if len(unused_terminals) > 1: + errorlog.warning("There are %d unused tokens", len(unused_terminals)) + + if len(unused_rules) == 1: + errorlog.warning("There is 1 unused rule") + if len(unused_rules) > 1: + errorlog.warning("There are %d unused rules", len(unused_rules)) + + if debug: + debuglog.info("") + debuglog.info("Terminals, with rules where they appear") + debuglog.info("") + terms = list(grammar.Terminals) + terms.sort() + for term in terms: + debuglog.info("%-20s : %s", term, " ".join([str(s) for s in grammar.Terminals[term]])) + + debuglog.info("") + debuglog.info("Nonterminals, with rules where they appear") + debuglog.info("") + nonterms = list(grammar.Nonterminals) + nonterms.sort() + for nonterm in nonterms: + debuglog.info("%-20s : %s", nonterm, " ".join([str(s) for s in grammar.Nonterminals[nonterm]])) + debuglog.info("") + + if check_recursion: + unreachable = grammar.find_unreachable() + for u in unreachable: + errorlog.warning("Symbol '%s' is unreachable",u) + + infinite = grammar.infinite_cycles() + for inf in infinite: + errorlog.error("Infinite recursion detected for symbol '%s'", inf) + errors = 1 + + unused_prec = grammar.unused_precedence() + for term, assoc in unused_prec: + errorlog.error("Precedence rule '%s' defined for unknown symbol '%s'", assoc, term) + errors = 1 + + if errors: + raise YaccError("Unable to build parser") + + # Run the LRGeneratedTable on the grammar + if debug: + errorlog.debug("Generating %s tables", method) + + lr = LRGeneratedTable(grammar,method,debuglog) + + if debug: + num_sr = len(lr.sr_conflicts) + + # Report shift/reduce and reduce/reduce conflicts + if num_sr == 1: + errorlog.warning("1 shift/reduce conflict") + elif num_sr > 1: + errorlog.warning("%d shift/reduce conflicts", num_sr) + + num_rr = len(lr.rr_conflicts) + if num_rr == 1: + errorlog.warning("1 reduce/reduce conflict") + elif num_rr > 1: + errorlog.warning("%d reduce/reduce conflicts", num_rr) + + # Write out conflicts to the output file + if debug and (lr.sr_conflicts or lr.rr_conflicts): + debuglog.warning("") + debuglog.warning("Conflicts:") + debuglog.warning("") + + for state, tok, resolution in lr.sr_conflicts: + debuglog.warning("shift/reduce conflict for %s in state %d resolved as %s", tok, state, resolution) + + already_reported = {} + for state, rule, rejected in lr.rr_conflicts: + if (state,id(rule),id(rejected)) in already_reported: + continue + debuglog.warning("reduce/reduce conflict in state %d resolved using rule (%s)", state, rule) + debuglog.warning("rejected rule (%s) in state %d", rejected,state) + errorlog.warning("reduce/reduce conflict in state %d resolved using rule (%s)", state, rule) + errorlog.warning("rejected rule (%s) in state %d", rejected, state) + already_reported[state,id(rule),id(rejected)] = 1 + + warned_never = [] + for state, rule, rejected in lr.rr_conflicts: + if not rejected.reduced and (rejected not in warned_never): + debuglog.warning("Rule (%s) is never reduced", rejected) + errorlog.warning("Rule (%s) is never reduced", rejected) + warned_never.append(rejected) + + # Write the table file if requested + if write_tables: + lr.write_table(tabmodule,outputdir,signature) + + # Write a pickled version of the tables + if picklefile: + lr.pickle_table(picklefile,signature) + + # Build the parser + lr.bind_callables(pinfo.pdict) + parser = LRParser(lr,pinfo.error_func) + + parse = parser.parse + return parser diff --git a/components/script/dom/bindings/codegen/pythonpath.py b/components/script/dom/bindings/codegen/pythonpath.py new file mode 100644 index 00000000000..49b2d2f740f --- /dev/null +++ b/components/script/dom/bindings/codegen/pythonpath.py @@ -0,0 +1,60 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +""" +Run a python script, adding extra directories to the python path. +""" + + +def main(args): + def usage(): + print >>sys.stderr, "pythonpath.py -I directory script.py [args...]" + sys.exit(150) + + paths = [] + + while True: + try: + arg = args[0] + except IndexError: + usage() + + if arg == '-I': + args.pop(0) + try: + path = args.pop(0) + except IndexError: + usage() + + paths.append(os.path.abspath(path)) + continue + + if arg.startswith('-I'): + paths.append(os.path.abspath(args.pop(0)[2:])) + continue + + if arg.startswith('-D'): + os.chdir(args.pop(0)[2:]) + continue + + break + + script = args[0] + + sys.path[0:0] = [os.path.abspath(os.path.dirname(script))] + paths + sys.argv = args + sys.argc = len(args) + + frozenglobals['__name__'] = '__main__' + frozenglobals['__file__'] = script + + execfile(script, frozenglobals) + +# Freeze scope here ... why this makes things work I have no idea ... +frozenglobals = globals() + +import sys, os + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/components/script/dom/bindings/codegen/stubgenerator/Skeleton.cpp b/components/script/dom/bindings/codegen/stubgenerator/Skeleton.cpp new file mode 100644 index 00000000000..dfa17d23400 --- /dev/null +++ b/components/script/dom/bindings/codegen/stubgenerator/Skeleton.cpp @@ -0,0 +1,40 @@ +/* -*- 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 "Skeleton.h" +#include "mozilla/dom/SkeletonBinding.h" +#include "nsContentUtils.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(Skeleton) +NS_IMPL_CYCLE_COLLECTING_ADDREF(Skeleton) +NS_IMPL_CYCLE_COLLECTING_RELEASE(Skeleton) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Skeleton) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +Skeleton::Skeleton() +{ + SetIsDOMBinding(); +} + +Skeleton::~Skeleton() +{ +} + +JSObject* +Skeleton::WrapObject(JSContext* aCx, JSObject* aScope, + bool* aTriedToWrap) +{ + return SkeletonBinding::Wrap(aCx, aScope, this, aTriedToWrap); +} + +} +} + diff --git a/components/script/dom/bindings/codegen/stubgenerator/Skeleton.h b/components/script/dom/bindings/codegen/stubgenerator/Skeleton.h new file mode 100644 index 00000000000..286cff9af4a --- /dev/null +++ b/components/script/dom/bindings/codegen/stubgenerator/Skeleton.h @@ -0,0 +1,40 @@ +/* -*- 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/. */ + +#pragma once + +#include "nsWrapperCache.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/Attributes.h" + +struct JSContext; + +namespace mozilla { +namespace dom { + +class Skeleton MOZ_FINAL : public nsISupports, + public nsWrapperCache +{ +public: + Skeleton(); + ~Skeleton(); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Skeleton) + + void* GetParentObject() const + { + // TODO: return something sensible here, and change the return type + return somethingSensible; + } + + virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope, + bool* aTriedToWrap); +}; + +} +} + diff --git a/components/script/dom/bindings/codegen/stubgenerator/generate.sh b/components/script/dom/bindings/codegen/stubgenerator/generate.sh new file mode 100644 index 00000000000..52577f6f42f --- /dev/null +++ b/components/script/dom/bindings/codegen/stubgenerator/generate.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# This script creates a skeleton implementation for a C++ class which +# implements a Web IDL interface. + +# This script is released into the public domain. + +if [ -z "$1" ]; then + echo usage: ./generate.sh ClassName + exit 1 +fi + +expression="s/Skeleton/$1/g" + +sed "$expression" < Skeleton.h > "$1.h" +sed "$expression" < Skeleton.cpp > "$1.cpp" + diff --git a/components/script/dom/bindings/codegen/test/Makefile.in b/components/script/dom/bindings/codegen/test/Makefile.in new file mode 100644 index 00000000000..d8104db5ffd --- /dev/null +++ b/components/script/dom/bindings/codegen/test/Makefile.in @@ -0,0 +1,87 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +DEPTH = @DEPTH@ +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ +relativesrcdir = @relativesrcdir@ + +MODULE = dom +LIBRARY_NAME = dombindings_test_s +LIBXUL_LIBRARY = 1 +FORCE_STATIC_LIB = 1 +# Do NOT export this library. We don't actually want our test code +# being added to libxul or anything. + +include $(DEPTH)/config/autoconf.mk + +# Need this to find all our DOM source files. +include $(topsrcdir)/dom/dom-config.mk + +# And need this for $(test_webidl_files) +include $(topsrcdir)/dom/webidl/WebIDL.mk + +# But the webidl actually lives in our parent dir +test_webidl_files := $(addprefix ../,$(test_webidl_files)) + +CPPSRCS := $(subst .webidl,Binding.cpp,$(test_webidl_files)) + +LOCAL_INCLUDES += \ + -I$(topsrcdir)/js/xpconnect/src \ + -I$(topsrcdir)/js/xpconnect/wrappers \ + -I$(topsrcdir)/dom/bindings \ + $(NULL) + + +# If you change bindinggen_dependencies here, change it in +# dom/bindings/Makefile.in too. But note that we include ../Makefile +# here manually, since $(GLOBAL_DEPS) won't cover it. +bindinggen_dependencies := \ + ../BindingGen.py \ + ../Bindings.conf \ + ../Configuration.py \ + ../Codegen.py \ + ../parser/WebIDL.py \ + ../ParserResults.pkl \ + ../Makefile \ + $(GLOBAL_DEPS) \ + $(NULL) + +MOCHITEST_FILES := \ + test_bug773326.html \ + test_enums.html \ + test_integers.html \ + test_interfaceToString.html \ + test_lookupGetter.html \ + test_InstanceOf.html \ + test_traceProtos.html \ + test_forOf.html \ + forOf_iframe.html \ + test_sequence_wrapping.html \ + file_bug775543.html \ + test_bug788369.html \ + $(NULL) + +MOCHITEST_CHROME_FILES = \ + test_bug775543.html \ + $(NULL) + +# Include rules.mk before any of our targets so our first target is coming from +# rules.mk and running make with no target in this dir does the right thing. +include $(topsrcdir)/config/rules.mk + +$(CPPSRCS): ../%Binding.cpp: $(bindinggen_dependencies) \ + ../%.webidl \ + $(NULL) + $(MAKE) -C .. $*Binding.h + $(MAKE) -C .. $*Binding.cpp + +check:: + $(PYTHON) $(topsrcdir)/config/pythonpath.py \ + $(PLY_INCLUDE) $(srcdir)/../parser/runtests.py + +check-interactive: + $(PYTHON) $(topsrcdir)/config/pythonpath.py \ + $(PLY_INCLUDE) $(srcdir)/../parser/runtests.py -q diff --git a/components/script/dom/bindings/codegen/test/TestBindingHeader.h b/components/script/dom/bindings/codegen/test/TestBindingHeader.h new file mode 100644 index 00000000000..1fbab0a9fb8 --- /dev/null +++ b/components/script/dom/bindings/codegen/test/TestBindingHeader.h @@ -0,0 +1,653 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef TestBindingHeader_h +#define TestBindingHeader_h + +#include "nsWrapperCache.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/TypedArray.h" +#include "nsCOMPtr.h" +// We don't export TestCodeGenBinding.h, but it's right in our parent dir. +#include "../TestCodeGenBinding.h" +#include "mozilla/dom/UnionTypes.h" + +namespace mozilla { +namespace dom { + +// IID for the TestNonCastableInterface +#define NS_TEST_NONCASTABLE_INTERFACE_IID \ +{ 0x7c9f8ee2, 0xc9bf, 0x46ca, \ + { 0xa0, 0xa9, 0x03, 0xa8, 0xd6, 0x30, 0x0e, 0xde } } + +class TestNonCastableInterface : public nsISupports, + public nsWrapperCache +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_TEST_NONCASTABLE_INTERFACE_IID) + NS_DECL_ISUPPORTS + + // We need a GetParentObject to make binding codegen happy + virtual nsISupports* GetParentObject(); +}; + +// IID for the IndirectlyImplementedInterface +#define NS_INDIRECTLY_IMPLEMENTED_INTERFACE_IID \ +{ 0xfed55b69, 0x7012, 0x4849, \ + { 0xaf, 0x56, 0x4b, 0xa9, 0xee, 0x41, 0x30, 0x89 } } + +class IndirectlyImplementedInterface : public nsISupports, + public nsWrapperCache +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_INDIRECTLY_IMPLEMENTED_INTERFACE_IID) + NS_DECL_ISUPPORTS + + // We need a GetParentObject to make binding codegen happy + virtual nsISupports* GetParentObject(); + + bool IndirectlyImplementedProperty(); + void IndirectlyImplementedProperty(bool); + void IndirectlyImplementedMethod(); +}; + +// IID for the TestExternalInterface +#define NS_TEST_EXTERNAL_INTERFACE_IID \ +{ 0xd5ba0c99, 0x9b1d, 0x4e71, \ + { 0x8a, 0x94, 0x56, 0x38, 0x6c, 0xa3, 0xda, 0x3d } } +class TestExternalInterface : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_TEST_EXTERNAL_INTERFACE_IID) + NS_DECL_ISUPPORTS +}; + +// IID for the TestCallbackInterface +#define NS_TEST_CALLBACK_INTERFACE_IID \ +{ 0xbf711ba4, 0xc8f6, 0x46cf, \ + { 0xba, 0x5b, 0xaa, 0xe2, 0x78, 0x18, 0xe6, 0x4a } } +class TestCallbackInterface : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_TEST_CALLBACK_INTERFACE_IID) + NS_DECL_ISUPPORTS +}; + +class TestNonWrapperCacheInterface : public nsISupports +{ +public: + NS_DECL_ISUPPORTS + + virtual JSObject* WrapObject(JSContext* cx, JSObject* scope); +}; + +class OnlyForUseInConstructor : public nsISupports, + public nsWrapperCache +{ +public: + NS_DECL_ISUPPORTS + // We need a GetParentObject to make binding codegen happy + virtual nsISupports* GetParentObject(); +}; + +class TestInterface : public nsISupports, + public nsWrapperCache +{ +public: + NS_DECL_ISUPPORTS + + // We need a GetParentObject to make binding codegen happy + virtual nsISupports* GetParentObject(); + + // And now our actual WebIDL API + // Constructors + static + already_AddRefed<TestInterface> Constructor(nsISupports*, ErrorResult&); + static + already_AddRefed<TestInterface> Constructor(nsISupports*, const nsAString&, + ErrorResult&); + static + already_AddRefed<TestInterface> Constructor(nsISupports*, uint32_t, + Nullable<bool>&, ErrorResult&); + static + already_AddRefed<TestInterface> Constructor(nsISupports*, TestInterface*, + ErrorResult&); + static + already_AddRefed<TestInterface> Constructor(nsISupports*, + TestNonCastableInterface&, + ErrorResult&); + /* static + already_AddRefed<TestInterface> Constructor(nsISupports*, + uint32_t, uint32_t, + const TestInterfaceOrOnlyForUseInConstructor&, + ErrorResult&); + */ + + // Integer types + int8_t ReadonlyByte(); + int8_t WritableByte(); + void SetWritableByte(int8_t); + void PassByte(int8_t); + int8_t ReceiveByte(); + void PassOptionalByte(const Optional<int8_t>&); + void PassOptionalByteWithDefault(int8_t); + void PassNullableByte(Nullable<int8_t>&); + void PassOptionalNullableByte(const Optional< Nullable<int8_t> >&); + + int16_t ReadonlyShort(); + int16_t WritableShort(); + void SetWritableShort(int16_t); + void PassShort(int16_t); + int16_t ReceiveShort(); + void PassOptionalShort(const Optional<int16_t>&); + void PassOptionalShortWithDefault(int16_t); + + int32_t ReadonlyLong(); + int32_t WritableLong(); + void SetWritableLong(int32_t); + void PassLong(int32_t); + int16_t ReceiveLong(); + void PassOptionalLong(const Optional<int32_t>&); + void PassOptionalLongWithDefault(int32_t); + + int64_t ReadonlyLongLong(); + int64_t WritableLongLong(); + void SetWritableLongLong(int64_t); + void PassLongLong(int64_t); + int64_t ReceiveLongLong(); + void PassOptionalLongLong(const Optional<int64_t>&); + void PassOptionalLongLongWithDefault(int64_t); + + uint8_t ReadonlyOctet(); + uint8_t WritableOctet(); + void SetWritableOctet(uint8_t); + void PassOctet(uint8_t); + uint8_t ReceiveOctet(); + void PassOptionalOctet(const Optional<uint8_t>&); + void PassOptionalOctetWithDefault(uint8_t); + + uint16_t ReadonlyUnsignedShort(); + uint16_t WritableUnsignedShort(); + void SetWritableUnsignedShort(uint16_t); + void PassUnsignedShort(uint16_t); + uint16_t ReceiveUnsignedShort(); + void PassOptionalUnsignedShort(const Optional<uint16_t>&); + void PassOptionalUnsignedShortWithDefault(uint16_t); + + uint32_t ReadonlyUnsignedLong(); + uint32_t WritableUnsignedLong(); + void SetWritableUnsignedLong(uint32_t); + void PassUnsignedLong(uint32_t); + uint32_t ReceiveUnsignedLong(); + void PassOptionalUnsignedLong(const Optional<uint32_t>&); + void PassOptionalUnsignedLongWithDefault(uint32_t); + + uint64_t ReadonlyUnsignedLongLong(); + uint64_t WritableUnsignedLongLong(); + void SetWritableUnsignedLongLong(uint64_t); + void PassUnsignedLongLong(uint64_t); + uint64_t ReceiveUnsignedLongLong(); + void PassOptionalUnsignedLongLong(const Optional<uint64_t>&); + void PassOptionalUnsignedLongLongWithDefault(uint64_t); + + // Interface types + already_AddRefed<TestInterface> ReceiveSelf(); + already_AddRefed<TestInterface> ReceiveNullableSelf(); + TestInterface* ReceiveWeakSelf(); + TestInterface* ReceiveWeakNullableSelf(); + void PassSelf(TestInterface&); + void PassSelf2(NonNull<TestInterface>&); + void PassNullableSelf(TestInterface*); + already_AddRefed<TestInterface> NonNullSelf(); + void SetNonNullSelf(TestInterface&); + already_AddRefed<TestInterface> GetNullableSelf(); + void SetNullableSelf(TestInterface*); + void PassOptionalSelf(const Optional<TestInterface*> &); + void PassOptionalNonNullSelf(const Optional<NonNull<TestInterface> >&); + void PassOptionalSelfWithDefault(TestInterface*); + + already_AddRefed<TestNonWrapperCacheInterface> ReceiveNonWrapperCacheInterface(); + already_AddRefed<TestNonWrapperCacheInterface> ReceiveNullableNonWrapperCacheInterface(); + void ReceiveNonWrapperCacheInterfaceSequence(nsTArray<nsRefPtr<TestNonWrapperCacheInterface> >&); + void ReceiveNullableNonWrapperCacheInterfaceSequence(nsTArray<nsRefPtr<TestNonWrapperCacheInterface> >&); + void ReceiveNonWrapperCacheInterfaceNullableSequence(Nullable<nsTArray<nsRefPtr<TestNonWrapperCacheInterface> > >&); + void ReceiveNullableNonWrapperCacheInterfaceNullableSequence(Nullable<nsTArray<nsRefPtr<TestNonWrapperCacheInterface> > >&); + + already_AddRefed<TestNonCastableInterface> ReceiveOther(); + already_AddRefed<TestNonCastableInterface> ReceiveNullableOther(); + TestNonCastableInterface* ReceiveWeakOther(); + TestNonCastableInterface* ReceiveWeakNullableOther(); + void PassOther(TestNonCastableInterface&); + void PassOther2(NonNull<TestNonCastableInterface>&); + void PassNullableOther(TestNonCastableInterface*); + already_AddRefed<TestNonCastableInterface> NonNullOther(); + void SetNonNullOther(TestNonCastableInterface&); + already_AddRefed<TestNonCastableInterface> GetNullableOther(); + void SetNullableOther(TestNonCastableInterface*); + void PassOptionalOther(const Optional<TestNonCastableInterface*>&); + void PassOptionalNonNullOther(const Optional<NonNull<TestNonCastableInterface> >&); + void PassOptionalOtherWithDefault(TestNonCastableInterface*); + + already_AddRefed<TestExternalInterface> ReceiveExternal(); + already_AddRefed<TestExternalInterface> ReceiveNullableExternal(); + TestExternalInterface* ReceiveWeakExternal(); + TestExternalInterface* ReceiveWeakNullableExternal(); + void PassExternal(TestExternalInterface*); + void PassExternal2(TestExternalInterface*); + void PassNullableExternal(TestExternalInterface*); + already_AddRefed<TestExternalInterface> NonNullExternal(); + void SetNonNullExternal(TestExternalInterface*); + already_AddRefed<TestExternalInterface> GetNullableExternal(); + void SetNullableExternal(TestExternalInterface*); + void PassOptionalExternal(const Optional<TestExternalInterface*>&); + void PassOptionalNonNullExternal(const Optional<TestExternalInterface*>&); + void PassOptionalExternalWithDefault(TestExternalInterface*); + + already_AddRefed<TestCallbackInterface> ReceiveCallbackInterface(); + already_AddRefed<TestCallbackInterface> ReceiveNullableCallbackInterface(); + TestCallbackInterface* ReceiveWeakCallbackInterface(); + TestCallbackInterface* ReceiveWeakNullableCallbackInterface(); + void PassCallbackInterface(TestCallbackInterface&); + void PassCallbackInterface2(OwningNonNull<TestCallbackInterface>); + void PassNullableCallbackInterface(TestCallbackInterface*); + already_AddRefed<TestCallbackInterface> NonNullCallbackInterface(); + void SetNonNullCallbackInterface(TestCallbackInterface&); + already_AddRefed<TestCallbackInterface> GetNullableCallbackInterface(); + void SetNullableCallbackInterface(TestCallbackInterface*); + void PassOptionalCallbackInterface(const Optional<nsRefPtr<TestCallbackInterface> >&); + void PassOptionalNonNullCallbackInterface(const Optional<OwningNonNull<TestCallbackInterface> >&); + void PassOptionalCallbackInterfaceWithDefault(TestCallbackInterface*); + + already_AddRefed<IndirectlyImplementedInterface> ReceiveConsequentialInterface(); + void PassConsequentialInterface(IndirectlyImplementedInterface&); + + // Sequence types + void ReceiveSequence(nsTArray<int32_t>&); + void ReceiveNullableSequence(Nullable< nsTArray<int32_t> >&); + void ReceiveSequenceOfNullableInts(nsTArray< Nullable<int32_t> >&); + void ReceiveNullableSequenceOfNullableInts(Nullable< nsTArray< Nullable<int32_t> > >&); + void PassSequence(const Sequence<int32_t> &); + void PassNullableSequence(const Nullable< Sequence<int32_t> >&); + void PassSequenceOfNullableInts(const Sequence<Nullable<int32_t> >&); + void PassOptionalSequenceOfNullableInts(const Optional<Sequence<Nullable<int32_t> > > &); + void PassOptionalNullableSequenceOfNullableInts(const Optional<Nullable<Sequence<Nullable<int32_t> > > > &); + void ReceiveCastableObjectSequence(nsTArray< nsRefPtr<TestInterface> > &); + void ReceiveNullableCastableObjectSequence(nsTArray< nsRefPtr<TestInterface> > &); + void ReceiveCastableObjectNullableSequence(Nullable< nsTArray< nsRefPtr<TestInterface> > >&); + void ReceiveNullableCastableObjectNullableSequence(Nullable< nsTArray< nsRefPtr<TestInterface> > >&); + void ReceiveWeakCastableObjectSequence(nsTArray<TestInterface*> &); + void ReceiveWeakNullableCastableObjectSequence(nsTArray<TestInterface*> &); + void ReceiveWeakCastableObjectNullableSequence(Nullable< nsTArray<TestInterface*> >&); + void ReceiveWeakNullableCastableObjectNullableSequence(Nullable< nsTArray<TestInterface*> >&); + void PassCastableObjectSequence(const Sequence< OwningNonNull<TestInterface> >&); + void PassNullableCastableObjectSequence(const Sequence< nsRefPtr<TestInterface> > &); + void PassCastableObjectNullableSequence(const Nullable< Sequence< OwningNonNull<TestInterface> > >&); + void PassNullableCastableObjectNullableSequence(const Nullable< Sequence< nsRefPtr<TestInterface> > >&); + void PassOptionalSequence(const Optional<Sequence<int32_t> >&); + void PassOptionalNullableSequence(const Optional<Nullable<Sequence<int32_t> > >&); + void PassOptionalNullableSequenceWithDefaultValue(const Nullable< Sequence<int32_t> >&); + void PassOptionalObjectSequence(const Optional<Sequence<OwningNonNull<TestInterface> > >&); + + void ReceiveStringSequence(nsTArray<nsString>&); + void PassStringSequence(const Sequence<nsString>&); + + void ReceiveAnySequence(JSContext*, nsTArray<JS::Value>&); + void ReceiveNullableAnySequence(JSContext*, Nullable<nsTArray<JS::Value> >); + + // Typed array types + void PassArrayBuffer(ArrayBuffer&); + void PassNullableArrayBuffer(ArrayBuffer*); + void PassOptionalArrayBuffer(const Optional<ArrayBuffer>&); + void PassOptionalNullableArrayBuffer(const Optional<ArrayBuffer*>&); + void PassOptionalNullableArrayBufferWithDefaultValue(ArrayBuffer*); + void PassArrayBufferView(ArrayBufferView&); + void PassInt8Array(Int8Array&); + void PassInt16Array(Int16Array&); + void PassInt32Array(Int32Array&); + void PassUint8Array(Uint8Array&); + void PassUint16Array(Uint16Array&); + void PassUint32Array(Uint32Array&); + void PassUint8ClampedArray(Uint8ClampedArray&); + void PassFloat32Array(Float32Array&); + void PassFloat64Array(Float64Array&); + JSObject* ReceiveUint8Array(JSContext*); + + // String types + void PassString(const nsAString&); + void PassNullableString(const nsAString&); + void PassOptionalString(const Optional<nsAString>&); + void PassOptionalStringWithDefaultValue(const nsAString&); + void PassOptionalNullableString(const Optional<nsAString>&); + void PassOptionalNullableStringWithDefaultValue(const nsAString&); + + // Enumarated types + void PassEnum(TestEnum); + void PassOptionalEnum(const Optional<TestEnum>&); + void PassEnumWithDefault(TestEnum); + TestEnum ReceiveEnum(); + TestEnum EnumAttribute(); + TestEnum ReadonlyEnumAttribute(); + void SetEnumAttribute(TestEnum); + + // Callback types + void PassCallback(JSContext*, JSObject*); + void PassNullableCallback(JSContext*, JSObject*); + void PassOptionalCallback(JSContext*, const Optional<JSObject*>&); + void PassOptionalNullableCallback(JSContext*, const Optional<JSObject*>&); + void PassOptionalNullableCallbackWithDefaultValue(JSContext*, JSObject*); + JSObject* ReceiveCallback(JSContext*); + JSObject* ReceiveNullableCallback(JSContext*); + + // Any types + void PassAny(JSContext*, JS::Value); + void PassOptionalAny(JSContext*, const Optional<JS::Value>&); + void PassAnyDefaultNull(JSContext*, JS::Value); + JS::Value ReceiveAny(JSContext*); + + // object types + void PassObject(JSContext*, JSObject&); + void PassNullableObject(JSContext*, JSObject*); + void PassOptionalObject(JSContext*, const Optional<NonNull<JSObject> >&); + void PassOptionalNullableObject(JSContext*, const Optional<JSObject*>&); + void PassOptionalNullableObjectWithDefaultValue(JSContext*, JSObject*); + JSObject* ReceiveObject(JSContext*); + JSObject* ReceiveNullableObject(JSContext*); + + // Union types + void PassUnion(JSContext*, const ObjectOrLong& arg); + void PassUnionWithNullable(JSContext*, const ObjectOrNullOrLong& arg) + { + ObjectOrLong returnValue; + if (arg.IsNull()) { + } else if (arg.IsObject()) { + JSObject& obj = (JSObject&)arg.GetAsObject(); + JS_GetClass(&obj); + //returnValue.SetAsObject(&obj); + } else { + int32_t i = arg.GetAsLong(); + i += 1; + } + } + void PassNullableUnion(JSContext*, const Nullable<ObjectOrLong>&); + void PassOptionalUnion(JSContext*, const Optional<ObjectOrLong>&); + void PassOptionalNullableUnion(JSContext*, const Optional<Nullable<ObjectOrLong> >&); + void PassOptionalNullableUnionWithDefaultValue(JSContext*, const Nullable<ObjectOrLong>&); + //void PassUnionWithInterfaces(const TestInterfaceOrTestExternalInterface& arg); + //void PassUnionWithInterfacesAndNullable(const TestInterfaceOrNullOrTestExternalInterface& arg); + void PassUnionWithArrayBuffer(const ArrayBufferOrLong&); + void PassUnionWithString(JSContext*, const StringOrObject&); + //void PassUnionWithEnum(JSContext*, const TestEnumOrObject&); + void PassUnionWithCallback(JSContext*, const TestCallbackOrLong&); + void PassUnionWithObject(JSContext*, const ObjectOrLong&); + + // binaryNames tests + void MethodRenamedTo(); + void MethodRenamedTo(int8_t); + int8_t AttributeGetterRenamedTo(); + int8_t AttributeRenamedTo(); + void SetAttributeRenamedTo(int8_t); + + // Dictionary tests + void PassDictionary(const Dict&); + void PassOtherDictionary(const GrandparentDict&); + void PassSequenceOfDictionaries(const Sequence<Dict>&); + void PassDictionaryOrLong(const Dict&); + void PassDictionaryOrLong(int32_t); + void PassDictContainingDict(const DictContainingDict&); + void PassDictContainingSequence(const DictContainingSequence&); + + // Typedefs + void ExerciseTypedefInterfaces1(TestInterface&); + already_AddRefed<TestInterface> ExerciseTypedefInterfaces2(TestInterface*); + void ExerciseTypedefInterfaces3(TestInterface&); + + // Miscellania + int32_t AttrWithLenientThis(); + void SetAttrWithLenientThis(int32_t); + + // Methods and properties imported via "implements" + bool ImplementedProperty(); + void SetImplementedProperty(bool); + void ImplementedMethod(); + bool ImplementedParentProperty(); + void SetImplementedParentProperty(bool); + void ImplementedParentMethod(); + bool IndirectlyImplementedProperty(); + void SetIndirectlyImplementedProperty(bool); + void IndirectlyImplementedMethod(); + uint32_t DiamondImplementedProperty(); + + // Test EnforceRange/Clamp + void DontEnforceRangeOrClamp(int8_t); + void DoEnforceRange(int8_t); + void DoClamp(int8_t); + +private: + // We add signatures here that _could_ start matching if the codegen + // got data types wrong. That way if it ever does we'll have a call + // to these private deleted methods and compilation will fail. + void SetReadonlyByte(int8_t) MOZ_DELETE; + template<typename T> + void SetWritableByte(T) MOZ_DELETE; + template<typename T> + void PassByte(T) MOZ_DELETE; + template<typename T> + void PassOptionalByte(const Optional<T>&) MOZ_DELETE; + template<typename T> + void PassOptionalByteWithDefault(T) MOZ_DELETE; + + void SetReadonlyShort(int16_t) MOZ_DELETE; + template<typename T> + void SetWritableShort(T) MOZ_DELETE; + template<typename T> + void PassShort(T) MOZ_DELETE; + template<typename T> + void PassOptionalShort(const Optional<T>&) MOZ_DELETE; + template<typename T> + void PassOptionalShortWithDefault(T) MOZ_DELETE; + + void SetReadonlyLong(int32_t) MOZ_DELETE; + template<typename T> + void SetWritableLong(T) MOZ_DELETE; + template<typename T> + void PassLong(T) MOZ_DELETE; + template<typename T> + void PassOptionalLong(const Optional<T>&) MOZ_DELETE; + template<typename T> + void PassOptionalLongWithDefault(T) MOZ_DELETE; + + void SetReadonlyLongLong(int64_t) MOZ_DELETE; + template<typename T> + void SetWritableLongLong(T) MOZ_DELETE; + template<typename T> + void PassLongLong(T) MOZ_DELETE; + template<typename T> + void PassOptionalLongLong(const Optional<T>&) MOZ_DELETE; + template<typename T> + void PassOptionalLongLongWithDefault(T) MOZ_DELETE; + + void SetReadonlyOctet(uint8_t) MOZ_DELETE; + template<typename T> + void SetWritableOctet(T) MOZ_DELETE; + template<typename T> + void PassOctet(T) MOZ_DELETE; + template<typename T> + void PassOptionalOctet(const Optional<T>&) MOZ_DELETE; + template<typename T> + void PassOptionalOctetWithDefault(T) MOZ_DELETE; + + void SetReadonlyUnsignedShort(uint16_t) MOZ_DELETE; + template<typename T> + void SetWritableUnsignedShort(T) MOZ_DELETE; + template<typename T> + void PassUnsignedShort(T) MOZ_DELETE; + template<typename T> + void PassOptionalUnsignedShort(const Optional<T>&) MOZ_DELETE; + template<typename T> + void PassOptionalUnsignedShortWithDefault(T) MOZ_DELETE; + + void SetReadonlyUnsignedLong(uint32_t) MOZ_DELETE; + template<typename T> + void SetWritableUnsignedLong(T) MOZ_DELETE; + template<typename T> + void PassUnsignedLong(T) MOZ_DELETE; + template<typename T> + void PassOptionalUnsignedLong(const Optional<T>&) MOZ_DELETE; + template<typename T> + void PassOptionalUnsignedLongWithDefault(T) MOZ_DELETE; + + void SetReadonlyUnsignedLongLong(uint64_t) MOZ_DELETE; + template<typename T> + void SetWritableUnsignedLongLong(T) MOZ_DELETE; + template<typename T> + void PassUnsignedLongLong(T) MOZ_DELETE; + template<typename T> + void PassOptionalUnsignedLongLong(const Optional<T>&) MOZ_DELETE; + template<typename T> + void PassOptionalUnsignedLongLongWithDefault(T) MOZ_DELETE; + + // Enforce that only const things are passed for sequences + void PassSequence(Sequence<int32_t> &) MOZ_DELETE; + void PassNullableSequence(Nullable< Sequence<int32_t> >&) MOZ_DELETE; + void PassOptionalNullableSequenceWithDefaultValue(Nullable< Sequence<int32_t> >&) MOZ_DELETE; + + // Enforce that only const things are passed for optional + void PassOptionalByte(Optional<int8_t>&) MOZ_DELETE; + void PassOptionalNullableByte(Optional<Nullable<int8_t> >&) MOZ_DELETE; + void PassOptionalShort(Optional<int16_t>&) MOZ_DELETE; + void PassOptionalLong(Optional<int32_t>&) MOZ_DELETE; + void PassOptionalLongLong(Optional<int64_t>&) MOZ_DELETE; + void PassOptionalOctet(Optional<uint8_t>&) MOZ_DELETE; + void PassOptionalUnsignedShort(Optional<uint16_t>&) MOZ_DELETE; + void PassOptionalUnsignedLong(Optional<uint32_t>&) MOZ_DELETE; + void PassOptionalUnsignedLongLong(Optional<uint64_t>&) MOZ_DELETE; + void PassOptionalSelf(Optional<TestInterface*> &) MOZ_DELETE; + void PassOptionalNonNullSelf(Optional<NonNull<TestInterface> >&) MOZ_DELETE; + void PassOptionalOther(Optional<TestNonCastableInterface*>&); + void PassOptionalNonNullOther(Optional<NonNull<TestNonCastableInterface> >&); + void PassOptionalExternal(Optional<TestExternalInterface*>&) MOZ_DELETE; + void PassOptionalNonNullExternal(Optional<TestExternalInterface*>&) MOZ_DELETE; + void PassOptionalSequence(Optional<Sequence<int32_t> >&) MOZ_DELETE; + void PassOptionalNullableSequence(Optional<Nullable<Sequence<int32_t> > >&) MOZ_DELETE; + void PassOptionalObjectSequence(Optional<Sequence<OwningNonNull<TestInterface> > >&) MOZ_DELETE; + void PassOptionalArrayBuffer(Optional<ArrayBuffer>&) MOZ_DELETE; + void PassOptionalNullableArrayBuffer(Optional<ArrayBuffer*>&) MOZ_DELETE; + void PassOptionalEnum(Optional<TestEnum>&) MOZ_DELETE; + void PassOptionalCallback(JSContext*, Optional<JSObject*>&) MOZ_DELETE; + void PassOptionalNullableCallback(JSContext*, Optional<JSObject*>&) MOZ_DELETE; + void PassOptionalAny(Optional<JS::Value>&) MOZ_DELETE; + + // And test that string stuff is always const + void PassString(nsAString&) MOZ_DELETE; + void PassNullableString(nsAString&) MOZ_DELETE; + void PassOptionalString(Optional<nsAString>&) MOZ_DELETE; + void PassOptionalStringWithDefaultValue(nsAString&) MOZ_DELETE; + void PassOptionalNullableString(Optional<nsAString>&) MOZ_DELETE; + void PassOptionalNullableStringWithDefaultValue(nsAString&) MOZ_DELETE; + +}; + +class TestIndexedGetterInterface : public nsISupports, + public nsWrapperCache +{ +public: + NS_DECL_ISUPPORTS + + // We need a GetParentObject to make binding codegen happy + virtual nsISupports* GetParentObject(); + + uint32_t IndexedGetter(uint32_t, bool&); + uint32_t IndexedGetter(uint32_t&) MOZ_DELETE; + uint32_t Item(uint32_t&); + uint32_t Item(uint32_t, bool&) MOZ_DELETE; + uint32_t Length(); +}; + +class TestNamedGetterInterface : public nsISupports, + public nsWrapperCache +{ +public: + NS_DECL_ISUPPORTS + + // We need a GetParentObject to make binding codegen happy + virtual nsISupports* GetParentObject(); + + void NamedGetter(const nsAString&, bool&, nsAString&); +}; + +class TestIndexedAndNamedGetterInterface : public nsISupports, + public nsWrapperCache +{ +public: + NS_DECL_ISUPPORTS + + // We need a GetParentObject to make binding codegen happy + virtual nsISupports* GetParentObject(); + + uint32_t IndexedGetter(uint32_t, bool&); + void NamedGetter(const nsAString&, bool&, nsAString&); + void NamedItem(const nsAString&, nsAString&); + uint32_t Length(); +}; + +class TestIndexedSetterInterface : public nsISupports, + public nsWrapperCache +{ +public: + NS_DECL_ISUPPORTS + + // We need a GetParentObject to make binding codegen happy + virtual nsISupports* GetParentObject(); + + void IndexedSetter(uint32_t, const nsAString&); + void SetItem(uint32_t, const nsAString&); +}; + +class TestNamedSetterInterface : public nsISupports, + public nsWrapperCache +{ +public: + NS_DECL_ISUPPORTS + + // We need a GetParentObject to make binding codegen happy + virtual nsISupports* GetParentObject(); + + void NamedSetter(const nsAString&, TestIndexedSetterInterface&); +}; + +class TestIndexedAndNamedSetterInterface : public nsISupports, + public nsWrapperCache +{ +public: + NS_DECL_ISUPPORTS + + // We need a GetParentObject to make binding codegen happy + virtual nsISupports* GetParentObject(); + + void IndexedSetter(uint32_t, TestIndexedSetterInterface&); + void NamedSetter(const nsAString&, TestIndexedSetterInterface&); + void SetNamedItem(const nsAString&, TestIndexedSetterInterface&); +}; + +class TestIndexedAndNamedGetterAndSetterInterface : public TestIndexedSetterInterface +{ +public: + uint32_t IndexedGetter(uint32_t, bool&); + uint32_t Item(uint32_t); + void NamedGetter(const nsAString&, bool&, nsAString&); + void NamedItem(const nsAString&, nsAString&); + void IndexedSetter(uint32_t, int32_t&); + void IndexedSetter(uint32_t, const nsAString&) MOZ_DELETE; + void NamedSetter(const nsAString&, const nsAString&); + void Stringify(nsAString&); + uint32_t Length(); +}; + +} // namespace dom +} // namespace mozilla + +#endif /* TestBindingHeader_h */ diff --git a/components/script/dom/bindings/codegen/test/TestCodeGen.webidl b/components/script/dom/bindings/codegen/test/TestCodeGen.webidl new file mode 100644 index 00000000000..8c2b3c1b6b4 --- /dev/null +++ b/components/script/dom/bindings/codegen/test/TestCodeGen.webidl @@ -0,0 +1,442 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +typedef long myLong; +typedef TestInterface AnotherNameForTestInterface; +typedef TestInterface? NullableTestInterface; + +interface TestExternalInterface; + +interface TestNonCastableInterface { +}; + +callback interface TestCallbackInterface { + readonly attribute long foo; + void doSomething(); +}; + +enum TestEnum { + "a", + "b" +}; + +callback TestCallback = void(); + +TestInterface implements ImplementedInterface; + +// This interface is only for use in the constructor below +interface OnlyForUseInConstructor { +}; + +[Constructor, + Constructor(DOMString str), + Constructor(unsigned long num, boolean? bool), + Constructor(TestInterface? iface), + Constructor(TestNonCastableInterface iface) + // , Constructor(long arg1, long arg2, (TestInterface or OnlyForUseInConstructor) arg3) + ] +interface TestInterface { + // Integer types + // XXXbz add tests for throwing versions of all the integer stuff + readonly attribute byte readonlyByte; + attribute byte writableByte; + void passByte(byte arg); + byte receiveByte(); + void passOptionalByte(optional byte arg); + void passOptionalByteWithDefault(optional byte arg = 0); + void passNullableByte(byte? arg); + void passOptionalNullableByte(optional byte? arg); + + readonly attribute short readonlyShort; + attribute short writableShort; + void passShort(short arg); + short receiveShort(); + void passOptionalShort(optional short arg); + void passOptionalShortWithDefault(optional short arg = 5); + + readonly attribute long readonlyLong; + attribute long writableLong; + void passLong(long arg); + long receiveLong(); + void passOptionalLong(optional long arg); + void passOptionalLongWithDefault(optional long arg = 7); + + readonly attribute long long readonlyLongLong; + attribute long long writableLongLong; + void passLongLong(long long arg); + long long receiveLongLong(); + void passOptionalLongLong(optional long long arg); + void passOptionalLongLongWithDefault(optional long long arg = -12); + + readonly attribute octet readonlyOctet; + attribute octet writableOctet; + void passOctet(octet arg); + octet receiveOctet(); + void passOptionalOctet(optional octet arg); + void passOptionalOctetWithDefault(optional octet arg = 19); + + readonly attribute unsigned short readonlyUnsignedShort; + attribute unsigned short writableUnsignedShort; + void passUnsignedShort(unsigned short arg); + unsigned short receiveUnsignedShort(); + void passOptionalUnsignedShort(optional unsigned short arg); + void passOptionalUnsignedShortWithDefault(optional unsigned short arg = 2); + + readonly attribute unsigned long readonlyUnsignedLong; + attribute unsigned long writableUnsignedLong; + void passUnsignedLong(unsigned long arg); + unsigned long receiveUnsignedLong(); + void passOptionalUnsignedLong(optional unsigned long arg); + void passOptionalUnsignedLongWithDefault(optional unsigned long arg = 6); + + readonly attribute unsigned long long readonlyUnsignedLongLong; + attribute unsigned long long writableUnsignedLongLong; + void passUnsignedLongLong(unsigned long long arg); + unsigned long long receiveUnsignedLongLong(); + void passOptionalUnsignedLongLong(optional unsigned long long arg); + void passOptionalUnsignedLongLongWithDefault(optional unsigned long long arg = 17); + + // Castable interface types + // XXXbz add tests for throwing versions of all the castable interface stuff + TestInterface receiveSelf(); + TestInterface? receiveNullableSelf(); + TestInterface receiveWeakSelf(); + TestInterface? receiveWeakNullableSelf(); + // A verstion to test for casting to TestInterface& + void passSelf(TestInterface arg); + // A version we can use to test for the exact type passed in + void passSelf2(TestInterface arg); + void passNullableSelf(TestInterface? arg); + attribute TestInterface nonNullSelf; + attribute TestInterface? nullableSelf; + // Optional arguments + void passOptionalSelf(optional TestInterface? arg); + void passOptionalNonNullSelf(optional TestInterface arg); + void passOptionalSelfWithDefault(optional TestInterface? arg = null); + + // Non-wrapper-cache interface types + [Creator] + TestNonWrapperCacheInterface receiveNonWrapperCacheInterface(); + [Creator] + TestNonWrapperCacheInterface? receiveNullableNonWrapperCacheInterface(); + [Creator] + sequence<TestNonWrapperCacheInterface> receiveNonWrapperCacheInterfaceSequence(); + [Creator] + sequence<TestNonWrapperCacheInterface?> receiveNullableNonWrapperCacheInterfaceSequence(); + [Creator] + sequence<TestNonWrapperCacheInterface>? receiveNonWrapperCacheInterfaceNullableSequence(); + [Creator] + sequence<TestNonWrapperCacheInterface?>? receiveNullableNonWrapperCacheInterfaceNullableSequence(); + + // Non-castable interface types + TestNonCastableInterface receiveOther(); + TestNonCastableInterface? receiveNullableOther(); + TestNonCastableInterface receiveWeakOther(); + TestNonCastableInterface? receiveWeakNullableOther(); + // A verstion to test for casting to TestNonCastableInterface& + void passOther(TestNonCastableInterface arg); + // A version we can use to test for the exact type passed in + void passOther2(TestNonCastableInterface arg); + void passNullableOther(TestNonCastableInterface? arg); + attribute TestNonCastableInterface nonNullOther; + attribute TestNonCastableInterface? nullableOther; + // Optional arguments + void passOptionalOther(optional TestNonCastableInterface? arg); + void passOptionalNonNullOther(optional TestNonCastableInterface arg); + void passOptionalOtherWithDefault(optional TestNonCastableInterface? arg = null); + + // External interface types + TestExternalInterface receiveExternal(); + TestExternalInterface? receiveNullableExternal(); + TestExternalInterface receiveWeakExternal(); + TestExternalInterface? receiveWeakNullableExternal(); + // A verstion to test for casting to TestExternalInterface& + void passExternal(TestExternalInterface arg); + // A version we can use to test for the exact type passed in + void passExternal2(TestExternalInterface arg); + void passNullableExternal(TestExternalInterface? arg); + attribute TestExternalInterface nonNullExternal; + attribute TestExternalInterface? nullableExternal; + // Optional arguments + void passOptionalExternal(optional TestExternalInterface? arg); + void passOptionalNonNullExternal(optional TestExternalInterface arg); + void passOptionalExternalWithDefault(optional TestExternalInterface? arg = null); + + // Callback interface types + TestCallbackInterface receiveCallbackInterface(); + TestCallbackInterface? receiveNullableCallbackInterface(); + TestCallbackInterface receiveWeakCallbackInterface(); + TestCallbackInterface? receiveWeakNullableCallbackInterface(); + // A verstion to test for casting to TestCallbackInterface& + void passCallbackInterface(TestCallbackInterface arg); + // A version we can use to test for the exact type passed in + void passCallbackInterface2(TestCallbackInterface arg); + void passNullableCallbackInterface(TestCallbackInterface? arg); + attribute TestCallbackInterface nonNullCallbackInterface; + attribute TestCallbackInterface? nullableCallbackInterface; + // Optional arguments + void passOptionalCallbackInterface(optional TestCallbackInterface? arg); + void passOptionalNonNullCallbackInterface(optional TestCallbackInterface arg); + void passOptionalCallbackInterfaceWithDefault(optional TestCallbackInterface? arg = null); + + // Miscellaneous interface tests + IndirectlyImplementedInterface receiveConsequentialInterface(); + void passConsequentialInterface(IndirectlyImplementedInterface arg); + + // Sequence types + sequence<long> receiveSequence(); + sequence<long>? receiveNullableSequence(); + sequence<long?> receiveSequenceOfNullableInts(); + sequence<long?>? receiveNullableSequenceOfNullableInts(); + void passSequence(sequence<long> arg); + void passNullableSequence(sequence<long>? arg); + void passSequenceOfNullableInts(sequence<long?> arg); + void passOptionalSequenceOfNullableInts(optional sequence<long?> arg); + void passOptionalNullableSequenceOfNullableInts(optional sequence<long?>? arg); + sequence<TestInterface> receiveCastableObjectSequence(); + sequence<TestInterface?> receiveNullableCastableObjectSequence(); + sequence<TestInterface>? receiveCastableObjectNullableSequence(); + sequence<TestInterface?>? receiveNullableCastableObjectNullableSequence(); + sequence<TestInterface> receiveWeakCastableObjectSequence(); + sequence<TestInterface?> receiveWeakNullableCastableObjectSequence(); + sequence<TestInterface>? receiveWeakCastableObjectNullableSequence(); + sequence<TestInterface?>? receiveWeakNullableCastableObjectNullableSequence(); + void passCastableObjectSequence(sequence<TestInterface> arg); + void passNullableCastableObjectSequence(sequence<TestInterface?> arg); + void passCastableObjectNullableSequence(sequence<TestInterface>? arg); + void passNullableCastableObjectNullableSequence(sequence<TestInterface?>? arg); + void passOptionalSequence(optional sequence<long> arg); + void passOptionalNullableSequence(optional sequence<long>? arg); + void passOptionalNullableSequenceWithDefaultValue(optional sequence<long>? arg = null); + void passOptionalObjectSequence(optional sequence<TestInterface> arg); + + sequence<DOMString> receiveStringSequence(); + void passStringSequence(sequence<DOMString> arg); + + sequence<any> receiveAnySequence(); + sequence<any>? receiveNullableAnySequence(); + + // Typed array types + void passArrayBuffer(ArrayBuffer arg); + void passNullableArrayBuffer(ArrayBuffer? arg); + void passOptionalArrayBuffer(optional ArrayBuffer arg); + void passOptionalNullableArrayBuffer(optional ArrayBuffer? arg); + void passOptionalNullableArrayBufferWithDefaultValue(optional ArrayBuffer? arg= null); + void passArrayBufferView(ArrayBufferView arg); + void passInt8Array(Int8Array arg); + void passInt16Array(Int16Array arg); + void passInt32Array(Int32Array arg); + void passUint8Array(Uint8Array arg); + void passUint16Array(Uint16Array arg); + void passUint32Array(Uint32Array arg); + void passUint8ClampedArray(Uint8ClampedArray arg); + void passFloat32Array(Float32Array arg); + void passFloat64Array(Float64Array arg); + Uint8Array receiveUint8Array(); + + // String types + void passString(DOMString arg); + void passNullableString(DOMString? arg); + void passOptionalString(optional DOMString arg); + void passOptionalStringWithDefaultValue(optional DOMString arg = "abc"); + void passOptionalNullableString(optional DOMString? arg); + void passOptionalNullableStringWithDefaultValue(optional DOMString? arg = null); + + // Enumerated types + void passEnum(TestEnum arg); + // No support for nullable enums yet + // void passNullableEnum(TestEnum? arg); + void passOptionalEnum(optional TestEnum arg); + void passEnumWithDefault(optional TestEnum arg = "a"); + // void passOptionalNullableEnum(optional TestEnum? arg); + // void passOptionalNullableEnumWithDefaultValue(optional TestEnum? arg = null); + TestEnum receiveEnum(); + attribute TestEnum enumAttribute; + readonly attribute TestEnum readonlyEnumAttribute; + + // Callback types + void passCallback(TestCallback arg); + void passNullableCallback(TestCallback? arg); + void passOptionalCallback(optional TestCallback arg); + void passOptionalNullableCallback(optional TestCallback? arg); + void passOptionalNullableCallbackWithDefaultValue(optional TestCallback? arg = null); + TestCallback receiveCallback(); + TestCallback? receiveNullableCallback(); + + // Any types + void passAny(any arg); + void passOptionalAny(optional any arg); + void passAnyDefaultNull(optional any arg = null); + any receiveAny(); + + // object types + void passObject(object arg); + void passNullableObject(object? arg); + void passOptionalObject(optional object arg); + void passOptionalNullableObject(optional object? arg); + void passOptionalNullableObjectWithDefaultValue(optional object? arg = null); + object receiveObject(); + object? receiveNullableObject(); + + // Union types + void passUnion((object or long) arg); + void passUnionWithNullable((object? or long) arg); + void passNullableUnion((object or long)? arg); + void passOptionalUnion(optional (object or long) arg); + void passOptionalNullableUnion(optional (object or long)? arg); + void passOptionalNullableUnionWithDefaultValue(optional (object or long)? arg = null); + //void passUnionWithInterfaces((TestInterface or TestExternalInterface) arg); + //void passUnionWithInterfacesAndNullable((TestInterface? or TestExternalInterface) arg); + //void passUnionWithSequence((sequence<object> or long) arg); + void passUnionWithArrayBuffer((ArrayBuffer or long) arg); + void passUnionWithString((DOMString or object) arg); + //void passUnionWithEnum((TestEnum or object) arg); + void passUnionWithCallback((TestCallback or long) arg); + void passUnionWithObject((object or long) arg); + //void passUnionWithDict((Dict or long) arg); + + // binaryNames tests + void methodRenamedFrom(); + void methodRenamedFrom(byte argument); + readonly attribute byte attributeGetterRenamedFrom; + attribute byte attributeRenamedFrom; + + void passDictionary(optional Dict x); + void passOtherDictionary(optional GrandparentDict x); + void passSequenceOfDictionaries(sequence<Dict> x); + void passDictionaryOrLong(optional Dict x); + void passDictionaryOrLong(long x); + + void passDictContainingDict(optional DictContainingDict arg); + void passDictContainingSequence(optional DictContainingSequence arg); + + // EnforceRange/Clamp tests + void dontEnforceRangeOrClamp(byte arg); + void doEnforceRange([EnforceRange] byte arg); + void doClamp([Clamp] byte arg); + + // Typedefs + const myLong myLongConstant = 5; + void exerciseTypedefInterfaces1(AnotherNameForTestInterface arg); + AnotherNameForTestInterface exerciseTypedefInterfaces2(NullableTestInterface arg); + void exerciseTypedefInterfaces3(YetAnotherNameForTestInterface arg); + + // Miscellania + [LenientThis] attribute long attrWithLenientThis; +}; + +interface TestNonWrapperCacheInterface { +}; + +interface ImplementedInterfaceParent { + void implementedParentMethod(); + attribute boolean implementedParentProperty; + + const long implementedParentConstant = 8; +}; + +ImplementedInterfaceParent implements IndirectlyImplementedInterface; + +[NoInterfaceObject] +interface IndirectlyImplementedInterface { + void indirectlyImplementedMethod(); + attribute boolean indirectlyImplementedProperty; + + const long indirectlyImplementedConstant = 9; +}; + +interface ImplementedInterface : ImplementedInterfaceParent { + void implementedMethod(); + attribute boolean implementedProperty; + + const long implementedConstant = 5; +}; + +interface DiamondImplements { + readonly attribute long diamondImplementedProperty; +}; +interface DiamondBranch1A { +}; +interface DiamondBranch1B { +}; +interface DiamondBranch2A : DiamondImplements { +}; +interface DiamondBranch2B : DiamondImplements { +}; +TestInterface implements DiamondBranch1A; +TestInterface implements DiamondBranch1B; +TestInterface implements DiamondBranch2A; +TestInterface implements DiamondBranch2B; +DiamondBranch1A implements DiamondImplements; +DiamondBranch1B implements DiamondImplements; + +dictionary Dict : ParentDict { + TestEnum someEnum; + long x; + long a; + long b = 8; + long z = 9; + DOMString str; + DOMString empty = ""; + TestEnum otherEnum = "b"; + DOMString otherStr = "def"; + DOMString? yetAnotherStr = null; +}; + +dictionary ParentDict : GrandparentDict { + long c = 5; + TestInterface someInterface; + TestExternalInterface someExternalInterface; +}; + +dictionary DictContainingDict { + Dict memberDict; +}; + +dictionary DictContainingSequence { + sequence<long> ourSequence; +}; + +interface TestIndexedGetterInterface { + getter long item(unsigned long index); + [Infallible] + readonly attribute unsigned long length; +}; + +interface TestNamedGetterInterface { + getter DOMString (DOMString name); +}; + +interface TestIndexedAndNamedGetterInterface { + getter long (unsigned long index); + getter DOMString namedItem(DOMString name); + [Infallible] + readonly attribute unsigned long length; +}; + +interface TestIndexedSetterInterface { + setter creator void setItem(unsigned long index, DOMString item); +}; + +interface TestNamedSetterInterface { + setter creator void (DOMString name, TestIndexedSetterInterface item); +}; + +interface TestIndexedAndNamedSetterInterface { + setter creator void (unsigned long index, TestIndexedSetterInterface item); + setter creator void setNamedItem(DOMString name, TestIndexedSetterInterface item); +}; + +interface TestIndexedAndNamedGetterAndSetterInterface : TestIndexedSetterInterface { + getter long item(unsigned long index); + getter DOMString namedItem(DOMString name); + setter creator void (unsigned long index, long item); + setter creator void (DOMString name, DOMString item); + [Infallible] + stringifier DOMString (); + [Infallible] + readonly attribute unsigned long length; +}; diff --git a/components/script/dom/bindings/codegen/test/TestDictionary.webidl b/components/script/dom/bindings/codegen/test/TestDictionary.webidl new file mode 100644 index 00000000000..3dd91bd6500 --- /dev/null +++ b/components/script/dom/bindings/codegen/test/TestDictionary.webidl @@ -0,0 +1,9 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +dictionary GrandparentDict { + double someNum; +};
\ No newline at end of file diff --git a/components/script/dom/bindings/codegen/test/TestTypedef.webidl b/components/script/dom/bindings/codegen/test/TestTypedef.webidl new file mode 100644 index 00000000000..7f758c79e8f --- /dev/null +++ b/components/script/dom/bindings/codegen/test/TestTypedef.webidl @@ -0,0 +1,7 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +typedef TestInterface YetAnotherNameForTestInterface; diff --git a/components/script/dom/bindings/codegen/test/file_bug775543.html b/components/script/dom/bindings/codegen/test/file_bug775543.html new file mode 100644 index 00000000000..ee8c14c4d9c --- /dev/null +++ b/components/script/dom/bindings/codegen/test/file_bug775543.html @@ -0,0 +1,5 @@ +<body> +<script> +worker = new Worker("a"); +</script> +</body> diff --git a/components/script/dom/bindings/codegen/test/forOf_iframe.html b/components/script/dom/bindings/codegen/test/forOf_iframe.html new file mode 100644 index 00000000000..91417aba0e8 --- /dev/null +++ b/components/script/dom/bindings/codegen/test/forOf_iframe.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>iframe content for test_forOf_iframe.html</title> +</head> +<body> + <div id="basket"> + <span id="egg0"></span> + <span id="egg1"><span id="duckling1"></span></span> + <span id="egg2"></span> + </div> +</body> +</html> diff --git a/components/script/dom/bindings/codegen/test/test_InstanceOf.html b/components/script/dom/bindings/codegen/test/test_InstanceOf.html new file mode 100644 index 00000000000..3a5a76b1b21 --- /dev/null +++ b/components/script/dom/bindings/codegen/test/test_InstanceOf.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=748983 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 748983</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=748983">Mozilla Bug 748983</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 748983 **/ +ok(document instanceof EventTarget, "document is an event target") +ok(new XMLHttpRequest() instanceof XMLHttpRequest, "instanceof should work on XHR"); + +</script> +</pre> +</body> +</html> diff --git a/components/script/dom/bindings/codegen/test/test_bug773326.html b/components/script/dom/bindings/codegen/test/test_bug773326.html new file mode 100644 index 00000000000..2e3b1ea304d --- /dev/null +++ b/components/script/dom/bindings/codegen/test/test_bug773326.html @@ -0,0 +1,11 @@ +<!doctype html> +<meta charset=utf-8> +<title>Test for Bug 773326</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id=log></div> +<script> +test(function() { + new Worker("data:text/javascript,new XMLHttpRequest(42)"); +}, "Should not crash") +</script> diff --git a/components/script/dom/bindings/codegen/test/test_bug775543.html b/components/script/dom/bindings/codegen/test/test_bug775543.html new file mode 100644 index 00000000000..d8df05f630f --- /dev/null +++ b/components/script/dom/bindings/codegen/test/test_bug775543.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=775543 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 775543</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=775543">Mozilla Bug 775543</a> +<p id="display"></p> +<div id="content" style="display: none"> +<iframe id="t" src="http://example.org/tests/dom/bindings/test/file_bug775543.html" onload="test();"></iframe> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 775543 **/ + +function test() +{ + var a = XPCNativeWrapper(document.getElementById("t").contentWindow.wrappedJSObject.worker); + isnot(XPCNativeWrapper.unwrap(a), a, "XPCNativeWrapper(Worker) should be an Xray wrapper"); + a.toString(); + ok(true, "Shouldn't crash when calling a method on an Xray wrapper around a worker"); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/components/script/dom/bindings/codegen/test/test_bug788369.html b/components/script/dom/bindings/codegen/test/test_bug788369.html new file mode 100644 index 00000000000..787bd28fe34 --- /dev/null +++ b/components/script/dom/bindings/codegen/test/test_bug788369.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=788369 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 788369</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=788369">Mozilla Bug 788369</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 788369 **/ +try { + var xhr = new(window.ActiveXObject || XMLHttpRequest)("Microsoft.XMLHTTP"); + ok(xhr instanceof XMLHttpRequest, "Should have an XHR object"); +} catch (e) { + ok(false, "Should not throw exception when constructing: " + e); +} +</script> +</pre> +</body> +</html> diff --git a/components/script/dom/bindings/codegen/test/test_enums.html b/components/script/dom/bindings/codegen/test/test_enums.html new file mode 100644 index 00000000000..e5dc519a0c9 --- /dev/null +++ b/components/script/dom/bindings/codegen/test/test_enums.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<title>Enums</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id=log></div> +<script> +test(function() { + var xhr = new XMLHttpRequest(); + xhr.open("get", "foo") + assert_equals(xhr.responseType, ""); + xhr.responseType = "foo"; + assert_equals(xhr.responseType, ""); +}, "Assigning an invalid value to an enum attribute should not throw."); +</script> diff --git a/components/script/dom/bindings/codegen/test/test_forOf.html b/components/script/dom/bindings/codegen/test/test_forOf.html new file mode 100644 index 00000000000..b1a3032a385 --- /dev/null +++ b/components/script/dom/bindings/codegen/test/test_forOf.html @@ -0,0 +1,94 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=725907 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 725907</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=725907">Mozilla Bug 725907</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<div id="basket"> + <span id="egg0"></span> + <span id="egg1"><span id="duckling1"></span></span> + <span id="egg2"></span> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 725907 **/ + +function runTestsForDocument(document, msgSuffix) { + function is(a, b, msg) { SimpleTest.is(a, b, msg + msgSuffix); } + function isnot(a, b, msg) { SimpleTest.isnot(a, b, msg + msgSuffix); } + + var basket = document.getElementById("basket"); + var egg3 = document.createElement("span"); + egg3.id = "egg3"; + + var log = ''; + for (var x of basket.childNodes) { + if (x.nodeType != x.TEXT_NODE) + log += x.id + ";"; + } + is(log, "egg0;egg1;egg2;", "'for (x of div.childNodes)' should iterate over child nodes"); + + log = ''; + for (var x of basket.childNodes) { + if (x.nodeType != x.TEXT_NODE) { + log += x.id + ";"; + if (x.id == "egg1") + basket.appendChild(egg3); + } + } + is(log, "egg0;egg1;egg2;egg3;", "'for (x of div.childNodes)' should see elements added during iteration"); + + var iter1 = basket.childNodes.iterator(); + var iter2 = basket.childNodes.iterator(); + isnot(iter1, iter2, "nodelist.iterator() returns a new iterator each time"); + + log = ''; + basket.appendChild(document.createTextNode("some text")); + for (var x of basket.children) + log += x.id + ";"; + is(log, "egg0;egg1;egg2;egg3;", "'for (x of div.children)' should iterate over child elements"); + + var iter1 = basket.children.iterator(); + var iter2 = basket.children.iterator(); + isnot(iter1, iter2, ".iterator() returns a new iterator each time"); + + var count = 0; + for (var x of document.getElementsByClassName("hazardous-materials")) + count++; + is(count, 0, "'for (x of emptyNodeList)' loop should run zero times"); + + var log = ''; + for (var x of document.querySelectorAll("span")) + log += x.id + ";"; + is(log, "egg0;egg1;duckling1;egg2;egg3;", "for-of loop should work with a querySelectorAll() NodeList"); +} + +/* All the tests run twice. First, in this document, so without any wrappers. */ +runTestsForDocument(document, ""); + +/* And once using the document of an iframe, so working with cross-compartment wrappers. */ +SimpleTest.waitForExplicitFinish(); +function iframeLoaded(iframe) { + runTestsForDocument(iframe.contentWindow.document, " (in iframe)"); + SimpleTest.finish(); +} + +</script> + +<iframe src="forOf_iframe.html" onload="iframeLoaded(this)"></iframe> + +</pre> +</body> +</html> diff --git a/components/script/dom/bindings/codegen/test/test_integers.html b/components/script/dom/bindings/codegen/test/test_integers.html new file mode 100644 index 00000000000..6799fd791a8 --- /dev/null +++ b/components/script/dom/bindings/codegen/test/test_integers.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + <canvas id="c" width="1" height="1"></canvas> +</div> +<pre id="test"> +<script type="application/javascript"> + + function testInt64NonFinite(arg) { + // We can use a WebGLRenderingContext to test conversion to 64-bit signed + // ints edge cases. + try { + var gl = $("c").getContext("experimental-webgl"); + } catch (ex) { + // No WebGL support on MacOS 10.5. Just skip this test + todo(false, "WebGL not supported"); + return; + } + is(gl.getError(), 0, "Should not start in an error state"); + + var b = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, b); + + var a = new Float32Array(1); + gl.bufferData(gl.ARRAY_BUFFER, a, gl.STATIC_DRAW); + + gl.bufferSubData(gl.ARRAY_BUFFER, arg, a); + + is(gl.getError(), 0, "Should have treated non-finite double as 0"); + } + + testInt64NonFinite(NaN); + testInt64NonFinite(Infinity); + testInt64NonFinite(-Infinity); +</script> +</pre> +</body> +</html> diff --git a/components/script/dom/bindings/codegen/test/test_interfaceToString.html b/components/script/dom/bindings/codegen/test/test_interfaceToString.html new file mode 100644 index 00000000000..cf670bf2d54 --- /dev/null +++ b/components/script/dom/bindings/codegen/test/test_interfaceToString.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=742156 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 742156</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=742156">Mozilla Bug 742156</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 742156 **/ + +var nativeToString = ("" + String.replace).replace("replace", "EventTarget"); +try { + var eventTargetToString = "" + EventTarget; + is(eventTargetToString, nativeToString, + "Stringifying a DOM interface object should return the same string" + + "as stringifying a native function."); +} +catch (e) { + ok(false, "Stringifying a DOM interface object shouldn't throw."); +} + + +</script> +</pre> +</body> +</html> diff --git a/components/script/dom/bindings/codegen/test/test_lookupGetter.html b/components/script/dom/bindings/codegen/test/test_lookupGetter.html new file mode 100644 index 00000000000..306ee4f643c --- /dev/null +++ b/components/script/dom/bindings/codegen/test/test_lookupGetter.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=462428 +--> +<head> + <title>Test for Bug 462428</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=462428">Mozilla Bug 462428</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 462428 **/ +var x = new XMLHttpRequest; +x.open("GET", ""); +var getter = x.__lookupGetter__('readyState'); +ok(getter !== undefined, "But able to look it up the normal way"); +ok(!x.hasOwnProperty('readyState'), "property should still be on the prototype"); + +var sawProp = false; +for (var i in x) { + if (i === "readyState") { + sawProp = true; + } +} + +ok(sawProp, "property should be enumerable"); + +is(getter.call(x), 1, "the getter actually works"); + +Object.getPrototypeOf(x).__defineSetter__('readyState', function() {}); +is(getter.call(x), 1, "the getter works after defineSetter"); + +is(x.responseType, "", "Should have correct responseType up front"); +var setter = x.__lookupSetter__('responseType'); +setter.call(x, "document"); +is(x.responseType, "document", "the setter is bound correctly"); + +</script> +</pre> +</body> +</html> diff --git a/components/script/dom/bindings/codegen/test/test_sequence_wrapping.html b/components/script/dom/bindings/codegen/test/test_sequence_wrapping.html new file mode 100644 index 00000000000..e4f18f9986c --- /dev/null +++ b/components/script/dom/bindings/codegen/test/test_sequence_wrapping.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=775852 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 775852</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=775852">Mozilla Bug 775852</a> +<p id="display"></p> +<div id="content" style="display: none"> + <canvas width="1" height="1" id="c"></canvas> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 775852 **/ +function doTest() { + try { + var gl = $("c").getContext("experimental-webgl"); + } catch (e) { + // No WebGL support on MacOS 10.5. Just skip this test + todo(false, "WebGL not supported"); + return; + } + var setterCalled = false; + + extLength = gl.getSupportedExtensions().length; + ok(extLength > 0, + "This test won't work right if we have no supported extensions"); + + Object.defineProperty(Array.prototype, "0", + { + set: function(val) { + setterCalled = true; + } + }); + + // Test that our property got defined correctly + var arr = [] + arr[0] = 5; + is(setterCalled, true, "Setter should be called when setting prop on array"); + + setterCalled = false; + + is(gl.getSupportedExtensions().length, extLength, + "We should still have the same number of extensions"); + + is(setterCalled, false, + "Setter should not be called when getting supported extensions"); +} +doTest(); +</script> +</pre> +</body> +</html> diff --git a/components/script/dom/bindings/codegen/test/test_traceProtos.html b/components/script/dom/bindings/codegen/test/test_traceProtos.html new file mode 100644 index 00000000000..195876744d6 --- /dev/null +++ b/components/script/dom/bindings/codegen/test/test_traceProtos.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=744772 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 744772</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=744772">Mozilla Bug 744772</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 744772 **/ + +SimpleTest.waitForExplicitFinish(); + +function callback() { + new XMLHttpRequest().upload; + ok(true, "Accessing unreferenced DOM interface objects shouldn't crash"); + SimpleTest.finish(); +} + +delete window.XMLHttpRequestUpload; +SpecialPowers.exactGC(window, callback); + +</script> +</pre> +</body> +</html> |