1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/html/document/src/HTMLAllCollection.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,382 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "mozilla/dom/HTMLAllCollection.h" 1.11 + 1.12 +#include "jsapi.h" 1.13 +#include "mozilla/HoldDropJSObjects.h" 1.14 +#include "nsContentUtils.h" 1.15 +#include "nsDOMClassInfo.h" 1.16 +#include "nsHTMLDocument.h" 1.17 +#include "nsJSUtils.h" 1.18 +#include "nsWrapperCacheInlines.h" 1.19 +#include "xpcpublic.h" 1.20 + 1.21 +using namespace mozilla; 1.22 +using namespace mozilla::dom; 1.23 + 1.24 +class nsHTMLDocumentSH 1.25 +{ 1.26 +public: 1.27 + static bool DocumentAllGetProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, 1.28 + JS::MutableHandle<JS::Value> vp); 1.29 + static bool DocumentAllNewResolve(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, 1.30 + JS::MutableHandle<JSObject*> objp); 1.31 + static void ReleaseDocument(JSFreeOp *fop, JSObject *obj); 1.32 + static bool CallToGetPropMapper(JSContext *cx, unsigned argc, JS::Value *vp); 1.33 +}; 1.34 + 1.35 +const JSClass sHTMLDocumentAllClass = { 1.36 + "HTML document.all class", 1.37 + JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS | JSCLASS_NEW_RESOLVE | 1.38 + JSCLASS_EMULATES_UNDEFINED, 1.39 + JS_PropertyStub, /* addProperty */ 1.40 + JS_DeletePropertyStub, /* delProperty */ 1.41 + nsHTMLDocumentSH::DocumentAllGetProperty, /* getProperty */ 1.42 + JS_StrictPropertyStub, /* setProperty */ 1.43 + JS_EnumerateStub, 1.44 + (JSResolveOp)nsHTMLDocumentSH::DocumentAllNewResolve, 1.45 + JS_ConvertStub, 1.46 + nsHTMLDocumentSH::ReleaseDocument, 1.47 + nsHTMLDocumentSH::CallToGetPropMapper 1.48 +}; 1.49 + 1.50 +namespace mozilla { 1.51 +namespace dom { 1.52 + 1.53 +HTMLAllCollection::HTMLAllCollection(nsHTMLDocument* aDocument) 1.54 + : mDocument(aDocument) 1.55 +{ 1.56 + MOZ_ASSERT(mDocument); 1.57 + mozilla::HoldJSObjects(this); 1.58 +} 1.59 + 1.60 +HTMLAllCollection::~HTMLAllCollection() 1.61 +{ 1.62 + mObject = nullptr; 1.63 + mozilla::DropJSObjects(this); 1.64 +} 1.65 + 1.66 +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(HTMLAllCollection, AddRef) 1.67 +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(HTMLAllCollection, Release) 1.68 + 1.69 +NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLAllCollection) 1.70 + 1.71 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(HTMLAllCollection) 1.72 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS 1.73 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument) 1.74 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCollection) 1.75 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNamedMap) 1.76 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 1.77 + 1.78 +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HTMLAllCollection) 1.79 + tmp->mObject = nullptr; 1.80 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument) 1.81 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCollection) 1.82 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mNamedMap) 1.83 +NS_IMPL_CYCLE_COLLECTION_UNLINK_END 1.84 + 1.85 +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(HTMLAllCollection) 1.86 + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mObject) 1.87 +NS_IMPL_CYCLE_COLLECTION_TRACE_END 1.88 + 1.89 +uint32_t 1.90 +HTMLAllCollection::Length() 1.91 +{ 1.92 + return Collection()->Length(true); 1.93 +} 1.94 + 1.95 +nsIContent* 1.96 +HTMLAllCollection::Item(uint32_t aIndex) 1.97 +{ 1.98 + return Collection()->Item(aIndex); 1.99 +} 1.100 + 1.101 +JSObject* 1.102 +HTMLAllCollection::GetObject(JSContext* aCx, ErrorResult& aRv) 1.103 +{ 1.104 + MOZ_ASSERT(aCx); 1.105 + 1.106 + if (!mObject) { 1.107 + JS::Rooted<JSObject*> wrapper(aCx, mDocument->GetWrapper()); 1.108 + MOZ_ASSERT(wrapper); 1.109 + 1.110 + JSAutoCompartment ac(aCx, wrapper); 1.111 + JS::Rooted<JSObject*> global(aCx, JS_GetGlobalForObject(aCx, wrapper)); 1.112 + mObject = JS_NewObject(aCx, &sHTMLDocumentAllClass, JS::NullPtr(), global); 1.113 + if (!mObject) { 1.114 + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 1.115 + return nullptr; 1.116 + } 1.117 + 1.118 + // Make the JSObject hold a reference to the document. 1.119 + JS_SetPrivate(mObject, ToSupports(mDocument)); 1.120 + NS_ADDREF(mDocument); 1.121 + } 1.122 + 1.123 + JS::ExposeObjectToActiveJS(mObject); 1.124 + return mObject; 1.125 +} 1.126 + 1.127 +nsContentList* 1.128 +HTMLAllCollection::Collection() 1.129 +{ 1.130 + if (!mCollection) { 1.131 + nsIDocument* document = mDocument; 1.132 + mCollection = document->GetElementsByTagName(NS_LITERAL_STRING("*")); 1.133 + MOZ_ASSERT(mCollection); 1.134 + } 1.135 + return mCollection; 1.136 +} 1.137 + 1.138 +static bool 1.139 +DocAllResultMatch(nsIContent* aContent, int32_t aNamespaceID, nsIAtom* aAtom, 1.140 + void* aData) 1.141 +{ 1.142 + if (aContent->GetID() == aAtom) { 1.143 + return true; 1.144 + } 1.145 + 1.146 + nsGenericHTMLElement* elm = nsGenericHTMLElement::FromContent(aContent); 1.147 + if (!elm) { 1.148 + return false; 1.149 + } 1.150 + 1.151 + nsIAtom* tag = elm->Tag(); 1.152 + if (tag != nsGkAtoms::a && 1.153 + tag != nsGkAtoms::applet && 1.154 + tag != nsGkAtoms::button && 1.155 + tag != nsGkAtoms::embed && 1.156 + tag != nsGkAtoms::form && 1.157 + tag != nsGkAtoms::iframe && 1.158 + tag != nsGkAtoms::img && 1.159 + tag != nsGkAtoms::input && 1.160 + tag != nsGkAtoms::map && 1.161 + tag != nsGkAtoms::meta && 1.162 + tag != nsGkAtoms::object && 1.163 + tag != nsGkAtoms::select && 1.164 + tag != nsGkAtoms::textarea) { 1.165 + return false; 1.166 + } 1.167 + 1.168 + const nsAttrValue* val = elm->GetParsedAttr(nsGkAtoms::name); 1.169 + return val && val->Type() == nsAttrValue::eAtom && 1.170 + val->GetAtomValue() == aAtom; 1.171 +} 1.172 + 1.173 +nsContentList* 1.174 +HTMLAllCollection::GetDocumentAllList(const nsAString& aID) 1.175 +{ 1.176 + if (nsContentList* docAllList = mNamedMap.GetWeak(aID)) { 1.177 + return docAllList; 1.178 + } 1.179 + 1.180 + Element* root = mDocument->GetRootElement(); 1.181 + if (!root) { 1.182 + return nullptr; 1.183 + } 1.184 + 1.185 + nsCOMPtr<nsIAtom> id = do_GetAtom(aID); 1.186 + nsRefPtr<nsContentList> docAllList = 1.187 + new nsContentList(root, DocAllResultMatch, nullptr, nullptr, true, id); 1.188 + mNamedMap.Put(aID, docAllList); 1.189 + return docAllList; 1.190 +} 1.191 + 1.192 +nsISupports* 1.193 +HTMLAllCollection::GetNamedItem(const nsAString& aID, 1.194 + nsWrapperCache** aCache) 1.195 +{ 1.196 + nsContentList* docAllList = GetDocumentAllList(aID); 1.197 + if (!docAllList) { 1.198 + return nullptr; 1.199 + } 1.200 + 1.201 + // Check if there are more than 1 entries. Do this by getting the second one 1.202 + // rather than the length since getting the length always requires walking 1.203 + // the entire document. 1.204 + 1.205 + nsIContent* cont = docAllList->Item(1, true); 1.206 + if (cont) { 1.207 + *aCache = docAllList; 1.208 + return static_cast<nsINodeList*>(docAllList); 1.209 + } 1.210 + 1.211 + // There's only 0 or 1 items. Return the first one or null. 1.212 + *aCache = cont = docAllList->Item(0, true); 1.213 + return cont; 1.214 +} 1.215 + 1.216 +} // namespace dom 1.217 +} // namespace mozilla 1.218 + 1.219 +static nsHTMLDocument* 1.220 +GetDocument(JSObject *obj) 1.221 +{ 1.222 + MOZ_ASSERT(js::GetObjectJSClass(obj) == &sHTMLDocumentAllClass); 1.223 + return static_cast<nsHTMLDocument*>( 1.224 + static_cast<nsINode*>(JS_GetPrivate(obj))); 1.225 +} 1.226 + 1.227 +bool 1.228 +nsHTMLDocumentSH::DocumentAllGetProperty(JSContext *cx, JS::Handle<JSObject*> obj_, 1.229 + JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) 1.230 +{ 1.231 + JS::Rooted<JSObject*> obj(cx, obj_); 1.232 + 1.233 + // document.all.item and .namedItem get their value in the 1.234 + // newResolve hook, so nothing to do for those properties here. And 1.235 + // we need to return early to prevent <div id="item"> from shadowing 1.236 + // document.all.item(), etc. 1.237 + if (nsDOMClassInfo::sItem_id == id || nsDOMClassInfo::sNamedItem_id == id) { 1.238 + return true; 1.239 + } 1.240 + 1.241 + JS::Rooted<JSObject*> proto(cx); 1.242 + while (js::GetObjectJSClass(obj) != &sHTMLDocumentAllClass) { 1.243 + if (!js::GetObjectProto(cx, obj, &proto)) { 1.244 + return false; 1.245 + } 1.246 + 1.247 + if (!proto) { 1.248 + NS_ERROR("The JS engine lies!"); 1.249 + return true; 1.250 + } 1.251 + 1.252 + obj = proto; 1.253 + } 1.254 + 1.255 + HTMLAllCollection* allCollection = GetDocument(obj)->All(); 1.256 + nsISupports *result; 1.257 + nsWrapperCache *cache; 1.258 + 1.259 + if (JSID_IS_STRING(id)) { 1.260 + if (nsDOMClassInfo::sLength_id == id) { 1.261 + // Make sure <div id="length"> doesn't shadow document.all.length. 1.262 + vp.setNumber(allCollection->Length()); 1.263 + return true; 1.264 + } 1.265 + 1.266 + // For all other strings, look for an element by id or name. 1.267 + nsDependentJSString str(id); 1.268 + result = allCollection->GetNamedItem(str, &cache); 1.269 + } else if (JSID_IS_INT(id) && JSID_TO_INT(id) >= 0) { 1.270 + // Map document.all[n] (where n is a number) to the n:th item in 1.271 + // the document.all node list. 1.272 + 1.273 + nsIContent* node = allCollection->Item(SafeCast<uint32_t>(JSID_TO_INT(id))); 1.274 + 1.275 + result = node; 1.276 + cache = node; 1.277 + } else { 1.278 + result = nullptr; 1.279 + } 1.280 + 1.281 + if (result) { 1.282 + nsresult rv = nsContentUtils::WrapNative(cx, result, cache, vp); 1.283 + if (NS_FAILED(rv)) { 1.284 + xpc::Throw(cx, rv); 1.285 + 1.286 + return false; 1.287 + } 1.288 + } else { 1.289 + vp.setUndefined(); 1.290 + } 1.291 + 1.292 + return true; 1.293 +} 1.294 + 1.295 +bool 1.296 +nsHTMLDocumentSH::DocumentAllNewResolve(JSContext *cx, JS::Handle<JSObject*> obj, 1.297 + JS::Handle<jsid> id, 1.298 + JS::MutableHandle<JSObject*> objp) 1.299 +{ 1.300 + JS::Rooted<JS::Value> v(cx); 1.301 + 1.302 + if (nsDOMClassInfo::sItem_id == id || nsDOMClassInfo::sNamedItem_id == id) { 1.303 + // Define the item() or namedItem() method. 1.304 + 1.305 + JSFunction *fnc = ::JS_DefineFunctionById(cx, obj, id, CallToGetPropMapper, 1.306 + 0, JSPROP_ENUMERATE); 1.307 + objp.set(obj); 1.308 + 1.309 + return fnc != nullptr; 1.310 + } 1.311 + 1.312 + if (nsDOMClassInfo::sLength_id == id) { 1.313 + // document.all.length. Any jsval other than undefined would do 1.314 + // here, all we need is to get into the code below that defines 1.315 + // this propery on obj, the rest happens in 1.316 + // DocumentAllGetProperty(). 1.317 + 1.318 + v = JSVAL_ONE; 1.319 + } else { 1.320 + if (!DocumentAllGetProperty(cx, obj, id, &v)) { 1.321 + return false; 1.322 + } 1.323 + } 1.324 + 1.325 + bool ok = true; 1.326 + 1.327 + if (v.get() != JSVAL_VOID) { 1.328 + ok = ::JS_DefinePropertyById(cx, obj, id, v, nullptr, nullptr, 0); 1.329 + objp.set(obj); 1.330 + } 1.331 + 1.332 + return ok; 1.333 +} 1.334 + 1.335 +void 1.336 +nsHTMLDocumentSH::ReleaseDocument(JSFreeOp *fop, JSObject *obj) 1.337 +{ 1.338 + nsIHTMLDocument* doc = GetDocument(obj); 1.339 + if (doc) { 1.340 + nsContentUtils::DeferredFinalize(doc); 1.341 + } 1.342 +} 1.343 + 1.344 +bool 1.345 +nsHTMLDocumentSH::CallToGetPropMapper(JSContext *cx, unsigned argc, jsval *vp) 1.346 +{ 1.347 + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 1.348 + // Handle document.all("foo") style access to document.all. 1.349 + 1.350 + if (args.length() != 1) { 1.351 + // XXX: Should throw NS_ERROR_XPC_NOT_ENOUGH_ARGS for argc < 1, 1.352 + // and create a new NS_ERROR_XPC_TOO_MANY_ARGS for argc > 1? IE 1.353 + // accepts nothing other than one arg. 1.354 + xpc::Throw(cx, NS_ERROR_INVALID_ARG); 1.355 + 1.356 + return false; 1.357 + } 1.358 + 1.359 + // Convert all types to string. 1.360 + JS::Rooted<JSString*> str(cx, JS::ToString(cx, args[0])); 1.361 + if (!str) { 1.362 + return false; 1.363 + } 1.364 + 1.365 + // If we are called via document.all(id) instead of document.all.item(i) or 1.366 + // another method, use the document.all callee object as self. 1.367 + JS::Rooted<JSObject*> self(cx); 1.368 + if (args.calleev().isObject() && 1.369 + JS_GetClass(&args.calleev().toObject()) == &sHTMLDocumentAllClass) { 1.370 + self = &args.calleev().toObject(); 1.371 + } else { 1.372 + self = JS_THIS_OBJECT(cx, vp); 1.373 + if (!self) 1.374 + return false; 1.375 + } 1.376 + 1.377 + size_t length; 1.378 + JS::Anchor<JSString *> anchor(str); 1.379 + const jschar *chars = ::JS_GetStringCharsAndLength(cx, str, &length); 1.380 + if (!chars) { 1.381 + return false; 1.382 + } 1.383 + 1.384 + return ::JS_GetUCProperty(cx, self, chars, length, args.rval()); 1.385 +}