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