Backed out 5 changesets (bug 1426525) for bustage at build/src/layout/xul/tree/nsTreeContentView.cpp r=backout on a CLOSED TREE
authorCoroiu Cristina <ccoroiu@mozilla.com>
Fri, 05 Jan 2018 21:40:44 +0200
changeset 449824 44d2697ba0a3456e3016e356abb4663b6aff1a9b
parent 449823 6d0debde5c246371ca8771c6b3e78291fc53939d
child 449825 e2501f2e295ebfc91ed5db1b0c16f70f443669f3
push id8527
push userCallek@gmail.com
push dateThu, 11 Jan 2018 21:05:50 +0000
treeherdermozilla-beta@95342d212a7a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbackout
bugs1426525
milestone59.0a1
backs out96efa1b6f4d5266a3c5cabc24ac46ca701986b8a
37fdd4a04f4e2dee47589d9fbca4ed88c445d306
cee58572336892ceffdbb81847ce4b936e4abd57
85e218bf000cee9d184c1b939f684ca19a60deb4
501c70abf837bf4b97b6b5773af1999ffcd76072
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 5 changesets (bug 1426525) for bustage at build/src/layout/xul/tree/nsTreeContentView.cpp r=backout on a CLOSED TREE Backed out changeset 96efa1b6f4d5 (bug 1426525) Backed out changeset 37fdd4a04f4e (bug 1426525) Backed out changeset cee585723368 (bug 1426525) Backed out changeset 85e218bf000c (bug 1426525) Backed out changeset 501c70abf837 (bug 1426525)
dom/base/DocumentOrShadowRoot.cpp
dom/base/nsDocument.cpp
dom/base/nsGkAtomList.h
dom/base/nsIdentifierMapEntry.h
dom/xul/XULDocument.cpp
dom/xul/XULDocument.h
dom/xul/nsIXULDocument.h
dom/xul/nsXULElement.h
layout/xul/tree/nsTreeContentView.cpp
--- a/dom/base/DocumentOrShadowRoot.cpp
+++ b/dom/base/DocumentOrShadowRoot.cpp
@@ -1,16 +1,17 @@
 /* -*- 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 "DocumentOrShadowRoot.h"
 #include "mozilla/dom/StyleSheetList.h"
+#include "XULDocument.h"
 
 namespace mozilla {
 namespace dom {
 
 DocumentOrShadowRoot::DocumentOrShadowRoot(mozilla::dom::ShadowRoot& aShadowRoot)
   : mAsNode(aShadowRoot)
   , mKind(Kind::ShadowRoot)
 {}
@@ -38,16 +39,21 @@ DocumentOrShadowRoot::GetElementById(con
   }
 
   if (nsIdentifierMapEntry* entry = mIdentifierMap.GetEntry(aElementId)) {
     if (Element* el = entry->GetIdElement()) {
       return el;
     }
   }
 
+  if (MOZ_UNLIKELY(mKind == Kind::Document &&
+      static_cast<nsIDocument&>(AsNode()).IsXULDocument())) {
+    return static_cast<XULDocument&>(AsNode()).GetRefById(aElementId);
+  }
+
   return nullptr;
 }
 
 already_AddRefed<nsContentList>
 DocumentOrShadowRoot::GetElementsByTagNameNS(const nsAString& aNamespaceURI,
                                    const nsAString& aLocalName)
 {
   ErrorResult rv;
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -440,16 +440,24 @@ nsIdentifierMapEntry::GetIdElement()
 
 Element*
 nsIdentifierMapEntry::GetImageIdElement()
 {
   return mImageElement ? mImageElement.get() : GetIdElement();
 }
 
 void
+nsIdentifierMapEntry::AppendAllIdContent(nsCOMArray<Element>* aElements)
+{
+  for (Element* element : mIdContentList) {
+    aElements->AppendObject(element);
+  }
+}
+
+void
 nsIdentifierMapEntry::AddContentChangeCallback(nsIDocument::IDTargetObserver aCallback,
                                                void* aData, bool aForImage)
 {
   if (!mChangeCallbacks) {
     mChangeCallbacks = new nsTHashtable<ChangeCallbackEntry>;
   }
 
   ChangeCallback cc = { aCallback, aData, aForImage };
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -1154,16 +1154,17 @@ GK_ATOM(query, "query")
 GK_ATOM(queryset, "queryset")
 GK_ATOM(querytype, "querytype")
 GK_ATOM(radio, "radio")
 GK_ATOM(radiogroup, "radiogroup")
 GK_ATOM(range, "range")
 GK_ATOM(readonly, "readonly")
 GK_ATOM(rect, "rect")
 GK_ATOM(rectangle, "rectangle")
+GK_ATOM(ref, "ref")
 GK_ATOM(refresh, "refresh")
 GK_ATOM(rel, "rel")
 GK_ATOM(onreloadpage, "onreloadpage")
 GK_ATOM(rem, "rem")
 GK_ATOM(remote, "remote")
 GK_ATOM(removeelement, "removeelement")
 GK_ATOM(renderingobserverlist, "renderingobserverlist")
 GK_ATOM(repeat, "repeat")
--- a/dom/base/nsIdentifierMapEntry.h
+++ b/dom/base/nsIdentifierMapEntry.h
@@ -12,16 +12,17 @@
 #define nsIdentifierMapEntry_h
 
 #include "PLDHashTable.h"
 
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Move.h"
 #include "mozilla/net/ReferrerPolicy.h"
 
+#include "nsCOMArray.h"
 #include "nsCOMPtr.h"
 #include "nsAtom.h"
 #include "nsTArray.h"
 #include "nsTHashtable.h"
 
 class nsIContent;
 class nsContentList;
 class nsBaseContentList;
@@ -134,16 +135,20 @@ public:
     return mIdContentList;
   }
   /**
    * If this entry has a non-null image element set (using SetImageElement),
    * the image element will be returned, otherwise the same as GetIdElement().
    */
   Element* GetImageIdElement();
   /**
+   * Append all the elements with this id to aElements
+   */
+  void AppendAllIdContent(nsCOMArray<Element>* aElements);
+  /**
    * This can fire ID change callbacks.
    * @return true if the content could be added, false if we failed due
    * to OOM.
    */
   bool AddIdElement(Element* aElement);
   /**
    * This can fire ID change callbacks.
    */
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -144,16 +144,46 @@ struct BroadcastListener {
 };
 
 struct BroadcasterMapEntry : public PLDHashEntryHdr
 {
     Element* mBroadcaster;  // [WEAK]
     nsTArray<BroadcastListener*> mListeners;  // [OWNING] of BroadcastListener objects
 };
 
