|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "mozilla/dom/HTMLAllCollection.h" |
|
8 |
|
9 #include "jsapi.h" |
|
10 #include "mozilla/HoldDropJSObjects.h" |
|
11 #include "nsContentUtils.h" |
|
12 #include "nsDOMClassInfo.h" |
|
13 #include "nsHTMLDocument.h" |
|
14 #include "nsJSUtils.h" |
|
15 #include "nsWrapperCacheInlines.h" |
|
16 #include "xpcpublic.h" |
|
17 |
|
18 using namespace mozilla; |
|
19 using namespace mozilla::dom; |
|
20 |
|
21 class nsHTMLDocumentSH |
|
22 { |
|
23 public: |
|
24 static bool DocumentAllGetProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, |
|
25 JS::MutableHandle<JS::Value> vp); |
|
26 static bool DocumentAllNewResolve(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, |
|
27 JS::MutableHandle<JSObject*> objp); |
|
28 static void ReleaseDocument(JSFreeOp *fop, JSObject *obj); |
|
29 static bool CallToGetPropMapper(JSContext *cx, unsigned argc, JS::Value *vp); |
|
30 }; |
|
31 |
|
32 const JSClass sHTMLDocumentAllClass = { |
|
33 "HTML document.all class", |
|
34 JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS | JSCLASS_NEW_RESOLVE | |
|
35 JSCLASS_EMULATES_UNDEFINED, |
|
36 JS_PropertyStub, /* addProperty */ |
|
37 JS_DeletePropertyStub, /* delProperty */ |
|
38 nsHTMLDocumentSH::DocumentAllGetProperty, /* getProperty */ |
|
39 JS_StrictPropertyStub, /* setProperty */ |
|
40 JS_EnumerateStub, |
|
41 (JSResolveOp)nsHTMLDocumentSH::DocumentAllNewResolve, |
|
42 JS_ConvertStub, |
|
43 nsHTMLDocumentSH::ReleaseDocument, |
|
44 nsHTMLDocumentSH::CallToGetPropMapper |
|
45 }; |
|
46 |
|
47 namespace mozilla { |
|
48 namespace dom { |
|
49 |
|
50 HTMLAllCollection::HTMLAllCollection(nsHTMLDocument* aDocument) |
|
51 : mDocument(aDocument) |
|
52 { |
|
53 MOZ_ASSERT(mDocument); |
|
54 mozilla::HoldJSObjects(this); |
|
55 } |
|
56 |
|
57 HTMLAllCollection::~HTMLAllCollection() |
|
58 { |
|
59 mObject = nullptr; |
|
60 mozilla::DropJSObjects(this); |
|
61 } |
|
62 |
|
63 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(HTMLAllCollection, AddRef) |
|
64 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(HTMLAllCollection, Release) |
|
65 |
|
66 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLAllCollection) |
|
67 |
|
68 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(HTMLAllCollection) |
|
69 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS |
|
70 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument) |
|
71 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCollection) |
|
72 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNamedMap) |
|
73 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
74 |
|
75 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HTMLAllCollection) |
|
76 tmp->mObject = nullptr; |
|
77 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument) |
|
78 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCollection) |
|
79 NS_IMPL_CYCLE_COLLECTION_UNLINK(mNamedMap) |
|
80 NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
81 |
|
82 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(HTMLAllCollection) |
|
83 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mObject) |
|
84 NS_IMPL_CYCLE_COLLECTION_TRACE_END |
|
85 |
|
86 uint32_t |
|
87 HTMLAllCollection::Length() |
|
88 { |
|
89 return Collection()->Length(true); |
|
90 } |
|
91 |
|
92 nsIContent* |
|
93 HTMLAllCollection::Item(uint32_t aIndex) |
|
94 { |
|
95 return Collection()->Item(aIndex); |
|
96 } |
|
97 |
|
98 JSObject* |
|
99 HTMLAllCollection::GetObject(JSContext* aCx, ErrorResult& aRv) |
|
100 { |
|
101 MOZ_ASSERT(aCx); |
|
102 |
|
103 if (!mObject) { |
|
104 JS::Rooted<JSObject*> wrapper(aCx, mDocument->GetWrapper()); |
|
105 MOZ_ASSERT(wrapper); |
|
106 |
|
107 JSAutoCompartment ac(aCx, wrapper); |
|
108 JS::Rooted<JSObject*> global(aCx, JS_GetGlobalForObject(aCx, wrapper)); |
|
109 mObject = JS_NewObject(aCx, &sHTMLDocumentAllClass, JS::NullPtr(), global); |
|
110 if (!mObject) { |
|
111 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); |
|
112 return nullptr; |
|
113 } |
|
114 |
|
115 // Make the JSObject hold a reference to the document. |
|
116 JS_SetPrivate(mObject, ToSupports(mDocument)); |
|
117 NS_ADDREF(mDocument); |
|
118 } |
|
119 |
|
120 JS::ExposeObjectToActiveJS(mObject); |
|
121 return mObject; |
|
122 } |
|
123 |
|
124 nsContentList* |
|
125 HTMLAllCollection::Collection() |
|
126 { |
|
127 if (!mCollection) { |
|
128 nsIDocument* document = mDocument; |
|
129 mCollection = document->GetElementsByTagName(NS_LITERAL_STRING("*")); |
|
130 MOZ_ASSERT(mCollection); |
|
131 } |
|
132 return mCollection; |
|
133 } |
|
134 |
|
135 static bool |
|
136 DocAllResultMatch(nsIContent* aContent, int32_t aNamespaceID, nsIAtom* aAtom, |
|
137 void* aData) |
|
138 { |
|
139 if (aContent->GetID() == aAtom) { |
|
140 return true; |
|
141 } |
|
142 |
|
143 nsGenericHTMLElement* elm = nsGenericHTMLElement::FromContent(aContent); |
|
144 if (!elm) { |
|
145 return false; |
|
146 } |
|
147 |
|
148 nsIAtom* tag = elm->Tag(); |
|
149 if (tag != nsGkAtoms::a && |
|
150 tag != nsGkAtoms::applet && |
|
151 tag != nsGkAtoms::button && |
|
152 tag != nsGkAtoms::embed && |
|
153 tag != nsGkAtoms::form && |
|
154 tag != nsGkAtoms::iframe && |
|
155 tag != nsGkAtoms::img && |
|
156 tag != nsGkAtoms::input && |
|
157 tag != nsGkAtoms::map && |
|
158 tag != nsGkAtoms::meta && |
|
159 tag != nsGkAtoms::object && |
|
160 tag != nsGkAtoms::select && |
|
161 tag != nsGkAtoms::textarea) { |
|
162 return false; |
|
163 } |
|
164 |
|
165 const nsAttrValue* val = elm->GetParsedAttr(nsGkAtoms::name); |
|
166 return val && val->Type() == nsAttrValue::eAtom && |
|
167 val->GetAtomValue() == aAtom; |
|
168 } |
|
169 |
|
170 nsContentList* |
|
171 HTMLAllCollection::GetDocumentAllList(const nsAString& aID) |
|
172 { |
|
173 if (nsContentList* docAllList = mNamedMap.GetWeak(aID)) { |
|
174 return docAllList; |
|
175 } |
|
176 |
|
177 Element* root = mDocument->GetRootElement(); |
|
178 if (!root) { |
|
179 return nullptr; |
|
180 } |
|
181 |
|
182 nsCOMPtr<nsIAtom> id = do_GetAtom(aID); |
|
183 nsRefPtr<nsContentList> docAllList = |
|
184 new nsContentList(root, DocAllResultMatch, nullptr, nullptr, true, id); |
|
185 mNamedMap.Put(aID, docAllList); |
|
186 return docAllList; |
|
187 } |
|
188 |
|
189 nsISupports* |
|
190 HTMLAllCollection::GetNamedItem(const nsAString& aID, |
|
191 nsWrapperCache** aCache) |
|
192 { |
|
193 nsContentList* docAllList = GetDocumentAllList(aID); |
|
194 if (!docAllList) { |
|
195 return nullptr; |
|
196 } |
|
197 |
|
198 // Check if there are more than 1 entries. Do this by getting the second one |
|
199 // rather than the length since getting the length always requires walking |
|
200 // the entire document. |
|
201 |
|
202 nsIContent* cont = docAllList->Item(1, true); |
|
203 if (cont) { |
|
204 *aCache = docAllList; |
|
205 return static_cast<nsINodeList*>(docAllList); |
|
206 } |
|
207 |
|
208 // There's only 0 or 1 items. Return the first one or null. |
|
209 *aCache = cont = docAllList->Item(0, true); |
|
210 return cont; |
|
211 } |
|
212 |
|
213 } // namespace dom |
|
214 } // namespace mozilla |
|
215 |
|
216 static nsHTMLDocument* |
|
217 GetDocument(JSObject *obj) |
|
218 { |
|
219 MOZ_ASSERT(js::GetObjectJSClass(obj) == &sHTMLDocumentAllClass); |
|
220 return static_cast<nsHTMLDocument*>( |
|
221 static_cast<nsINode*>(JS_GetPrivate(obj))); |
|
222 } |
|
223 |
|
224 bool |
|
225 nsHTMLDocumentSH::DocumentAllGetProperty(JSContext *cx, JS::Handle<JSObject*> obj_, |
|
226 JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) |
|
227 { |
|
228 JS::Rooted<JSObject*> obj(cx, obj_); |
|
229 |
|
230 // document.all.item and .namedItem get their value in the |
|
231 // newResolve hook, so nothing to do for those properties here. And |
|
232 // we need to return early to prevent <div id="item"> from shadowing |
|
233 // document.all.item(), etc. |
|
234 if (nsDOMClassInfo::sItem_id == id || nsDOMClassInfo::sNamedItem_id == id) { |
|
235 return true; |
|
236 } |
|
237 |
|
238 JS::Rooted<JSObject*> proto(cx); |
|
239 while (js::GetObjectJSClass(obj) != &sHTMLDocumentAllClass) { |
|
240 if (!js::GetObjectProto(cx, obj, &proto)) { |
|
241 return false; |
|
242 } |
|
243 |
|
244 if (!proto) { |
|
245 NS_ERROR("The JS engine lies!"); |
|
246 return true; |
|
247 } |
|
248 |
|
249 obj = proto; |
|
250 } |
|
251 |
|
252 HTMLAllCollection* allCollection = GetDocument(obj)->All(); |
|
253 nsISupports *result; |
|
254 nsWrapperCache *cache; |
|
255 |
|
256 if (JSID_IS_STRING(id)) { |
|
257 if (nsDOMClassInfo::sLength_id == id) { |
|
258 // Make sure <div id="length"> doesn't shadow document.all.length. |
|
259 vp.setNumber(allCollection->Length()); |
|
260 return true; |
|
261 } |
|
262 |
|
263 // For all other strings, look for an element by id or name. |
|
264 nsDependentJSString str(id); |
|
265 result = allCollection->GetNamedItem(str, &cache); |
|
266 } else if (JSID_IS_INT(id) && JSID_TO_INT(id) >= 0) { |
|
267 // Map document.all[n] (where n is a number) to the n:th item in |
|
268 // the document.all node list. |
|
269 |
|
270 nsIContent* node = allCollection->Item(SafeCast<uint32_t>(JSID_TO_INT(id))); |
|
271 |
|
272 result = node; |
|
273 cache = node; |
|
274 } else { |
|
275 result = nullptr; |
|
276 } |
|
277 |
|
278 if (result) { |
|
279 nsresult rv = nsContentUtils::WrapNative(cx, result, cache, vp); |
|
280 if (NS_FAILED(rv)) { |
|
281 xpc::Throw(cx, rv); |
|
282 |
|
283 return false; |
|
284 } |
|
285 } else { |
|
286 vp.setUndefined(); |
|
287 } |
|
288 |
|
289 return true; |
|
290 } |
|
291 |
|
292 bool |
|
293 nsHTMLDocumentSH::DocumentAllNewResolve(JSContext *cx, JS::Handle<JSObject*> obj, |
|
294 JS::Handle<jsid> id, |
|
295 JS::MutableHandle<JSObject*> objp) |
|
296 { |
|
297 JS::Rooted<JS::Value> v(cx); |
|
298 |
|
299 if (nsDOMClassInfo::sItem_id == id || nsDOMClassInfo::sNamedItem_id == id) { |
|
300 // Define the item() or namedItem() method. |
|
301 |
|
302 JSFunction *fnc = ::JS_DefineFunctionById(cx, obj, id, CallToGetPropMapper, |
|
303 0, JSPROP_ENUMERATE); |
|
304 objp.set(obj); |
|
305 |
|
306 return fnc != nullptr; |
|
307 } |
|
308 |
|
309 if (nsDOMClassInfo::sLength_id == id) { |
|
310 // document.all.length. Any jsval other than undefined would do |
|
311 // here, all we need is to get into the code below that defines |
|
312 // this propery on obj, the rest happens in |
|
313 // DocumentAllGetProperty(). |
|
314 |
|
315 v = JSVAL_ONE; |
|
316 } else { |
|
317 if (!DocumentAllGetProperty(cx, obj, id, &v)) { |
|
318 return false; |
|
319 } |
|
320 } |
|
321 |
|
322 bool ok = true; |
|
323 |
|
324 if (v.get() != JSVAL_VOID) { |
|
325 ok = ::JS_DefinePropertyById(cx, obj, id, v, nullptr, nullptr, 0); |
|
326 objp.set(obj); |
|
327 } |
|
328 |
|
329 return ok; |
|
330 } |
|
331 |
|
332 void |
|
333 nsHTMLDocumentSH::ReleaseDocument(JSFreeOp *fop, JSObject *obj) |
|
334 { |
|
335 nsIHTMLDocument* doc = GetDocument(obj); |
|
336 if (doc) { |
|
337 nsContentUtils::DeferredFinalize(doc); |
|
338 } |
|
339 } |
|
340 |
|
341 bool |
|
342 nsHTMLDocumentSH::CallToGetPropMapper(JSContext *cx, unsigned argc, jsval *vp) |
|
343 { |
|
344 JS::CallArgs args = JS::CallArgsFromVp(argc, vp); |
|
345 // Handle document.all("foo") style access to document.all. |
|
346 |
|
347 if (args.length() != 1) { |
|
348 // XXX: Should throw NS_ERROR_XPC_NOT_ENOUGH_ARGS for argc < 1, |
|
349 // and create a new NS_ERROR_XPC_TOO_MANY_ARGS for argc > 1? IE |
|
350 // accepts nothing other than one arg. |
|
351 xpc::Throw(cx, NS_ERROR_INVALID_ARG); |
|
352 |
|
353 return false; |
|
354 } |
|
355 |
|
356 // Convert all types to string. |
|
357 JS::Rooted<JSString*> str(cx, JS::ToString(cx, args[0])); |
|
358 if (!str) { |
|
359 return false; |
|
360 } |
|
361 |
|
362 // If we are called via document.all(id) instead of document.all.item(i) or |
|
363 // another method, use the document.all callee object as self. |
|
364 JS::Rooted<JSObject*> self(cx); |
|
365 if (args.calleev().isObject() && |
|
366 JS_GetClass(&args.calleev().toObject()) == &sHTMLDocumentAllClass) { |
|
367 self = &args.calleev().toObject(); |
|
368 } else { |
|
369 self = JS_THIS_OBJECT(cx, vp); |
|
370 if (!self) |
|
371 return false; |
|
372 } |
|
373 |
|
374 size_t length; |
|
375 JS::Anchor<JSString *> anchor(str); |
|
376 const jschar *chars = ::JS_GetStringCharsAndLength(cx, str, &length); |
|
377 if (!chars) { |
|
378 return false; |
|
379 } |
|
380 |
|
381 return ::JS_GetUCProperty(cx, self, chars, length, args.rval()); |
|
382 } |