michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/dom/HTMLAllCollection.h" michael@0: michael@0: #include "jsapi.h" michael@0: #include "mozilla/HoldDropJSObjects.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsDOMClassInfo.h" michael@0: #include "nsHTMLDocument.h" michael@0: #include "nsJSUtils.h" michael@0: #include "nsWrapperCacheInlines.h" michael@0: #include "xpcpublic.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: class nsHTMLDocumentSH michael@0: { michael@0: public: michael@0: static bool DocumentAllGetProperty(JSContext *cx, JS::Handle obj, JS::Handle id, michael@0: JS::MutableHandle vp); michael@0: static bool DocumentAllNewResolve(JSContext *cx, JS::Handle obj, JS::Handle id, michael@0: JS::MutableHandle objp); michael@0: static void ReleaseDocument(JSFreeOp *fop, JSObject *obj); michael@0: static bool CallToGetPropMapper(JSContext *cx, unsigned argc, JS::Value *vp); michael@0: }; michael@0: michael@0: const JSClass sHTMLDocumentAllClass = { michael@0: "HTML document.all class", michael@0: JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS | JSCLASS_NEW_RESOLVE | michael@0: JSCLASS_EMULATES_UNDEFINED, michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: nsHTMLDocumentSH::DocumentAllGetProperty, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: (JSResolveOp)nsHTMLDocumentSH::DocumentAllNewResolve, michael@0: JS_ConvertStub, michael@0: nsHTMLDocumentSH::ReleaseDocument, michael@0: nsHTMLDocumentSH::CallToGetPropMapper michael@0: }; michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: HTMLAllCollection::HTMLAllCollection(nsHTMLDocument* aDocument) michael@0: : mDocument(aDocument) michael@0: { michael@0: MOZ_ASSERT(mDocument); michael@0: mozilla::HoldJSObjects(this); michael@0: } michael@0: michael@0: HTMLAllCollection::~HTMLAllCollection() michael@0: { michael@0: mObject = nullptr; michael@0: mozilla::DropJSObjects(this); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(HTMLAllCollection, AddRef) michael@0: NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(HTMLAllCollection, Release) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLAllCollection) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(HTMLAllCollection) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCollection) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNamedMap) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HTMLAllCollection) michael@0: tmp->mObject = nullptr; michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mCollection) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mNamedMap) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(HTMLAllCollection) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mObject) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_END michael@0: michael@0: uint32_t michael@0: HTMLAllCollection::Length() michael@0: { michael@0: return Collection()->Length(true); michael@0: } michael@0: michael@0: nsIContent* michael@0: HTMLAllCollection::Item(uint32_t aIndex) michael@0: { michael@0: return Collection()->Item(aIndex); michael@0: } michael@0: michael@0: JSObject* michael@0: HTMLAllCollection::GetObject(JSContext* aCx, ErrorResult& aRv) michael@0: { michael@0: MOZ_ASSERT(aCx); michael@0: michael@0: if (!mObject) { michael@0: JS::Rooted wrapper(aCx, mDocument->GetWrapper()); michael@0: MOZ_ASSERT(wrapper); michael@0: michael@0: JSAutoCompartment ac(aCx, wrapper); michael@0: JS::Rooted global(aCx, JS_GetGlobalForObject(aCx, wrapper)); michael@0: mObject = JS_NewObject(aCx, &sHTMLDocumentAllClass, JS::NullPtr(), global); michael@0: if (!mObject) { michael@0: aRv.Throw(NS_ERROR_OUT_OF_MEMORY); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Make the JSObject hold a reference to the document. michael@0: JS_SetPrivate(mObject, ToSupports(mDocument)); michael@0: NS_ADDREF(mDocument); michael@0: } michael@0: michael@0: JS::ExposeObjectToActiveJS(mObject); michael@0: return mObject; michael@0: } michael@0: michael@0: nsContentList* michael@0: HTMLAllCollection::Collection() michael@0: { michael@0: if (!mCollection) { michael@0: nsIDocument* document = mDocument; michael@0: mCollection = document->GetElementsByTagName(NS_LITERAL_STRING("*")); michael@0: MOZ_ASSERT(mCollection); michael@0: } michael@0: return mCollection; michael@0: } michael@0: michael@0: static bool michael@0: DocAllResultMatch(nsIContent* aContent, int32_t aNamespaceID, nsIAtom* aAtom, michael@0: void* aData) michael@0: { michael@0: if (aContent->GetID() == aAtom) { michael@0: return true; michael@0: } michael@0: michael@0: nsGenericHTMLElement* elm = nsGenericHTMLElement::FromContent(aContent); michael@0: if (!elm) { michael@0: return false; michael@0: } michael@0: michael@0: nsIAtom* tag = elm->Tag(); michael@0: if (tag != nsGkAtoms::a && michael@0: tag != nsGkAtoms::applet && michael@0: tag != nsGkAtoms::button && michael@0: tag != nsGkAtoms::embed && michael@0: tag != nsGkAtoms::form && michael@0: tag != nsGkAtoms::iframe && michael@0: tag != nsGkAtoms::img && michael@0: tag != nsGkAtoms::input && michael@0: tag != nsGkAtoms::map && michael@0: tag != nsGkAtoms::meta && michael@0: tag != nsGkAtoms::object && michael@0: tag != nsGkAtoms::select && michael@0: tag != nsGkAtoms::textarea) { michael@0: return false; michael@0: } michael@0: michael@0: const nsAttrValue* val = elm->GetParsedAttr(nsGkAtoms::name); michael@0: return val && val->Type() == nsAttrValue::eAtom && michael@0: val->GetAtomValue() == aAtom; michael@0: } michael@0: michael@0: nsContentList* michael@0: HTMLAllCollection::GetDocumentAllList(const nsAString& aID) michael@0: { michael@0: if (nsContentList* docAllList = mNamedMap.GetWeak(aID)) { michael@0: return docAllList; michael@0: } michael@0: michael@0: Element* root = mDocument->GetRootElement(); michael@0: if (!root) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr id = do_GetAtom(aID); michael@0: nsRefPtr docAllList = michael@0: new nsContentList(root, DocAllResultMatch, nullptr, nullptr, true, id); michael@0: mNamedMap.Put(aID, docAllList); michael@0: return docAllList; michael@0: } michael@0: michael@0: nsISupports* michael@0: HTMLAllCollection::GetNamedItem(const nsAString& aID, michael@0: nsWrapperCache** aCache) michael@0: { michael@0: nsContentList* docAllList = GetDocumentAllList(aID); michael@0: if (!docAllList) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // Check if there are more than 1 entries. Do this by getting the second one michael@0: // rather than the length since getting the length always requires walking michael@0: // the entire document. michael@0: michael@0: nsIContent* cont = docAllList->Item(1, true); michael@0: if (cont) { michael@0: *aCache = docAllList; michael@0: return static_cast(docAllList); michael@0: } michael@0: michael@0: // There's only 0 or 1 items. Return the first one or null. michael@0: *aCache = cont = docAllList->Item(0, true); michael@0: return cont; michael@0: } michael@0: michael@0: } // namespace dom michael@0: } // namespace mozilla michael@0: michael@0: static nsHTMLDocument* michael@0: GetDocument(JSObject *obj) michael@0: { michael@0: MOZ_ASSERT(js::GetObjectJSClass(obj) == &sHTMLDocumentAllClass); michael@0: return static_cast( michael@0: static_cast(JS_GetPrivate(obj))); michael@0: } michael@0: michael@0: bool michael@0: nsHTMLDocumentSH::DocumentAllGetProperty(JSContext *cx, JS::Handle obj_, michael@0: JS::Handle id, JS::MutableHandle vp) michael@0: { michael@0: JS::Rooted obj(cx, obj_); michael@0: michael@0: // document.all.item and .namedItem get their value in the michael@0: // newResolve hook, so nothing to do for those properties here. And michael@0: // we need to return early to prevent
from shadowing michael@0: // document.all.item(), etc. michael@0: if (nsDOMClassInfo::sItem_id == id || nsDOMClassInfo::sNamedItem_id == id) { michael@0: return true; michael@0: } michael@0: michael@0: JS::Rooted proto(cx); michael@0: while (js::GetObjectJSClass(obj) != &sHTMLDocumentAllClass) { michael@0: if (!js::GetObjectProto(cx, obj, &proto)) { michael@0: return false; michael@0: } michael@0: michael@0: if (!proto) { michael@0: NS_ERROR("The JS engine lies!"); michael@0: return true; michael@0: } michael@0: michael@0: obj = proto; michael@0: } michael@0: michael@0: HTMLAllCollection* allCollection = GetDocument(obj)->All(); michael@0: nsISupports *result; michael@0: nsWrapperCache *cache; michael@0: michael@0: if (JSID_IS_STRING(id)) { michael@0: if (nsDOMClassInfo::sLength_id == id) { michael@0: // Make sure
doesn't shadow document.all.length. michael@0: vp.setNumber(allCollection->Length()); michael@0: return true; michael@0: } michael@0: michael@0: // For all other strings, look for an element by id or name. michael@0: nsDependentJSString str(id); michael@0: result = allCollection->GetNamedItem(str, &cache); michael@0: } else if (JSID_IS_INT(id) && JSID_TO_INT(id) >= 0) { michael@0: // Map document.all[n] (where n is a number) to the n:th item in michael@0: // the document.all node list. michael@0: michael@0: nsIContent* node = allCollection->Item(SafeCast(JSID_TO_INT(id))); michael@0: michael@0: result = node; michael@0: cache = node; michael@0: } else { michael@0: result = nullptr; michael@0: } michael@0: michael@0: if (result) { michael@0: nsresult rv = nsContentUtils::WrapNative(cx, result, cache, vp); michael@0: if (NS_FAILED(rv)) { michael@0: xpc::Throw(cx, rv); michael@0: michael@0: return false; michael@0: } michael@0: } else { michael@0: vp.setUndefined(); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsHTMLDocumentSH::DocumentAllNewResolve(JSContext *cx, JS::Handle obj, michael@0: JS::Handle id, michael@0: JS::MutableHandle objp) michael@0: { michael@0: JS::Rooted v(cx); michael@0: michael@0: if (nsDOMClassInfo::sItem_id == id || nsDOMClassInfo::sNamedItem_id == id) { michael@0: // Define the item() or namedItem() method. michael@0: michael@0: JSFunction *fnc = ::JS_DefineFunctionById(cx, obj, id, CallToGetPropMapper, michael@0: 0, JSPROP_ENUMERATE); michael@0: objp.set(obj); michael@0: michael@0: return fnc != nullptr; michael@0: } michael@0: michael@0: if (nsDOMClassInfo::sLength_id == id) { michael@0: // document.all.length. Any jsval other than undefined would do michael@0: // here, all we need is to get into the code below that defines michael@0: // this propery on obj, the rest happens in michael@0: // DocumentAllGetProperty(). michael@0: michael@0: v = JSVAL_ONE; michael@0: } else { michael@0: if (!DocumentAllGetProperty(cx, obj, id, &v)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: bool ok = true; michael@0: michael@0: if (v.get() != JSVAL_VOID) { michael@0: ok = ::JS_DefinePropertyById(cx, obj, id, v, nullptr, nullptr, 0); michael@0: objp.set(obj); michael@0: } michael@0: michael@0: return ok; michael@0: } michael@0: michael@0: void michael@0: nsHTMLDocumentSH::ReleaseDocument(JSFreeOp *fop, JSObject *obj) michael@0: { michael@0: nsIHTMLDocument* doc = GetDocument(obj); michael@0: if (doc) { michael@0: nsContentUtils::DeferredFinalize(doc); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsHTMLDocumentSH::CallToGetPropMapper(JSContext *cx, unsigned argc, jsval *vp) michael@0: { michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: // Handle document.all("foo") style access to document.all. michael@0: michael@0: if (args.length() != 1) { michael@0: // XXX: Should throw NS_ERROR_XPC_NOT_ENOUGH_ARGS for argc < 1, michael@0: // and create a new NS_ERROR_XPC_TOO_MANY_ARGS for argc > 1? IE michael@0: // accepts nothing other than one arg. michael@0: xpc::Throw(cx, NS_ERROR_INVALID_ARG); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // Convert all types to string. michael@0: JS::Rooted str(cx, JS::ToString(cx, args[0])); michael@0: if (!str) { michael@0: return false; michael@0: } michael@0: michael@0: // If we are called via document.all(id) instead of document.all.item(i) or michael@0: // another method, use the document.all callee object as self. michael@0: JS::Rooted self(cx); michael@0: if (args.calleev().isObject() && michael@0: JS_GetClass(&args.calleev().toObject()) == &sHTMLDocumentAllClass) { michael@0: self = &args.calleev().toObject(); michael@0: } else { michael@0: self = JS_THIS_OBJECT(cx, vp); michael@0: if (!self) michael@0: return false; michael@0: } michael@0: michael@0: size_t length; michael@0: JS::Anchor anchor(str); michael@0: const jschar *chars = ::JS_GetStringCharsAndLength(cx, str, &length); michael@0: if (!chars) { michael@0: return false; michael@0: } michael@0: michael@0: return ::JS_GetUCProperty(cx, self, chars, length, args.rval()); michael@0: }