+Element*
+nsRefMapEntry::GetFirstElement()
+{
+    return mRefContentList.SafeElementAt(0);
+}
+
+void
+nsRefMapEntry::AppendAll(nsCOMArray<Element>* aElements)
+{
+    for (size_t i = 0; i < mRefContentList.Length(); ++i) {
+        aElements->AppendObject(mRefContentList[i]);
+    }
+}
+
+bool
+nsRefMapEntry::AddElement(Element* aElement)
+{
+    if (mRefContentList.Contains(aElement)) {
+        return true;
+    }
+    return mRefContentList.AppendElement(aElement);
+}
+
+bool
+nsRefMapEntry::RemoveElement(Element* aElement)
+{
+    mRefContentList.RemoveElement(aElement);
+    return mRefContentList.IsEmpty();
+}
+
 //----------------------------------------------------------------------
 //
 // ctors & dtors
 //
 
 namespace mozilla {
 namespace dom {
 
@@ -576,20 +606,21 @@ ClearBroadcasterMapEntry(PLDHashTable* a
     // N.B. that we need to manually run the dtor because we
     // constructed the nsTArray object in-place.
     entry->mListeners.~nsTArray<BroadcastListener*>();
 }
 
 static bool
 CanBroadcast(int32_t aNameSpaceID, nsAtom* aAttribute)
 {
-    // Don't push changes to the |id|, |persist|, |command| or
+    // Don't push changes to the |id|, |ref|, |persist|, |command| or
     // |observes| attribute.
     if (aNameSpaceID == kNameSpaceID_None) {
         if ((aAttribute == nsGkAtoms::id) ||
+            (aAttribute == nsGkAtoms::ref) ||
             (aAttribute == nsGkAtoms::persist) ||
             (aAttribute == nsGkAtoms::command) ||
             (aAttribute == nsGkAtoms::observes)) {
             return false;
         }
     }
     return true;
 }
@@ -855,16 +886,34 @@ XULDocument::ExecuteOnBroadcastHandlerFo
             EventDispatcher::Dispatch(child, aPresContext, &event, nullptr,
                                       &status);
         }
     }
 
     return NS_OK;
 }
 
