dom/xbl/nsXBLBinding.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ts=2 sw=2 et tw=79: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "nsCOMPtr.h"
michael@0 8 #include "nsIAtom.h"
michael@0 9 #include "nsXBLDocumentInfo.h"
michael@0 10 #include "nsIInputStream.h"
michael@0 11 #include "nsNameSpaceManager.h"
michael@0 12 #include "nsIURI.h"
michael@0 13 #include "nsIURL.h"
michael@0 14 #include "nsIChannel.h"
michael@0 15 #include "nsXPIDLString.h"
michael@0 16 #include "nsReadableUtils.h"
michael@0 17 #include "nsNetUtil.h"
michael@0 18 #include "plstr.h"
michael@0 19 #include "nsIContent.h"
michael@0 20 #include "nsIDocument.h"
michael@0 21 #include "nsContentUtils.h"
michael@0 22 #include "ChildIterator.h"
michael@0 23 #include "nsCxPusher.h"
michael@0 24 #ifdef MOZ_XUL
michael@0 25 #include "nsIXULDocument.h"
michael@0 26 #endif
michael@0 27 #include "nsIXMLContentSink.h"
michael@0 28 #include "nsContentCID.h"
michael@0 29 #include "mozilla/dom/XMLDocument.h"
michael@0 30 #include "jsapi.h"
michael@0 31 #include "nsXBLService.h"
michael@0 32 #include "nsIXPConnect.h"
michael@0 33 #include "nsIScriptContext.h"
michael@0 34 #include "nsCRT.h"
michael@0 35
michael@0 36 // Event listeners
michael@0 37 #include "mozilla/EventListenerManager.h"
michael@0 38 #include "nsIDOMEventListener.h"
michael@0 39 #include "nsAttrName.h"
michael@0 40
michael@0 41 #include "nsGkAtoms.h"
michael@0 42
michael@0 43 #include "nsXBLPrototypeHandler.h"
michael@0 44
michael@0 45 #include "nsXBLPrototypeBinding.h"
michael@0 46 #include "nsXBLBinding.h"
michael@0 47 #include "nsIPrincipal.h"
michael@0 48 #include "nsIScriptSecurityManager.h"
michael@0 49 #include "mozilla/dom/XBLChildrenElement.h"
michael@0 50
michael@0 51 #include "prprf.h"
michael@0 52 #include "nsNodeUtils.h"
michael@0 53 #include "nsJSUtils.h"
michael@0 54
michael@0 55 // Nasty hack. Maybe we could move some of the classinfo utility methods
michael@0 56 // (e.g. WrapNative) over to nsContentUtils?
michael@0 57 #include "nsDOMClassInfo.h"
michael@0 58
michael@0 59 #include "mozilla/dom/Element.h"
michael@0 60 #include "mozilla/dom/ShadowRoot.h"
michael@0 61
michael@0 62 using namespace mozilla;
michael@0 63 using namespace mozilla::dom;
michael@0 64
michael@0 65 // Helper classes
michael@0 66
michael@0 67 /***********************************************************************/
michael@0 68 //
michael@0 69 // The JS class for XBLBinding
michael@0 70 //
michael@0 71 static void
michael@0 72 XBLFinalize(JSFreeOp *fop, JSObject *obj)
michael@0 73 {
michael@0 74 nsXBLDocumentInfo* docInfo =
michael@0 75 static_cast<nsXBLDocumentInfo*>(::JS_GetPrivate(obj));
michael@0 76 nsContentUtils::DeferredFinalize(docInfo);
michael@0 77 }
michael@0 78
michael@0 79 static bool
michael@0 80 XBLEnumerate(JSContext *cx, JS::Handle<JSObject*> obj)
michael@0 81 {
michael@0 82 nsXBLPrototypeBinding* protoBinding =
michael@0 83 static_cast<nsXBLPrototypeBinding*>(::JS_GetReservedSlot(obj, 0).toPrivate());
michael@0 84 MOZ_ASSERT(protoBinding);
michael@0 85
michael@0 86 return protoBinding->ResolveAllFields(cx, obj);
michael@0 87 }
michael@0 88
michael@0 89 static const JSClass gPrototypeJSClass = {
michael@0 90 "XBL prototype JSClass",
michael@0 91 JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS |
michael@0 92 JSCLASS_NEW_RESOLVE |
michael@0 93 // Our one reserved slot holds the relevant nsXBLPrototypeBinding
michael@0 94 JSCLASS_HAS_RESERVED_SLOTS(1),
michael@0 95 JS_PropertyStub, JS_DeletePropertyStub,
michael@0 96 JS_PropertyStub, JS_StrictPropertyStub,
michael@0 97 XBLEnumerate, JS_ResolveStub,
michael@0 98 JS_ConvertStub, XBLFinalize,
michael@0 99 nullptr, nullptr, nullptr, nullptr
michael@0 100 };
michael@0 101
michael@0 102 // Implementation /////////////////////////////////////////////////////////////////
michael@0 103
michael@0 104 // Constructors/Destructors
michael@0 105 nsXBLBinding::nsXBLBinding(nsXBLPrototypeBinding* aBinding)
michael@0 106 : mMarkedForDeath(false)
michael@0 107 , mUsingXBLScope(false)
michael@0 108 , mPrototypeBinding(aBinding)
michael@0 109 {
michael@0 110 NS_ASSERTION(mPrototypeBinding, "Must have a prototype binding!");
michael@0 111 // Grab a ref to the document info so the prototype binding won't die
michael@0 112 NS_ADDREF(mPrototypeBinding->XBLDocumentInfo());
michael@0 113 }
michael@0 114
michael@0 115 // Constructor used by web components.
michael@0 116 nsXBLBinding::nsXBLBinding(ShadowRoot* aShadowRoot, nsXBLPrototypeBinding* aBinding)
michael@0 117 : mMarkedForDeath(false),
michael@0 118 mUsingXBLScope(false),
michael@0 119 mPrototypeBinding(aBinding),
michael@0 120 mContent(aShadowRoot)
michael@0 121 {
michael@0 122 NS_ASSERTION(mPrototypeBinding, "Must have a prototype binding!");
michael@0 123 // Grab a ref to the document info so the prototype binding won't die
michael@0 124 NS_ADDREF(mPrototypeBinding->XBLDocumentInfo());
michael@0 125 }
michael@0 126
michael@0 127 nsXBLBinding::~nsXBLBinding(void)
michael@0 128 {
michael@0 129 if (mContent) {
michael@0 130 nsXBLBinding::UninstallAnonymousContent(mContent->OwnerDoc(), mContent);
michael@0 131 }
michael@0 132 nsXBLDocumentInfo* info = mPrototypeBinding->XBLDocumentInfo();
michael@0 133 NS_RELEASE(info);
michael@0 134 }
michael@0 135
michael@0 136 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXBLBinding)
michael@0 137
michael@0 138 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXBLBinding)
michael@0 139 // XXX Probably can't unlink mPrototypeBinding->XBLDocumentInfo(), because
michael@0 140 // mPrototypeBinding is weak.
michael@0 141 if (tmp->mContent) {
michael@0 142 nsXBLBinding::UninstallAnonymousContent(tmp->mContent->OwnerDoc(),
michael@0 143 tmp->mContent);
michael@0 144 }
michael@0 145 NS_IMPL_CYCLE_COLLECTION_UNLINK(mContent)
michael@0 146 NS_IMPL_CYCLE_COLLECTION_UNLINK(mNextBinding)
michael@0 147 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDefaultInsertionPoint)
michael@0 148 NS_IMPL_CYCLE_COLLECTION_UNLINK(mInsertionPoints)
michael@0 149 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContentList)
michael@0 150 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
michael@0 151 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXBLBinding)
michael@0 152 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
michael@0 153 "mPrototypeBinding->XBLDocumentInfo()");
michael@0 154 cb.NoteXPCOMChild(tmp->mPrototypeBinding->XBLDocumentInfo());
michael@0 155 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent)
michael@0 156 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNextBinding)
michael@0 157 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDefaultInsertionPoint)
michael@0 158 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInsertionPoints)
michael@0 159 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContentList)
michael@0 160 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
michael@0 161 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsXBLBinding, AddRef)
michael@0 162 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsXBLBinding, Release)
michael@0 163
michael@0 164 void
michael@0 165 nsXBLBinding::SetBaseBinding(nsXBLBinding* aBinding)
michael@0 166 {
michael@0 167 if (mNextBinding) {
michael@0 168 NS_ERROR("Base XBL binding is already defined!");
michael@0 169 return;
michael@0 170 }
michael@0 171
michael@0 172 mNextBinding = aBinding; // Comptr handles rel/add
michael@0 173 }
michael@0 174
michael@0 175 nsXBLBinding*
michael@0 176 nsXBLBinding::GetBindingWithContent()
michael@0 177 {
michael@0 178 if (mContent) {
michael@0 179 return this;
michael@0 180 }
michael@0 181
michael@0 182 return mNextBinding ? mNextBinding->GetBindingWithContent() : nullptr;
michael@0 183 }
michael@0 184
michael@0 185 void
michael@0 186 nsXBLBinding::InstallAnonymousContent(nsIContent* aAnonParent, nsIContent* aElement,
michael@0 187 bool aChromeOnlyContent)
michael@0 188 {
michael@0 189 // We need to ensure two things.
michael@0 190 // (1) The anonymous content should be fooled into thinking it's in the bound
michael@0 191 // element's document, assuming that the bound element is in a document
michael@0 192 // Note that we don't change the current doc of aAnonParent here, since that
michael@0 193 // quite simply does not matter. aAnonParent is just a way of keeping refs
michael@0 194 // to all its kids, which are anonymous content from the point of view of
michael@0 195 // aElement.
michael@0 196 // (2) The children's parent back pointer should not be to this synthetic root
michael@0 197 // but should instead point to the enclosing parent element.
michael@0 198 nsIDocument* doc = aElement->GetCurrentDoc();
michael@0 199 bool allowScripts = AllowScripts();
michael@0 200
michael@0 201 nsAutoScriptBlocker scriptBlocker;
michael@0 202 for (nsIContent* child = aAnonParent->GetFirstChild();
michael@0 203 child;
michael@0 204 child = child->GetNextSibling()) {
michael@0 205 child->UnbindFromTree();
michael@0 206 if (aChromeOnlyContent) {
michael@0 207 child->SetFlags(NODE_CHROME_ONLY_ACCESS |
michael@0 208 NODE_IS_ROOT_OF_CHROME_ONLY_ACCESS);
michael@0 209 }
michael@0 210 nsresult rv =
michael@0 211 child->BindToTree(doc, aElement, mBoundElement, allowScripts);
michael@0 212 if (NS_FAILED(rv)) {
michael@0 213 // Oh, well... Just give up.
michael@0 214 // XXXbz This really shouldn't be a void method!
michael@0 215 child->UnbindFromTree();
michael@0 216 return;
michael@0 217 }
michael@0 218
michael@0 219 child->SetFlags(NODE_IS_ANONYMOUS_ROOT);
michael@0 220
michael@0 221 #ifdef MOZ_XUL
michael@0 222 // To make XUL templates work (and other goodies that happen when
michael@0 223 // an element is added to a XUL document), we need to notify the
michael@0 224 // XUL document using its special API.
michael@0 225 nsCOMPtr<nsIXULDocument> xuldoc(do_QueryInterface(doc));
michael@0 226 if (xuldoc)
michael@0 227 xuldoc->AddSubtreeToDocument(child);
michael@0 228 #endif
michael@0 229 }
michael@0 230 }
michael@0 231
michael@0 232 void
michael@0 233 nsXBLBinding::UninstallAnonymousContent(nsIDocument* aDocument,
michael@0 234 nsIContent* aAnonParent)
michael@0 235 {
michael@0 236 if (aAnonParent->HasFlag(NODE_IS_IN_SHADOW_TREE)) {
michael@0 237 // It is unnecessary to uninstall anonymous content in a shadow tree
michael@0 238 // because the ShadowRoot itself is a DocumentFragment and does not
michael@0 239 // need any additional cleanup.
michael@0 240 return;
michael@0 241 }
michael@0 242
michael@0 243 nsAutoScriptBlocker scriptBlocker;
michael@0 244 // Hold a strong ref while doing this, just in case.
michael@0 245 nsCOMPtr<nsIContent> anonParent = aAnonParent;
michael@0 246 #ifdef MOZ_XUL
michael@0 247 nsCOMPtr<nsIXULDocument> xuldoc =
michael@0 248 do_QueryInterface(aDocument);
michael@0 249 #endif
michael@0 250 for (nsIContent* child = aAnonParent->GetFirstChild();
michael@0 251 child;
michael@0 252 child = child->GetNextSibling()) {
michael@0 253 child->UnbindFromTree();
michael@0 254 #ifdef MOZ_XUL
michael@0 255 if (xuldoc) {
michael@0 256 xuldoc->RemoveSubtreeFromDocument(child);
michael@0 257 }
michael@0 258 #endif
michael@0 259 }
michael@0 260 }
michael@0 261
michael@0 262 void
michael@0 263 nsXBLBinding::SetBoundElement(nsIContent* aElement)
michael@0 264 {
michael@0 265 mBoundElement = aElement;
michael@0 266 if (mNextBinding)
michael@0 267 mNextBinding->SetBoundElement(aElement);
michael@0 268
michael@0 269 if (!mBoundElement) {
michael@0 270 return;
michael@0 271 }
michael@0 272
michael@0 273 // Compute whether we're using an XBL scope.
michael@0 274 //
michael@0 275 // We disable XBL scopes for remote XUL, where we care about compat more
michael@0 276 // than security. So we need to know whether we're using an XBL scope so that
michael@0 277 // we can decide what to do about untrusted events when "allowuntrusted"
michael@0 278 // is not given in the handler declaration.
michael@0 279 nsCOMPtr<nsIGlobalObject> go = mBoundElement->OwnerDoc()->GetScopeObject();
michael@0 280 NS_ENSURE_TRUE_VOID(go && go->GetGlobalJSObject());
michael@0 281 mUsingXBLScope = xpc::UseXBLScope(js::GetObjectCompartment(go->GetGlobalJSObject()));
michael@0 282 }
michael@0 283
michael@0 284 bool
michael@0 285 nsXBLBinding::HasStyleSheets() const
michael@0 286 {
michael@0 287 // Find out if we need to re-resolve style. We'll need to do this
michael@0 288 // if we have additional stylesheets in our binding document.
michael@0 289 if (mPrototypeBinding->HasStyleSheets())
michael@0 290 return true;
michael@0 291
michael@0 292 return mNextBinding ? mNextBinding->HasStyleSheets() : false;
michael@0 293 }
michael@0 294
michael@0 295 void
michael@0 296 nsXBLBinding::GenerateAnonymousContent()
michael@0 297 {
michael@0 298 NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
michael@0 299 "Someone forgot a script blocker");
michael@0 300
michael@0 301 // Fetch the content element for this binding.
michael@0 302 nsIContent* content =
michael@0 303 mPrototypeBinding->GetImmediateChild(nsGkAtoms::content);
michael@0 304
michael@0 305 if (!content) {
michael@0 306 // We have no anonymous content.
michael@0 307 if (mNextBinding)
michael@0 308 mNextBinding->GenerateAnonymousContent();
michael@0 309
michael@0 310 return;
michael@0 311 }
michael@0 312
michael@0 313 // Find out if we're really building kids or if we're just
michael@0 314 // using the attribute-setting shorthand hack.
michael@0 315 uint32_t contentCount = content->GetChildCount();
michael@0 316
michael@0 317 // Plan to build the content by default.
michael@0 318 bool hasContent = (contentCount > 0);
michael@0 319 if (hasContent) {
michael@0 320 nsIDocument* doc = mBoundElement->OwnerDoc();
michael@0 321
michael@0 322 nsCOMPtr<nsINode> clonedNode;
michael@0 323 nsCOMArray<nsINode> nodesWithProperties;
michael@0 324 nsNodeUtils::Clone(content, true, doc->NodeInfoManager(),
michael@0 325 nodesWithProperties, getter_AddRefs(clonedNode));
michael@0 326 mContent = clonedNode->AsElement();
michael@0 327
michael@0 328 // Search for <xbl:children> elements in the XBL content. In the presence
michael@0 329 // of multiple default insertion points, we use the last one in document
michael@0 330 // order.
michael@0 331 for (nsIContent* child = mContent; child; child = child->GetNextNode(mContent)) {
michael@0 332 if (child->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) {
michael@0 333 XBLChildrenElement* point = static_cast<XBLChildrenElement*>(child);
michael@0 334 if (point->IsDefaultInsertion()) {
michael@0 335 mDefaultInsertionPoint = point;
michael@0 336 } else {
michael@0 337 mInsertionPoints.AppendElement(point);
michael@0 338 }
michael@0 339 }
michael@0 340 }
michael@0 341
michael@0 342 // Do this after looking for <children> as this messes up the parent
michael@0 343 // pointer which would make the GetNextNode call above fail
michael@0 344 InstallAnonymousContent(mContent, mBoundElement,
michael@0 345 mPrototypeBinding->ChromeOnlyContent());
michael@0 346
michael@0 347 // Insert explicit children into insertion points
michael@0 348 if (mDefaultInsertionPoint && mInsertionPoints.IsEmpty()) {
michael@0 349 ExplicitChildIterator iter(mBoundElement);
michael@0 350 for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
michael@0 351 mDefaultInsertionPoint->AppendInsertedChild(child);
michael@0 352 }
michael@0 353 } else {
michael@0 354 // It is odd to come into this code if mInsertionPoints is not empty, but
michael@0 355 // we need to make sure to do the compatibility hack below if the bound
michael@0 356 // node has any non <xul:template> or <xul:observes> children.
michael@0 357 ExplicitChildIterator iter(mBoundElement);
michael@0 358 for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
michael@0 359 XBLChildrenElement* point = FindInsertionPointForInternal(child);
michael@0 360 if (point) {
michael@0 361 point->AppendInsertedChild(child);
michael@0 362 } else {
michael@0 363 nsINodeInfo *ni = child->NodeInfo();
michael@0 364 if (ni->NamespaceID() != kNameSpaceID_XUL ||
michael@0 365 (!ni->Equals(nsGkAtoms::_template) &&
michael@0 366 !ni->Equals(nsGkAtoms::observes))) {
michael@0 367 // Compatibility hack. For some reason the original XBL
michael@0 368 // implementation dropped the content of a binding if any child of
michael@0 369 // the bound element didn't match any of the <children> in the
michael@0 370 // binding. This became a pseudo-API that we have to maintain.
michael@0 371
michael@0 372 // Undo InstallAnonymousContent
michael@0 373 UninstallAnonymousContent(doc, mContent);
michael@0 374
michael@0 375 // Clear out our children elements to avoid dangling references.
michael@0 376 ClearInsertionPoints();
michael@0 377
michael@0 378 // Pretend as though there was no content in the binding.
michael@0 379 mContent = nullptr;
michael@0 380 return;
michael@0 381 }
michael@0 382 }
michael@0 383 }
michael@0 384 }
michael@0 385
michael@0 386 // Set binding parent on default content if need
michael@0 387 if (mDefaultInsertionPoint) {
michael@0 388 mDefaultInsertionPoint->MaybeSetupDefaultContent();
michael@0 389 }
michael@0 390 for (uint32_t i = 0; i < mInsertionPoints.Length(); ++i) {
michael@0 391 mInsertionPoints[i]->MaybeSetupDefaultContent();
michael@0 392 }
michael@0 393
michael@0 394 mPrototypeBinding->SetInitialAttributes(mBoundElement, mContent);
michael@0 395 }
michael@0 396
michael@0 397 // Always check the content element for potential attributes.
michael@0 398 // This shorthand hack always happens, even when we didn't
michael@0 399 // build anonymous content.
michael@0 400 const nsAttrName* attrName;
michael@0 401 for (uint32_t i = 0; (attrName = content->GetAttrNameAt(i)); ++i) {
michael@0 402 int32_t namespaceID = attrName->NamespaceID();
michael@0 403 // Hold a strong reference here so that the atom doesn't go away during
michael@0 404 // UnsetAttr.
michael@0 405 nsCOMPtr<nsIAtom> name = attrName->LocalName();
michael@0 406
michael@0 407 if (name != nsGkAtoms::includes) {
michael@0 408 if (!nsContentUtils::HasNonEmptyAttr(mBoundElement, namespaceID, name)) {
michael@0 409 nsAutoString value2;
michael@0 410 content->GetAttr(namespaceID, name, value2);
michael@0 411 mBoundElement->SetAttr(namespaceID, name, attrName->GetPrefix(),
michael@0 412 value2, false);
michael@0 413 }
michael@0 414 }
michael@0 415
michael@0 416 // Conserve space by wiping the attributes off the clone.
michael@0 417 if (mContent)
michael@0 418 mContent->UnsetAttr(namespaceID, name, false);
michael@0 419 }
michael@0 420 }
michael@0 421
michael@0 422 XBLChildrenElement*
michael@0 423 nsXBLBinding::FindInsertionPointFor(nsIContent* aChild)
michael@0 424 {
michael@0 425 // XXX We should get rid of this function as it causes us to traverse the
michael@0 426 // binding chain multiple times
michael@0 427 if (mContent) {
michael@0 428 return FindInsertionPointForInternal(aChild);
michael@0 429 }
michael@0 430
michael@0 431 return mNextBinding ? mNextBinding->FindInsertionPointFor(aChild)
michael@0 432 : nullptr;
michael@0 433 }
michael@0 434
michael@0 435 XBLChildrenElement*
michael@0 436 nsXBLBinding::FindInsertionPointForInternal(nsIContent* aChild)
michael@0 437 {
michael@0 438 for (uint32_t i = 0; i < mInsertionPoints.Length(); ++i) {
michael@0 439 XBLChildrenElement* point = mInsertionPoints[i];
michael@0 440 if (point->Includes(aChild)) {
michael@0 441 return point;
michael@0 442 }
michael@0 443 }
michael@0 444
michael@0 445 return mDefaultInsertionPoint;
michael@0 446 }
michael@0 447
michael@0 448 void
michael@0 449 nsXBLBinding::ClearInsertionPoints()
michael@0 450 {
michael@0 451 if (mDefaultInsertionPoint) {
michael@0 452 mDefaultInsertionPoint->ClearInsertedChildren();
michael@0 453 }
michael@0 454
michael@0 455 for (uint32_t i = 0; i < mInsertionPoints.Length(); ++i) {
michael@0 456 mInsertionPoints[i]->ClearInsertedChildren();
michael@0 457 }
michael@0 458 }
michael@0 459
michael@0 460 nsAnonymousContentList*
michael@0 461 nsXBLBinding::GetAnonymousNodeList()
michael@0 462 {
michael@0 463 if (!mContent) {
michael@0 464 return mNextBinding ? mNextBinding->GetAnonymousNodeList() : nullptr;
michael@0 465 }
michael@0 466
michael@0 467 if (!mAnonymousContentList) {
michael@0 468 mAnonymousContentList = new nsAnonymousContentList(mContent);
michael@0 469 }
michael@0 470
michael@0 471 return mAnonymousContentList;
michael@0 472 }
michael@0 473
michael@0 474 void
michael@0 475 nsXBLBinding::InstallEventHandlers()
michael@0 476 {
michael@0 477 // Don't install handlers if scripts aren't allowed.
michael@0 478 if (AllowScripts()) {
michael@0 479 // Fetch the handlers prototypes for this binding.
michael@0 480 nsXBLPrototypeHandler* handlerChain = mPrototypeBinding->GetPrototypeHandlers();
michael@0 481
michael@0 482 if (handlerChain) {
michael@0 483 EventListenerManager* manager = mBoundElement->GetOrCreateListenerManager();
michael@0 484 if (!manager)
michael@0 485 return;
michael@0 486
michael@0 487 bool isChromeDoc =
michael@0 488 nsContentUtils::IsChromeDoc(mBoundElement->OwnerDoc());
michael@0 489 bool isChromeBinding = mPrototypeBinding->IsChrome();
michael@0 490 nsXBLPrototypeHandler* curr;
michael@0 491 for (curr = handlerChain; curr; curr = curr->GetNextHandler()) {
michael@0 492 // Fetch the event type.
michael@0 493 nsCOMPtr<nsIAtom> eventAtom = curr->GetEventName();
michael@0 494 if (!eventAtom ||
michael@0 495 eventAtom == nsGkAtoms::keyup ||
michael@0 496 eventAtom == nsGkAtoms::keydown ||
michael@0 497 eventAtom == nsGkAtoms::keypress)
michael@0 498 continue;
michael@0 499
michael@0 500 nsXBLEventHandler* handler = curr->GetEventHandler();
michael@0 501 if (handler) {
michael@0 502 // Figure out if we're using capturing or not.
michael@0 503 EventListenerFlags flags;
michael@0 504 flags.mCapture = (curr->GetPhase() == NS_PHASE_CAPTURING);
michael@0 505
michael@0 506 // If this is a command, add it in the system event group
michael@0 507 if ((curr->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND |
michael@0 508 NS_HANDLER_TYPE_SYSTEM)) &&
michael@0 509 (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) {
michael@0 510 flags.mInSystemGroup = true;
michael@0 511 }
michael@0 512
michael@0 513 bool hasAllowUntrustedAttr = curr->HasAllowUntrustedAttr();
michael@0 514 if ((hasAllowUntrustedAttr && curr->AllowUntrustedEvents()) ||
michael@0 515 (!hasAllowUntrustedAttr && !isChromeDoc && !mUsingXBLScope)) {
michael@0 516 flags.mAllowUntrustedEvents = true;
michael@0 517 }
michael@0 518
michael@0 519 manager->AddEventListenerByType(handler,
michael@0 520 nsDependentAtomString(eventAtom),
michael@0 521 flags);
michael@0 522 }
michael@0 523 }
michael@0 524
michael@0 525 const nsCOMArray<nsXBLKeyEventHandler>* keyHandlers =
michael@0 526 mPrototypeBinding->GetKeyEventHandlers();
michael@0 527 int32_t i;
michael@0 528 for (i = 0; i < keyHandlers->Count(); ++i) {
michael@0 529 nsXBLKeyEventHandler* handler = keyHandlers->ObjectAt(i);
michael@0 530 handler->SetIsBoundToChrome(isChromeDoc);
michael@0 531 handler->SetUsingXBLScope(mUsingXBLScope);
michael@0 532
michael@0 533 nsAutoString type;
michael@0 534 handler->GetEventName(type);
michael@0 535
michael@0 536 // If this is a command, add it in the system event group, otherwise
michael@0 537 // add it to the standard event group.
michael@0 538
michael@0 539 // Figure out if we're using capturing or not.
michael@0 540 EventListenerFlags flags;
michael@0 541 flags.mCapture = (handler->GetPhase() == NS_PHASE_CAPTURING);
michael@0 542
michael@0 543 if ((handler->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND |
michael@0 544 NS_HANDLER_TYPE_SYSTEM)) &&
michael@0 545 (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) {
michael@0 546 flags.mInSystemGroup = true;
michael@0 547 }
michael@0 548
michael@0 549 // For key handlers we have to set mAllowUntrustedEvents flag.
michael@0 550 // Whether the handling of the event is allowed or not is handled in
michael@0 551 // nsXBLKeyEventHandler::HandleEvent
michael@0 552 flags.mAllowUntrustedEvents = true;
michael@0 553
michael@0 554 manager->AddEventListenerByType(handler, type, flags);
michael@0 555 }
michael@0 556 }
michael@0 557 }
michael@0 558
michael@0 559 if (mNextBinding)
michael@0 560 mNextBinding->InstallEventHandlers();
michael@0 561 }
michael@0 562
michael@0 563 nsresult
michael@0 564 nsXBLBinding::InstallImplementation()
michael@0 565 {
michael@0 566 // Always install the base class properties first, so that
michael@0 567 // derived classes can reference the base class properties.
michael@0 568
michael@0 569 if (mNextBinding) {
michael@0 570 nsresult rv = mNextBinding->InstallImplementation();
michael@0 571 NS_ENSURE_SUCCESS(rv, rv);
michael@0 572 }
michael@0 573
michael@0 574 // iterate through each property in the prototype's list and install the property.
michael@0 575 if (AllowScripts())
michael@0 576 return mPrototypeBinding->InstallImplementation(this);
michael@0 577
michael@0 578 return NS_OK;
michael@0 579 }
michael@0 580
michael@0 581 nsIAtom*
michael@0 582 nsXBLBinding::GetBaseTag(int32_t* aNameSpaceID)
michael@0 583 {
michael@0 584 nsIAtom *tag = mPrototypeBinding->GetBaseTag(aNameSpaceID);
michael@0 585 if (!tag && mNextBinding)
michael@0 586 return mNextBinding->GetBaseTag(aNameSpaceID);
michael@0 587
michael@0 588 return tag;
michael@0 589 }
michael@0 590
michael@0 591 void
michael@0 592 nsXBLBinding::AttributeChanged(nsIAtom* aAttribute, int32_t aNameSpaceID,
michael@0 593 bool aRemoveFlag, bool aNotify)
michael@0 594 {
michael@0 595 // XXX Change if we ever allow multiple bindings in a chain to contribute anonymous content
michael@0 596 if (!mContent) {
michael@0 597 if (mNextBinding)
michael@0 598 mNextBinding->AttributeChanged(aAttribute, aNameSpaceID,
michael@0 599 aRemoveFlag, aNotify);
michael@0 600 } else {
michael@0 601 mPrototypeBinding->AttributeChanged(aAttribute, aNameSpaceID, aRemoveFlag,
michael@0 602 mBoundElement, mContent, aNotify);
michael@0 603 }
michael@0 604 }
michael@0 605
michael@0 606 void
michael@0 607 nsXBLBinding::ExecuteAttachedHandler()
michael@0 608 {
michael@0 609 if (mNextBinding)
michael@0 610 mNextBinding->ExecuteAttachedHandler();
michael@0 611
michael@0 612 if (AllowScripts())
michael@0 613 mPrototypeBinding->BindingAttached(mBoundElement);
michael@0 614 }
michael@0 615
michael@0 616 void
michael@0 617 nsXBLBinding::ExecuteDetachedHandler()
michael@0 618 {
michael@0 619 if (AllowScripts())
michael@0 620 mPrototypeBinding->BindingDetached(mBoundElement);
michael@0 621
michael@0 622 if (mNextBinding)
michael@0 623 mNextBinding->ExecuteDetachedHandler();
michael@0 624 }
michael@0 625
michael@0 626 void
michael@0 627 nsXBLBinding::UnhookEventHandlers()
michael@0 628 {
michael@0 629 nsXBLPrototypeHandler* handlerChain = mPrototypeBinding->GetPrototypeHandlers();
michael@0 630
michael@0 631 if (handlerChain) {
michael@0 632 EventListenerManager* manager = mBoundElement->GetExistingListenerManager();
michael@0 633 if (!manager) {
michael@0 634 return;
michael@0 635 }
michael@0 636
michael@0 637 bool isChromeBinding = mPrototypeBinding->IsChrome();
michael@0 638 nsXBLPrototypeHandler* curr;
michael@0 639 for (curr = handlerChain; curr; curr = curr->GetNextHandler()) {
michael@0 640 nsXBLEventHandler* handler = curr->GetCachedEventHandler();
michael@0 641 if (!handler) {
michael@0 642 continue;
michael@0 643 }
michael@0 644
michael@0 645 nsCOMPtr<nsIAtom> eventAtom = curr->GetEventName();
michael@0 646 if (!eventAtom ||
michael@0 647 eventAtom == nsGkAtoms::keyup ||
michael@0 648 eventAtom == nsGkAtoms::keydown ||
michael@0 649 eventAtom == nsGkAtoms::keypress)
michael@0 650 continue;
michael@0 651
michael@0 652 // Figure out if we're using capturing or not.
michael@0 653 EventListenerFlags flags;
michael@0 654 flags.mCapture = (curr->GetPhase() == NS_PHASE_CAPTURING);
michael@0 655
michael@0 656 // If this is a command, remove it from the system event group,
michael@0 657 // otherwise remove it from the standard event group.
michael@0 658
michael@0 659 if ((curr->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND |
michael@0 660 NS_HANDLER_TYPE_SYSTEM)) &&
michael@0 661 (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) {
michael@0 662 flags.mInSystemGroup = true;
michael@0 663 }
michael@0 664
michael@0 665 manager->RemoveEventListenerByType(handler,
michael@0 666 nsDependentAtomString(eventAtom),
michael@0 667 flags);
michael@0 668 }
michael@0 669
michael@0 670 const nsCOMArray<nsXBLKeyEventHandler>* keyHandlers =
michael@0 671 mPrototypeBinding->GetKeyEventHandlers();
michael@0 672 int32_t i;
michael@0 673 for (i = 0; i < keyHandlers->Count(); ++i) {
michael@0 674 nsXBLKeyEventHandler* handler = keyHandlers->ObjectAt(i);
michael@0 675
michael@0 676 nsAutoString type;
michael@0 677 handler->GetEventName(type);
michael@0 678
michael@0 679 // Figure out if we're using capturing or not.
michael@0 680 EventListenerFlags flags;
michael@0 681 flags.mCapture = (handler->GetPhase() == NS_PHASE_CAPTURING);
michael@0 682
michael@0 683 // If this is a command, remove it from the system event group, otherwise
michael@0 684 // remove it from the standard event group.
michael@0 685
michael@0 686 if ((handler->GetType() & (NS_HANDLER_TYPE_XBL_COMMAND | NS_HANDLER_TYPE_SYSTEM)) &&
michael@0 687 (isChromeBinding || mBoundElement->IsInNativeAnonymousSubtree())) {
michael@0 688 flags.mInSystemGroup = true;
michael@0 689 }
michael@0 690
michael@0 691 manager->RemoveEventListenerByType(handler, type, flags);
michael@0 692 }
michael@0 693 }
michael@0 694 }
michael@0 695
michael@0 696 static void
michael@0 697 UpdateInsertionParent(XBLChildrenElement* aPoint,
michael@0 698 nsIContent* aOldBoundElement)
michael@0 699 {
michael@0 700 if (aPoint->IsDefaultInsertion()) {
michael@0 701 return;
michael@0 702 }
michael@0 703
michael@0 704 for (size_t i = 0; i < aPoint->InsertedChildrenLength(); ++i) {
michael@0 705 nsIContent* child = aPoint->mInsertedChildren[i];
michael@0 706
michael@0 707 MOZ_ASSERT(child->GetParentNode());
michael@0 708
michael@0 709 // Here, we're iterating children that we inserted. There are two cases:
michael@0 710 // either |child| is an explicit child of |aOldBoundElement| and is no
michael@0 711 // longer inserted anywhere or it's a child of a <children> element
michael@0 712 // parented to |aOldBoundElement|. In the former case, the child is no
michael@0 713 // longer inserted anywhere, so we set its insertion parent to null. In the
michael@0 714 // latter case, the child is now inserted into |aOldBoundElement| from some
michael@0 715 // binding above us, so we set its insertion parent to aOldBoundElement.
michael@0 716 if (child->GetParentNode() == aOldBoundElement) {
michael@0 717 child->SetXBLInsertionParent(nullptr);
michael@0 718 } else {
michael@0 719 child->SetXBLInsertionParent(aOldBoundElement);
michael@0 720 }
michael@0 721 }
michael@0 722 }
michael@0 723
michael@0 724 void
michael@0 725 nsXBLBinding::ChangeDocument(nsIDocument* aOldDocument, nsIDocument* aNewDocument)
michael@0 726 {
michael@0 727 if (aOldDocument == aNewDocument)
michael@0 728 return;
michael@0 729
michael@0 730 // Now the binding dies. Unhook our prototypes.
michael@0 731 if (mPrototypeBinding->HasImplementation()) {
michael@0 732 nsCOMPtr<nsIScriptGlobalObject> global = do_QueryInterface(
michael@0 733 aOldDocument->GetScopeObject());
michael@0 734 if (global) {
michael@0 735 nsCOMPtr<nsIScriptContext> context = global->GetContext();
michael@0 736 if (context) {
michael@0 737 JSContext *cx = context->GetNativeContext();
michael@0 738
michael@0 739 nsCxPusher pusher;
michael@0 740 pusher.Push(cx);
michael@0 741
michael@0 742 // scope might be null if we've cycle-collected the global
michael@0 743 // object, since the Unlink phase of cycle collection happens
michael@0 744 // after JS GC finalization. But in that case, we don't care
michael@0 745 // about fixing the prototype chain, since everything's going
michael@0 746 // away immediately.
michael@0 747 JS::Rooted<JSObject*> scope(cx, global->GetGlobalJSObject());
michael@0 748 JS::Rooted<JSObject*> scriptObject(cx, mBoundElement->GetWrapper());
michael@0 749 if (scope && scriptObject) {
michael@0 750 // XXX Stay in sync! What if a layered binding has an
michael@0 751 // <interface>?!
michael@0 752 // XXXbz what does that comment mean, really? It seems to date
michael@0 753 // back to when there was such a thing as an <interface>, whever
michael@0 754 // that was...
michael@0 755
michael@0 756 // Find the right prototype.
michael@0 757 JSAutoCompartment ac(cx, scriptObject);
michael@0 758
michael@0 759 JS::Rooted<JSObject*> base(cx, scriptObject);
michael@0 760 JS::Rooted<JSObject*> proto(cx);
michael@0 761 for ( ; true; base = proto) { // Will break out on null proto
michael@0 762 if (!JS_GetPrototype(cx, base, &proto)) {
michael@0 763 return;
michael@0 764 }
michael@0 765 if (!proto) {
michael@0 766 break;
michael@0 767 }
michael@0 768
michael@0 769 if (JS_GetClass(proto) != &gPrototypeJSClass) {
michael@0 770 // Clearly not the right class
michael@0 771 continue;
michael@0 772 }
michael@0 773
michael@0 774 nsRefPtr<nsXBLDocumentInfo> docInfo =
michael@0 775 static_cast<nsXBLDocumentInfo*>(::JS_GetPrivate(proto));
michael@0 776 if (!docInfo) {
michael@0 777 // Not the proto we seek
michael@0 778 continue;
michael@0 779 }
michael@0 780
michael@0 781 JS::Value protoBinding = ::JS_GetReservedSlot(proto, 0);
michael@0 782
michael@0 783 if (JSVAL_TO_PRIVATE(protoBinding) != mPrototypeBinding) {
michael@0 784 // Not the right binding
michael@0 785 continue;
michael@0 786 }
michael@0 787
michael@0 788 // Alright! This is the right prototype. Pull it out of the
michael@0 789 // proto chain.
michael@0 790 JS::Rooted<JSObject*> grandProto(cx);
michael@0 791 if (!JS_GetPrototype(cx, proto, &grandProto)) {
michael@0 792 return;
michael@0 793 }
michael@0 794 ::JS_SetPrototype(cx, base, grandProto);
michael@0 795 break;
michael@0 796 }
michael@0 797
michael@0 798 mPrototypeBinding->UndefineFields(cx, scriptObject);
michael@0 799
michael@0 800 // Don't remove the reference from the document to the
michael@0 801 // wrapper here since it'll be removed by the element
michael@0 802 // itself when that's taken out of the document.
michael@0 803 }
michael@0 804 }
michael@0 805 }
michael@0 806 }
michael@0 807
michael@0 808 // Remove our event handlers
michael@0 809 UnhookEventHandlers();
michael@0 810
michael@0 811 {
michael@0 812 nsAutoScriptBlocker scriptBlocker;
michael@0 813
michael@0 814 // Then do our ancestors. This reverses the construction order, so that at
michael@0 815 // all times things are consistent as far as everyone is concerned.
michael@0 816 if (mNextBinding) {
michael@0 817 mNextBinding->ChangeDocument(aOldDocument, aNewDocument);
michael@0 818 }
michael@0 819
michael@0 820 // Update the anonymous content.
michael@0 821 // XXXbz why not only for style bindings?
michael@0 822 if (mContent) {
michael@0 823 nsXBLBinding::UninstallAnonymousContent(aOldDocument, mContent);
michael@0 824 }
michael@0 825
michael@0 826 // Now that we've unbound our anonymous content from the tree and updated
michael@0 827 // its binding parent, update the insertion parent for content inserted
michael@0 828 // into our <children> elements.
michael@0 829 if (mDefaultInsertionPoint) {
michael@0 830 UpdateInsertionParent(mDefaultInsertionPoint, mBoundElement);
michael@0 831 }
michael@0 832
michael@0 833 for (size_t i = 0; i < mInsertionPoints.Length(); ++i) {
michael@0 834 UpdateInsertionParent(mInsertionPoints[i], mBoundElement);
michael@0 835 }
michael@0 836
michael@0 837 // Now that our inserted children no longer think they're inserted
michael@0 838 // anywhere, make sure our internal state reflects that as well.
michael@0 839 ClearInsertionPoints();
michael@0 840 }
michael@0 841 }
michael@0 842
michael@0 843 bool
michael@0 844 nsXBLBinding::InheritsStyle() const
michael@0 845 {
michael@0 846 // XXX Will have to change if we ever allow multiple bindings to contribute anonymous content.
michael@0 847 // Most derived binding with anonymous content determines style inheritance for now.
michael@0 848
michael@0 849 // XXX What about bindings with <content> but no kids, e.g., my treecell-text binding?
michael@0 850 if (mContent)
michael@0 851 return mPrototypeBinding->InheritsStyle();
michael@0 852
michael@0 853 if (mNextBinding)
michael@0 854 return mNextBinding->InheritsStyle();
michael@0 855
michael@0 856 return true;
michael@0 857 }
michael@0 858
michael@0 859 void
michael@0 860 nsXBLBinding::WalkRules(nsIStyleRuleProcessor::EnumFunc aFunc, void* aData)
michael@0 861 {
michael@0 862 if (mNextBinding)
michael@0 863 mNextBinding->WalkRules(aFunc, aData);
michael@0 864
michael@0 865 nsIStyleRuleProcessor *rules = mPrototypeBinding->GetRuleProcessor();
michael@0 866 if (rules)
michael@0 867 (*aFunc)(rules, aData);
michael@0 868 }
michael@0 869
michael@0 870 // Internal helper methods ////////////////////////////////////////////////////////////////
michael@0 871
michael@0 872 // Get or create a WeakMap object on a given XBL-hosting global.
michael@0 873 //
michael@0 874 // The scheme is as follows. XBL-hosting globals (either privileged content
michael@0 875 // Windows or XBL scopes) get two lazily-defined WeakMap properties. Each
michael@0 876 // WeakMap is keyed by the grand-proto - i.e. the original prototype of the
michael@0 877 // content before it was bound, and the prototype of the class object that we
michael@0 878 // splice in. The values in the WeakMap are simple dictionary-style objects,
michael@0 879 // mapping from XBL class names to class objects.
michael@0 880 static JSObject*
michael@0 881 GetOrCreateClassObjectMap(JSContext *cx, JS::Handle<JSObject*> scope, const char *mapName)
michael@0 882 {
michael@0 883 AssertSameCompartment(cx, scope);
michael@0 884 MOZ_ASSERT(JS_IsGlobalObject(scope));
michael@0 885 MOZ_ASSERT(scope == xpc::GetXBLScopeOrGlobal(cx, scope));
michael@0 886
michael@0 887 // First, see if the map is already defined.
michael@0 888 JS::Rooted<JSPropertyDescriptor> desc(cx);
michael@0 889 if (!JS_GetOwnPropertyDescriptor(cx, scope, mapName, &desc)) {
michael@0 890 return nullptr;
michael@0 891 }
michael@0 892 if (desc.object() && desc.value().isObject() &&
michael@0 893 JS::IsWeakMapObject(&desc.value().toObject())) {
michael@0 894 return &desc.value().toObject();
michael@0 895 }
michael@0 896
michael@0 897 // It's not there. Create and define it.
michael@0 898 JS::Rooted<JSObject*> map(cx, JS::NewWeakMapObject(cx));
michael@0 899 if (!map || !JS_DefineProperty(cx, scope, mapName, map,
michael@0 900 JSPROP_PERMANENT | JSPROP_READONLY,
michael@0 901 JS_PropertyStub, JS_StrictPropertyStub))
michael@0 902 {
michael@0 903 return nullptr;
michael@0 904 }
michael@0 905 return map;
michael@0 906 }
michael@0 907
michael@0 908 static JSObject*
michael@0 909 GetOrCreateMapEntryForPrototype(JSContext *cx, JS::Handle<JSObject*> proto)
michael@0 910 {
michael@0 911 AssertSameCompartment(cx, proto);
michael@0 912 // We want to hang our class objects off the XBL scope. But since we also
michael@0 913 // hoist anonymous content into the XBL scope, this creates the potential for
michael@0 914 // tricky collisions, since we can simultaneously have a bound in-content
michael@0 915 // node with grand-proto HTMLDivElement and a bound anonymous node whose
michael@0 916 // grand-proto is the XBL scope's cross-compartment wrapper to HTMLDivElement.
michael@0 917 // Since we have to wrap the WeakMap keys into its scope, this distinction
michael@0 918 // would be lost if we don't do something about it.
michael@0 919 //
michael@0 920 // So we define two maps - one class objects that live in content (prototyped
michael@0 921 // to content prototypes), and the other for class objects that live in the
michael@0 922 // XBL scope (prototyped to cross-compartment-wrapped content prototypes).
michael@0 923 const char* name = xpc::IsInXBLScope(proto) ? "__ContentClassObjectMap__"
michael@0 924 : "__XBLClassObjectMap__";
michael@0 925
michael@0 926 // Now, enter the XBL scope, since that's where we need to operate, and wrap
michael@0 927 // the proto accordingly.
michael@0 928 JS::Rooted<JSObject*> scope(cx, xpc::GetXBLScopeOrGlobal(cx, proto));
michael@0 929 NS_ENSURE_TRUE(scope, nullptr);
michael@0 930 JS::Rooted<JSObject*> wrappedProto(cx, proto);
michael@0 931 JSAutoCompartment ac(cx, scope);
michael@0 932 if (!JS_WrapObject(cx, &wrappedProto)) {
michael@0 933 return nullptr;
michael@0 934 }
michael@0 935
michael@0 936 // Grab the appropriate WeakMap.
michael@0 937 JS::Rooted<JSObject*> map(cx, GetOrCreateClassObjectMap(cx, scope, name));
michael@0 938 if (!map) {
michael@0 939 return nullptr;
michael@0 940 }
michael@0 941
michael@0 942 // See if we already have a map entry for that prototype.
michael@0 943 JS::Rooted<JS::Value> val(cx);
michael@0 944 if (!JS::GetWeakMapEntry(cx, map, wrappedProto, &val)) {
michael@0 945 return nullptr;
michael@0 946 }
michael@0 947 if (val.isObject()) {
michael@0 948 return &val.toObject();
michael@0 949 }
michael@0 950
michael@0 951 // We don't have an entry. Create one and stick it in the map.
michael@0 952 JS::Rooted<JSObject*> entry(cx);
michael@0 953 entry = JS_NewObjectWithGivenProto(cx, nullptr, JS::NullPtr(), scope);
michael@0 954 if (!entry) {
michael@0 955 return nullptr;
michael@0 956 }
michael@0 957 JS::Rooted<JS::Value> entryVal(cx, JS::ObjectValue(*entry));
michael@0 958 if (!JS::SetWeakMapEntry(cx, map, wrappedProto, entryVal)) {
michael@0 959 NS_WARNING("SetWeakMapEntry failed, probably due to non-preservable WeakMap "
michael@0 960 "key. XBL binding will fail for this element.");
michael@0 961 return nullptr;
michael@0 962 }
michael@0 963 return entry;
michael@0 964 }
michael@0 965
michael@0 966 // static
michael@0 967 nsresult
michael@0 968 nsXBLBinding::DoInitJSClass(JSContext *cx,
michael@0 969 JS::Handle<JSObject*> obj,
michael@0 970 const nsAFlatCString& aClassName,
michael@0 971 nsXBLPrototypeBinding* aProtoBinding,
michael@0 972 JS::MutableHandle<JSObject*> aClassObject,
michael@0 973 bool* aNew)
michael@0 974 {
michael@0 975 MOZ_ASSERT(obj);
michael@0 976
michael@0 977 // Note that, now that NAC reflectors are created in the XBL scope, the
michael@0 978 // reflector is not necessarily same-compartment with the document. So we'll
michael@0 979 // end up creating a separate instance of the oddly-named XBL class object
michael@0 980 // and defining it as a property on the XBL scope's global. This works fine,
michael@0 981 // but we need to make sure never to assume that the the reflector and
michael@0 982 // prototype are same-compartment with the bound document.
michael@0 983 JS::Rooted<JSObject*> global(cx, js::GetGlobalForObjectCrossCompartment(obj));
michael@0 984 JS::Rooted<JSObject*> xblScope(cx, xpc::GetXBLScopeOrGlobal(cx, global));
michael@0 985 NS_ENSURE_TRUE(xblScope, NS_ERROR_UNEXPECTED);
michael@0 986
michael@0 987 JS::Rooted<JSObject*> parent_proto(cx);
michael@0 988 if (!JS_GetPrototype(cx, obj, &parent_proto)) {
michael@0 989 return NS_ERROR_FAILURE;
michael@0 990 }
michael@0 991
michael@0 992 // Get the map entry for the parent prototype. In the one-off case that the
michael@0 993 // parent prototype is null, we somewhat hackily just use the WeakMap itself
michael@0 994 // as a property holder.
michael@0 995 JS::Rooted<JSObject*> holder(cx);
michael@0 996 if (parent_proto) {
michael@0 997 holder = GetOrCreateMapEntryForPrototype(cx, parent_proto);
michael@0 998 } else {
michael@0 999 JSAutoCompartment innerAC(cx, xblScope);
michael@0 1000 holder = GetOrCreateClassObjectMap(cx, xblScope, "__ContentClassObjectMap__");
michael@0 1001 }
michael@0 1002 if (NS_WARN_IF(!holder)) {
michael@0 1003 return NS_ERROR_FAILURE;
michael@0 1004 }
michael@0 1005 js::AssertSameCompartment(holder, xblScope);
michael@0 1006 JSAutoCompartment ac(cx, holder);
michael@0 1007
michael@0 1008 // Look up the class on the property holder. The only properties on the
michael@0 1009 // holder should be class objects. If we don't find the class object, we need
michael@0 1010 // to create and define it.
michael@0 1011 JS::Rooted<JSObject*> proto(cx);
michael@0 1012 JS::Rooted<JSPropertyDescriptor> desc(cx);
michael@0 1013 if (!JS_GetOwnPropertyDescriptor(cx, holder, aClassName.get(), &desc)) {
michael@0 1014 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1015 }
michael@0 1016 *aNew = !desc.object();
michael@0 1017 if (desc.object()) {
michael@0 1018 proto = &desc.value().toObject();
michael@0 1019 MOZ_ASSERT(JS_GetClass(js::UncheckedUnwrap(proto)) == &gPrototypeJSClass);
michael@0 1020 } else {
michael@0 1021
michael@0 1022 // We need to create the prototype. First, enter the compartment where it's
michael@0 1023 // going to live, and create it.
michael@0 1024 JSAutoCompartment ac2(cx, global);
michael@0 1025 proto = JS_NewObjectWithGivenProto(cx, &gPrototypeJSClass, parent_proto, global);
michael@0 1026 if (!proto) {
michael@0 1027 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1028 }
michael@0 1029
michael@0 1030 // Keep this proto binding alive while we're alive. Do this first so that
michael@0 1031 // we can guarantee that in XBLFinalize this will be non-null.
michael@0 1032 // Note that we can't just store aProtoBinding in the private and
michael@0 1033 // addref/release the nsXBLDocumentInfo through it, because cycle
michael@0 1034 // collection doesn't seem to work right if the private is not an
michael@0 1035 // nsISupports.
michael@0 1036 nsXBLDocumentInfo* docInfo = aProtoBinding->XBLDocumentInfo();
michael@0 1037 ::JS_SetPrivate(proto, docInfo);
michael@0 1038 NS_ADDREF(docInfo);
michael@0 1039 JS_SetReservedSlot(proto, 0, PRIVATE_TO_JSVAL(aProtoBinding));
michael@0 1040
michael@0 1041 // Next, enter the compartment of the property holder, wrap the proto, and
michael@0 1042 // stick it on.
michael@0 1043 JSAutoCompartment ac3(cx, holder);
michael@0 1044 if (!JS_WrapObject(cx, &proto) ||
michael@0 1045 !JS_DefineProperty(cx, holder, aClassName.get(), proto,
michael@0 1046 JSPROP_READONLY | JSPROP_PERMANENT,
michael@0 1047 JS_PropertyStub, JS_StrictPropertyStub))
michael@0 1048 {
michael@0 1049 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1050 }
michael@0 1051 }
michael@0 1052
michael@0 1053 // Whew. We have the proto. Wrap it back into the compartment of |obj|,
michael@0 1054 // splice it in, and return it.
michael@0 1055 JSAutoCompartment ac4(cx, obj);
michael@0 1056 if (!JS_WrapObject(cx, &proto) || !JS_SetPrototype(cx, obj, proto)) {
michael@0 1057 return NS_ERROR_FAILURE;
michael@0 1058 }
michael@0 1059 aClassObject.set(proto);
michael@0 1060 return NS_OK;
michael@0 1061 }
michael@0 1062
michael@0 1063 bool
michael@0 1064 nsXBLBinding::AllowScripts()
michael@0 1065 {
michael@0 1066 return mBoundElement && mPrototypeBinding->GetAllowScripts();
michael@0 1067 }
michael@0 1068
michael@0 1069 nsXBLBinding*
michael@0 1070 nsXBLBinding::RootBinding()
michael@0 1071 {
michael@0 1072 if (mNextBinding)
michael@0 1073 return mNextBinding->RootBinding();
michael@0 1074
michael@0 1075 return this;
michael@0 1076 }
michael@0 1077
michael@0 1078 bool
michael@0 1079 nsXBLBinding::ResolveAllFields(JSContext *cx, JS::Handle<JSObject*> obj) const
michael@0 1080 {
michael@0 1081 if (!mPrototypeBinding->ResolveAllFields(cx, obj)) {
michael@0 1082 return false;
michael@0 1083 }
michael@0 1084
michael@0 1085 if (mNextBinding) {
michael@0 1086 return mNextBinding->ResolveAllFields(cx, obj);
michael@0 1087 }
michael@0 1088
michael@0 1089 return true;
michael@0 1090 }
michael@0 1091
michael@0 1092 bool
michael@0 1093 nsXBLBinding::LookupMember(JSContext* aCx, JS::Handle<jsid> aId,
michael@0 1094 JS::MutableHandle<JSPropertyDescriptor> aDesc)
michael@0 1095 {
michael@0 1096 // We should never enter this function with a pre-filled property descriptor.
michael@0 1097 MOZ_ASSERT(!aDesc.object());
michael@0 1098
michael@0 1099 // Get the string as an nsString before doing anything, so we can make
michael@0 1100 // convenient comparisons during our search.
michael@0 1101 if (!JSID_IS_STRING(aId)) {
michael@0 1102 return true;
michael@0 1103 }
michael@0 1104 nsDependentJSString name(aId);
michael@0 1105
michael@0 1106 // We have a weak reference to our bound element, so make sure it's alive.
michael@0 1107 if (!mBoundElement || !mBoundElement->GetWrapper()) {
michael@0 1108 return false;
michael@0 1109 }
michael@0 1110
michael@0 1111 // Get the scope of mBoundElement and the associated XBL scope. We should only
michael@0 1112 // be calling into this machinery if we're running in a separate XBL scope.
michael@0 1113 //
michael@0 1114 // Note that we only end up in LookupMember for XrayWrappers from XBL scopes
michael@0 1115 // into content. So for NAC reflectors that live in the XBL scope, we should
michael@0 1116 // never get here. But on the off-chance that someone adds new callsites to
michael@0 1117 // LookupMember, we do a release-mode assertion as belt-and-braces.
michael@0 1118 // We do a release-mode assertion here to be extra safe.
michael@0 1119 JS::Rooted<JSObject*> boundScope(aCx,
michael@0 1120 js::GetGlobalForObjectCrossCompartment(mBoundElement->GetWrapper()));
michael@0 1121 MOZ_RELEASE_ASSERT(!xpc::IsInXBLScope(boundScope));
michael@0 1122 JS::Rooted<JSObject*> xblScope(aCx, xpc::GetXBLScope(aCx, boundScope));
michael@0 1123 NS_ENSURE_TRUE(xblScope, false);
michael@0 1124 MOZ_ASSERT(boundScope != xblScope);
michael@0 1125
michael@0 1126 // Enter the xbl scope and invoke the internal version.
michael@0 1127 {
michael@0 1128 JSAutoCompartment ac(aCx, xblScope);
michael@0 1129 JS::Rooted<jsid> id(aCx, aId);
michael@0 1130 if (!JS_WrapId(aCx, &id) ||
michael@0 1131 !LookupMemberInternal(aCx, name, id, aDesc, xblScope))
michael@0 1132 {
michael@0 1133 return false;
michael@0 1134 }
michael@0 1135 }
michael@0 1136
michael@0 1137 // Wrap into the caller's scope.
michael@0 1138 return JS_WrapPropertyDescriptor(aCx, aDesc);
michael@0 1139 }
michael@0 1140
michael@0 1141 bool
michael@0 1142 nsXBLBinding::LookupMemberInternal(JSContext* aCx, nsString& aName,
michael@0 1143 JS::Handle<jsid> aNameAsId,
michael@0 1144 JS::MutableHandle<JSPropertyDescriptor> aDesc,
michael@0 1145 JS::Handle<JSObject*> aXBLScope)
michael@0 1146 {
michael@0 1147 // First, see if we have an implementation. If we don't, it means that this
michael@0 1148 // binding doesn't have a class object, and thus doesn't have any members.
michael@0 1149 // Skip it.
michael@0 1150 if (!PrototypeBinding()->HasImplementation()) {
michael@0 1151 if (!mNextBinding) {
michael@0 1152 return true;
michael@0 1153 }
michael@0 1154 return mNextBinding->LookupMemberInternal(aCx, aName, aNameAsId,
michael@0 1155 aDesc, aXBLScope);
michael@0 1156 }
michael@0 1157
michael@0 1158 // Find our class object. It's in a protected scope and permanent just in case,
michael@0 1159 // so should be there no matter what.
michael@0 1160 JS::Rooted<JS::Value> classObject(aCx);
michael@0 1161 if (!JS_GetProperty(aCx, aXBLScope, PrototypeBinding()->ClassName().get(),
michael@0 1162 &classObject)) {
michael@0 1163 return false;
michael@0 1164 }
michael@0 1165
michael@0 1166 // The bound element may have been adoped by a document and have a different
michael@0 1167 // wrapper (and different xbl scope) than when the binding was applied, in
michael@0 1168 // this case getting the class object will fail. Behave as if the class
michael@0 1169 // object did not exist.
michael@0 1170 if (classObject.isUndefined()) {
michael@0 1171 return true;
michael@0 1172 }
michael@0 1173
michael@0 1174 MOZ_ASSERT(classObject.isObject());
michael@0 1175
michael@0 1176 // Look for the property on this binding. If it's not there, try the next
michael@0 1177 // binding on the chain.
michael@0 1178 nsXBLProtoImpl* impl = mPrototypeBinding->GetImplementation();
michael@0 1179 JS::Rooted<JSObject*> object(aCx, &classObject.toObject());
michael@0 1180 if (impl && !impl->LookupMember(aCx, aName, aNameAsId, aDesc, object)) {
michael@0 1181 return false;
michael@0 1182 }
michael@0 1183 if (aDesc.object() || !mNextBinding) {
michael@0 1184 return true;
michael@0 1185 }
michael@0 1186
michael@0 1187 return mNextBinding->LookupMemberInternal(aCx, aName, aNameAsId, aDesc,
michael@0 1188 aXBLScope);
michael@0 1189 }
michael@0 1190
michael@0 1191 bool
michael@0 1192 nsXBLBinding::HasField(nsString& aName)
michael@0 1193 {
michael@0 1194 // See if this binding has such a field.
michael@0 1195 return mPrototypeBinding->FindField(aName) ||
michael@0 1196 (mNextBinding && mNextBinding->HasField(aName));
michael@0 1197 }
michael@0 1198
michael@0 1199 void
michael@0 1200 nsXBLBinding::MarkForDeath()
michael@0 1201 {
michael@0 1202 mMarkedForDeath = true;
michael@0 1203 ExecuteDetachedHandler();
michael@0 1204 }
michael@0 1205
michael@0 1206 bool
michael@0 1207 nsXBLBinding::ImplementsInterface(REFNSIID aIID) const
michael@0 1208 {
michael@0 1209 return mPrototypeBinding->ImplementsInterface(aIID) ||
michael@0 1210 (mNextBinding && mNextBinding->ImplementsInterface(aIID));
michael@0 1211 }

mercurial