Cache and reuse HTMLCollections exposed by Document.
<http://webkit.org/b/71956>

Reviewed by Antti Koivisto.

Source/WebCore: 

Let Document cache the various HTMLCollection objects it exposes.
This is a behavior change in two ways:

1) The lifetime of returned collections is now tied to the lifetime
   of the Document. This matches the behavior of Firefox and Opera.

2) The cached collections returned by document are now exactly equal
   to those returned by subsequent calls to the same getters.

This reduces memory consumption by ~800 kB (on 64-bit) when loading
the full HTML5 spec. document.links was called 34001 times, yielding
34001 separate HTMLCollections, and now we only need 1.

The document.all collection retains the old behavior, as caching it
will be a bit more complicated.

To avoid a reference cycle between Document and HTMLCollection,
collections that are cached on Document do not retained their base
node pointer (controlled by a m_baseIsRetained flag.)

Tests: fast/dom/document-collection-idempotence.html
       fast/dom/gc-9.html

* dom/Document.cpp:
(WebCore::Document::detach):
(WebCore::Document::cachedCollection):
(WebCore::Document::images):
(WebCore::Document::applets):
(WebCore::Document::embeds):
(WebCore::Document::plugins):
(WebCore::Document::objects):
(WebCore::Document::scripts):
(WebCore::Document::links):
(WebCore::Document::forms):
(WebCore::Document::anchors):
* dom/Document.h:
* html/HTMLCollection.cpp:
(WebCore::HTMLCollection::HTMLCollection):
(WebCore::HTMLCollection::createForCachingOnDocument):
(WebCore::HTMLCollection::~HTMLCollection):
(WebCore::HTMLCollection::itemAfter):
* html/HTMLCollection.h:
(WebCore::HTMLCollection::base):

LayoutTests: 

Added a test to verify that collections returned by document (excluding .all)
are equal to the collections returned by subsequent calls to the same getters.

Also update fast/dom/gc-9.html to cover the new lifetime behavior of
HTMLCollection objects returned by document.

* fast/dom/document-collection-idempotence-expected.txt: Added.
* fast/dom/document-collection-idempotence.html: Added.
* fast/dom/gc-9-expected.txt:
* fast/dom/gc-9.html:


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@103115 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/html/HTMLCollection.cpp b/Source/WebCore/html/HTMLCollection.cpp
index 3f2d0e1..9e31863 100644
--- a/Source/WebCore/html/HTMLCollection.cpp
+++ b/Source/WebCore/html/HTMLCollection.cpp
@@ -36,20 +36,28 @@
 
 using namespace HTMLNames;
 
-HTMLCollection::HTMLCollection(PassRefPtr<Node> base, CollectionType type)
-    : m_ownsInfo(false)
+HTMLCollection::HTMLCollection(Document* document, CollectionType type)
+    : m_baseIsRetained(false)
+    , m_ownsInfo(false)
     , m_type(type)
-    , m_base(base)
-    , m_info(m_base->isDocumentNode() ? static_cast<Document*>(m_base.get())->collectionInfo(type) : 0)
+    , m_base(document)
+    , m_info(document->collectionInfo(type))
 {
 }
 
 HTMLCollection::HTMLCollection(PassRefPtr<Node> base, CollectionType type, CollectionCache* info)
-    : m_ownsInfo(false)
+    : m_baseIsRetained(true)
+    , m_ownsInfo(false)
     , m_type(type)
-    , m_base(base)
+    , m_base(base.get())
     , m_info(info)
 {
+    m_base->ref();
+}
+
+PassRefPtr<HTMLCollection> HTMLCollection::createForCachingOnDocument(Document* document, CollectionType type)
+{
+    return adoptRef(new HTMLCollection(document, type));
 }
 
 PassRefPtr<HTMLCollection> HTMLCollection::create(PassRefPtr<Node> base, CollectionType type)
@@ -61,6 +69,8 @@
 {
     if (m_ownsInfo)
         delete m_info;
+    if (m_baseIsRetained)
+        m_base->deref();
 }
 
 void HTMLCollection::resetCollectionInfo() const
@@ -121,9 +131,9 @@
     if (!previous)
         current = m_base->firstChild();
     else
-        current = nextNodeOrSibling(m_base.get(), previous, deep);
+        current = nextNodeOrSibling(m_base, previous, deep);
 
-    for (; current; current = nextNodeOrSibling(m_base.get(), current, deep)) {
+    for (; current; current = nextNodeOrSibling(m_base, current, deep)) {
         if (!current->isElementNode())
             continue;
         Element* e = static_cast<Element*>(current);