+void
+XULDocument::AttributeWillChange(nsIDocument* aDocument,
+                                 Element* aElement, int32_t aNameSpaceID,
+                                 nsAtom* aAttribute, int32_t aModType,
+                                 const nsAttrValue* aNewValue)
+{
+    MOZ_ASSERT(aElement, "Null content!");
+    NS_PRECONDITION(aAttribute, "Must have an attribute that's changing!");
+
+    // XXXbz check aNameSpaceID, dammit!
+    // See if we need to update our ref map.
+    if (aAttribute == nsGkAtoms::ref) {
+        // Might not need this, but be safe for now.
+        nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+        RemoveElementFromRefMap(aElement);
+    }
+}
+
 static bool
 ShouldPersistAttribute(Element* aElement, nsAtom* aAttribute)
 {
     if (aElement->IsXULElement(nsGkAtoms::window)) {
         // This is not an element of the top document, its owner is
         // not an nsXULWindow. Persist it.
         if (aElement->OwnerDoc()->GetParentDocument()) {
             return true;
@@ -888,16 +937,22 @@ XULDocument::AttributeChanged(nsIDocumen
                               nsAtom* aAttribute, int32_t aModType,
                               const nsAttrValue* aOldValue)
 {
     NS_ASSERTION(aDocument == this, "unexpected doc");
 
     // Might not need this, but be safe for now.
     nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
 
+    // XXXbz check aNameSpaceID, dammit!
+    // See if we need to update our ref map.
+    if (aAttribute == nsGkAtoms::ref) {
+        AddElementToRefMap(aElement);
+    }
+
     // Synchronize broadcast listeners
     if (mBroadcasterMap &&
         CanBroadcast(aNameSpaceID, aAttribute)) {
         auto entry = static_cast<BroadcasterMapEntry*>
                                 (mBroadcasterMap->Search(aElement));
 
         if (entry) {
             // We've got listeners: push the value.
@@ -1015,16 +1070,32 @@ XULDocument::ContentRemoved(nsIDocument*
     RemoveSubtreeFromDocument(aChild);
 }
 
 //----------------------------------------------------------------------
 //
 // nsIXULDocument interface
 //
 
+void
+XULDocument::GetElementsForID(const nsAString& aID,
+                              nsCOMArray<Element>& aElements)
+{
+    aElements.Clear();
+
+    nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aID);
+    if (entry) {
+        entry->AppendAllIdContent(&aElements);
+    }
+    nsRefMapEntry *refEntry = mRefMap.GetEntry(aID);
+    if (refEntry) {
+        refEntry->AppendAll(&aElements);
+    }
+}
+
 nsresult
 XULDocument::AddForwardReference(nsForwardReference* aRef)
 {
     if (mResolutionPhase < aRef->GetPhase()) {
         if (!mForwardReferences.AppendElement(aRef)) {
             delete aRef;
             return NS_ERROR_OUT_OF_MEMORY;
         }
@@ -1501,31 +1572,45 @@ XULDocument::SetTooltipNode(nsIDOMNode* 
 NS_IMETHODIMP
 XULDocument::GetCommandDispatcher(nsIDOMXULCommandDispatcher** aTracker)
 {
     *aTracker = mCommandDispatcher;
     NS_IF_ADDREF(*aTracker);
     return NS_OK;
 }
 
+Element*
+XULDocument::GetRefById(const nsAString& aID)
+{
+    if (nsRefMapEntry* refEntry = mRefMap.GetEntry(aID)) {
+        MOZ_ASSERT(refEntry->GetFirstElement());
+        return refEntry->GetFirstElement();
+    }
+
+    return nullptr;
+}
+
 nsresult
 XULDocument::AddElementToDocumentPre(Element* aElement)
 {
     // Do a bunch of work that's necessary when an element gets added
     // to the XUL Document.
     nsresult rv;
 
-    // 1. Add the element to the id map, since it seems this can be
-    // called when creating elements from prototypes.
+    // 1. Add the element to the resource-to-element map. Also add it to
+    // the id map, since it seems this can be called when creating
+    // elements from prototypes.
     nsAtom* id = aElement->GetID();
     if (id) {
         // FIXME: Shouldn't BindToTree take care of this?
         nsAutoScriptBlocker scriptBlocker;
         AddToIdTable(aElement, id);
     }
+    rv = AddElementToRefMap(aElement);
+    if (NS_FAILED(rv)) return rv;
 
     // 2. If the element is a 'command updater' (i.e., has a
     // "commandupdater='true'" attribute), then add the element to the
     // document's command dispatcher
     if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::commandupdater,
                               nsGkAtoms::_true, eCaseMatters)) {
         rv = nsXULContentUtils::SetCommandUpdater(this, aElement);
         if (NS_FAILED(rv)) return rv;
@@ -1616,18 +1701,20 @@ XULDocument::RemoveSubtreeFromDocument(n
          child;
          child = child->GetPreviousSibling()) {
 
         rv = RemoveSubtreeFromDocument(child);
         if (NS_FAILED(rv))
             return rv;
     }
 
-    // Remove the element from the id map, since we added it in
+    // 2. Remove the element from the resource-to-element map.
+    // Also remove it from the id map, since we added it in
     // AddElementToDocumentPre().
+    RemoveElementFromRefMap(aElement);
     nsAtom* id = aElement->GetID();
     if (id) {
         // FIXME: Shouldn't UnbindFromTree take care of this?
         nsAutoScriptBlocker scriptBlocker;
         RemoveFromIdTable(aElement, id);
     }
 
     // 3. If the element is a 'command updater', then remove the
@@ -1651,16 +1738,56 @@ XULDocument::RemoveSubtreeFromDocument(n
                          broadcasterID, attribute, getter_AddRefs(broadcaster));
     if (rv == NS_FINDBROADCASTER_FOUND) {
         RemoveBroadcastListenerFor(*broadcaster, *listener, attribute);
     }
 
     return NS_OK;
 }
 
+static void
+GetRefMapAttribute(Element* aElement, nsAutoString* aValue)
+{
+    aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::ref, *aValue);
+}
+
+nsresult
+XULDocument::AddElementToRefMap(Element* aElement)
+{
+    // Look at the element's 'ref' attribute, and if set,
+    // add an entry in the resource-to-element map to the element.
+    nsAutoString value;
+    GetRefMapAttribute(aElement, &value);
+    if (!value.IsEmpty()) {
+        nsRefMapEntry *entry = mRefMap.PutEntry(value);
+        if (!entry)
+            return NS_ERROR_OUT_OF_MEMORY;
+        if (!entry->AddElement(aElement))
+            return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    return NS_OK;
+}
+
+void
+XULDocument::RemoveElementFromRefMap(Element* aElement)
+{
+    // Remove the element from the resource-to-element map.
+    nsAutoString value;
+    GetRefMapAttribute(aElement, &value);
+    if (!value.IsEmpty()) {
+        nsRefMapEntry *entry = mRefMap.GetEntry(value);
+        if (!entry)
+            return;
+        if (entry->RemoveElement(aElement)) {
+            mRefMap.RemoveEntry(entry);
+        }
+    }
+}
+
 //----------------------------------------------------------------------
 //
 // nsIDOMNode interface
 //
 
 nsresult
 XULDocument::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
                    bool aPreallocateChildren) const
@@ -1875,29 +2002,19 @@ XULDocument::ApplyPersistentAttributesIn
 
         nsAutoString id;
         ids->GetNext(id);
 
         if (mRestrictPersistence && !mPersistenceIds.Contains(id)) {
             continue;
         }
 
-        nsIdentifierMapEntry* entry = mIdentifierMap.GetEntry(id);
-        if (!entry) {
-            continue;
-        }
-
-        // We want to hold strong refs to the elements while applying
-        // persistent attributes, just in case.
-        elements.Clear();
-        elements.SetCapacity(entry->GetIdElements().Length());
-        for (Element* element : entry->GetIdElements()) {
-            elements.AppendObject(element);
-        }
-        if (elements.IsEmpty()) {
+        // This will clear the array if there are no elements.
+        GetElementsForID(id, elements);
+        if (!elements.Count()) {
             continue;
         }
 
         rv = ApplyPersistentAttributesToElements(id, elements);
         if (NS_WARN_IF(NS_FAILED(rv))) {
             return rv;
         }
     }
@@ -2116,16 +2233,19 @@ XULDocument::PrepareToWalk()
     if (mState == eState_Master) {
         // Add the root element
         rv = CreateElementFromPrototype(proto, getter_AddRefs(root), true);
         if (NS_FAILED(rv)) return rv;
 
         rv = AppendChildTo(root, false);
         if (NS_FAILED(rv)) return rv;
 
+        rv = AddElementToRefMap(root);
+        if (NS_FAILED(rv)) return rv;
+
         // Block onload until we've finished building the complete
         // document content model.
         BlockOnload();
     }
 
     // There'd better not be anything on the context stack at this
     // point! This is the basis case for our "induction" in
     // ResumeWalk(), below, which'll assume that there's always a
--- a/dom/xul/XULDocument.h
+++ b/dom/xul/XULDocument.h
@@ -41,16 +41,49 @@ class nsIObjectOutputStream;
 #else
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 #include "nsXULElement.h"
 #endif
 #include "nsURIHashKey.h"
 #include "nsInterfaceHashtable.h"
 
+class nsRefMapEntry : public nsStringHashKey
+{
+public:
+  explicit nsRefMapEntry(const nsAString& aKey) :
+    nsStringHashKey(&aKey)
+  {
+  }
+  explicit nsRefMapEntry(const nsAString* aKey) :
+    nsStringHashKey(aKey)
+  {
+  }
+  nsRefMapEntry(const nsRefMapEntry& aOther) :
+    nsStringHashKey(&aOther.GetKey())
+  {
+    NS_ERROR("Should never be called");
+  }
+
+  mozilla::dom::Element* GetFirstElement();
+  void AppendAll(nsCOMArray<mozilla::dom::Element>* aElements);
+  /**
+   * @return true if aElement was added, false if we failed due to OOM
+   */
+  bool AddElement(mozilla::dom::Element* aElement);
+  /**
+   * @return true if aElement was removed and it was the last content for
+   * this ref, so this entry should be removed from the map
+   */
+  bool RemoveElement(mozilla::dom::Element* aElement);
+
+private:
+  nsTArray<mozilla::dom::Element*> mRefContentList;
+};
+
 /**
  * The XUL document class
  */
 
 namespace mozilla {
 namespace dom {
 
 class XULDocument final : public XMLDocument,
@@ -84,18 +117,22 @@ public:
 
     virtual void EndLoad() override;
 
     // nsIMutationObserver interface
     NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
     NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
     NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
     NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+    NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE
 
     // nsIXULDocument interface
+    virtual void GetElementsForID(const nsAString& aID,
+                                  nsCOMArray<mozilla::dom::Element>& aElements) override;
+
     NS_IMETHOD AddSubtreeToDocument(nsIContent* aContent) override;
     NS_IMETHOD RemoveSubtreeFromDocument(nsIContent* aContent) override;
     NS_IMETHOD OnPrototypeLoadDone(bool aResumeWalk) override;
     bool OnDocumentParserError() override;
 
     // nsINode interface overrides
     virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
                            bool aPreallocateChildren) const override;
@@ -111,16 +148,19 @@ public:
     using mozilla::dom::DocumentOrShadowRoot::GetElementById;
     using nsDocument::GetImplementation;
     using nsDocument::GetTitle;
     using nsDocument::SetTitle;
     using nsDocument::GetLastStyleSheetSet;
     using nsDocument::MozSetImageElement;
     using nsIDocument::GetLocation;
 
+    // Helper for StyleScope::GetElementById.
+    Element* GetRefById(const nsAString & elementId);
+
     // nsIDOMXULDocument interface
     NS_DECL_NSIDOMXULDOCUMENT
 
     // nsICSSLoaderObserver
     NS_IMETHOD StyleSheetLoaded(mozilla::StyleSheet* aSheet,
                                 bool aWasAlternate,
                                 nsresult aStatus) override;
 
@@ -188,16 +228,21 @@ protected:
 
     // Implementation methods
     friend nsresult
     (::NS_NewXULDocument(nsIXULDocument** aResult));
 
     nsresult Init(void) override;
     nsresult StartLayout(void);
 
+    nsresult
+    AddElementToRefMap(Element* aElement);
+    void
+    RemoveElementFromRefMap(Element* aElement);
+
     nsresult GetViewportSize(int32_t* aWidth, int32_t* aHeight);
 
     nsresult PrepareToLoad(nsISupports* aContainer,
                            const char* aCommand,
                            nsIChannel* aChannel,
                            nsILoadGroup* aLoadGroup,
                            nsIParser** aResult);
 
@@ -268,16 +313,19 @@ protected:
     // for owning pointers and raw COM interface pointers for weak
     // (ie, non owning) references. If you add any members to this
     // class, please make the ownership explicit (pinkerton, scc).
     // NOTE, THIS IS STILL IN PROGRESS, TALK TO PINK OR SCC BEFORE
     // CHANGING
 
     XULDocument*             mNextSrcLoadWaiter;  // [OWNER] but not COMPtr
 
+    // Tracks elements with a 'ref' attribute, or an 'id' attribute where
+    // the element's namespace has no registered ID attribute name.
+    nsTHashtable<nsRefMapEntry> mRefMap;
     nsCOMPtr<nsIXULStore>       mLocalStore;
     bool                        mApplyingPersistedAttrs;
     bool                        mIsWritingFastLoad;
     bool                        mDocumentLoaded;
     /**
      * Since ResumeWalk is interruptible, it's possible that last
      * stylesheet finishes loading while the PD walk is still in
      * progress (waiting for an overlay to finish loading).
--- a/dom/xul/nsIXULDocument.h
+++ b/dom/xul/nsIXULDocument.h
@@ -31,16 +31,24 @@ class Element;
  * method for constructing the children of a node, etc.
  */
 class nsIXULDocument : public nsISupports
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IXULDOCUMENT_IID)
 
   /**
+   * Get the elements for a particular resource --- all elements whose 'id'
+   * or 'ref' is aID. The nsCOMArray will be truncated and filled in with
+   * nsIContent pointers.
+   */
+  virtual void GetElementsForID(const nsAString& aID,
+                                nsCOMArray<mozilla::dom::Element>& aElements) = 0;
+
+  /**
    * Notify the XUL document that a subtree has been added
    */
   NS_IMETHOD AddSubtreeToDocument(nsIContent* aElement) = 0;
 
   /**
    * Notify the XUL document that a subtree has been removed
    */
   NS_IMETHOD RemoveSubtreeFromDocument(nsIContent* aElement) = 0;
--- a/dom/xul/nsXULElement.h
+++ b/dom/xul/nsXULElement.h
@@ -618,16 +618,24 @@ public:
     void GetTop(DOMString& aValue) const
     {
         GetXULAttr(nsGkAtoms::top, aValue);
     }
     void SetTop(const nsAString& aValue, mozilla::ErrorResult& rv)
     {
         SetXULAttr(nsGkAtoms::top, aValue, rv);
     }
+    void GetRef(DOMString& aValue) const
+    {
+        GetXULAttr(nsGkAtoms::ref, aValue);
+    }
+    void SetRef(const nsAString& aValue, mozilla::ErrorResult& rv)
+    {
+        SetXULAttr(nsGkAtoms::ref, aValue, rv);
+    }
     void GetTooltipText(DOMString& aValue) const
     {
         GetXULAttr(nsGkAtoms::tooltiptext, aValue);
     }
     void SetTooltipText(const nsAString& aValue, mozilla::ErrorResult& rv)
     {
         SetXULAttr(nsGkAtoms::tooltiptext, aValue, rv);
     }
--- a/layout/xul/tree/nsTreeContentView.cpp
+++ b/layout/xul/tree/nsTreeContentView.cpp
@@ -1068,17 +1068,18 @@ nsTreeContentView::AttributeChanged(nsID
         int32_t index = FindContent(parent);
         if (index >= 0 && mBoxObject) {
           mBoxObject->InvalidateRow(index);
         }
       }
     }
   }
   else if (aElement->IsXULElement(nsGkAtoms::treecell)) {
-    if (aAttribute == nsGkAtoms::properties ||
+    if (aAttribute == nsGkAtoms::ref ||
+        aAttribute == nsGkAtoms::properties ||
         aAttribute == nsGkAtoms::mode ||
         aAttribute == nsGkAtoms::src ||
         aAttribute == nsGkAtoms::value ||
         aAttribute == nsGkAtoms::label) {
       nsIContent* parent = aElement->GetParent();
       if (parent) {
         nsCOMPtr<nsIContent> grandParent = parent->GetParent();
         if (grandParent && grandParent->IsXULElement()) {
@@ -1565,26 +1566,32 @@ nsTreeContentView::UpdateParentIndexes(i
 }
 
 Element*
 nsTreeContentView::GetCell(nsIContent* aContainer, nsTreeColumn& aCol)
 {
   RefPtr<nsAtom> colAtom(aCol.GetAtom());
   int32_t colIndex(aCol.GetIndex());
 
-  // Traverse through cells, try to find the cell by index in a row.
+  // Traverse through cells, try to find the cell by "ref" attribute or by cell
+  // index in a row. "ref" attribute has higher priority.
   Element* result = nullptr;
   int32_t j = 0;
   dom::FlattenedChildIterator iter(aContainer);
   for (nsIContent* cell = iter.GetNextChild(); cell; cell = iter.GetNextChild()) {
     if (cell->IsXULElement(nsGkAtoms::treecell)) {
-      if (j == colIndex) {
+      if (colAtom &&
+          cell->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ref,
+                                         colAtom, eCaseMatters)) {
         result = cell->AsElement();
         break;
       }
+      else if (j == colIndex) {
+        result = cell->AsElement();
+      }
       j++;
     }
   }
 
   return result;
 }
 
 bool