Title: [176496] trunk
Revision
176496
Author
[email protected]
Date
2014-11-21 19:29:58 -0800 (Fri, 21 Nov 2014)

Log Message

Throttled DOMTimers can prevent their document from being garbage collected
https://bugs.webkit.org/show_bug.cgi?id=138915

Reviewed by Andreas Kling.

Source/WebCore:

Throttled DOMTimers whose interval depend on viewport changes would
keep a Vector of elements outside viewport causing them to be throttled
so that we could check later on (upon scroll or layout) if those
elements are still outside viewport. The issue is that these elements
could potentially be removed from the document (and destroyed) after
the timer has fired. To handle this, DOMTimer was ref'ing the
elements. Unfortunately, this was causing us to leak the document
as the elements in the Vector would keep the document alive.

To handle this issue, this patch updates the DOMTimer Vector to use
weak pointers. The WeakPtrFactory is stored in ElementRareData to
avoid wasting memory for all kinds of Elements (it is a fair assumption
that the number of elements whose style is animated via timers is low).

Test: fast/dom/throttled-timer-running-on-document-destruction.html

* dom/Element.cpp:
(WebCore::Element::createWeakPtr):
* dom/Element.h:
* dom/ElementRareData.cpp:
* dom/ElementRareData.h:
(WebCore::ElementRareData::weakPtrFactory):
* page/DOMTimer.cpp:
(WebCore::DOMTimerFireState::elementsChangedOutsideViewport):
(WebCore::DOMTimer::updateThrottlingStateAfterViewportChange):
* page/DOMTimer.h:

LayoutTests:

Improve fast/dom/throttled-timer-running-on-document-destruction.html
layout test to cover the case where the throttled timer is changing the
style of an element on the *same* document when the document is
destroyed.

* fast/dom/resources/frame-with-throttled-timer-animating-element-other-document.html: Renamed from LayoutTests/fast/dom/resources/frame-with-throttled-timer.html.
* fast/dom/resources/frame-with-throttled-timer-animating-element-same-document.html: Added.
* fast/dom/throttled-timer-running-on-document-destruction.html:

Modified Paths

Added Paths

Removed Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (176495 => 176496)


--- trunk/LayoutTests/ChangeLog	2014-11-22 03:27:53 UTC (rev 176495)
+++ trunk/LayoutTests/ChangeLog	2014-11-22 03:29:58 UTC (rev 176496)
@@ -1,5 +1,21 @@
 2014-11-21  Chris Dumez  <[email protected]>
 
+        Throttled DOMTimers can prevent their document from being garbage collected
+        https://bugs.webkit.org/show_bug.cgi?id=138915
+
+        Reviewed by Andreas Kling.
+
+        Improve fast/dom/throttled-timer-running-on-document-destruction.html
+        layout test to cover the case where the throttled timer is changing the
+        style of an element on the *same* document when the document is
+        destroyed.
+
+        * fast/dom/resources/frame-with-throttled-timer-animating-element-other-document.html: Renamed from LayoutTests/fast/dom/resources/frame-with-throttled-timer.html.
+        * fast/dom/resources/frame-with-throttled-timer-animating-element-same-document.html: Added.
+        * fast/dom/throttled-timer-running-on-document-destruction.html:
+
+2014-11-21  Chris Dumez  <[email protected]>
+
         [iOS] Regression(r176202): line-height is wrong on marco.org
         https://bugs.webkit.org/show_bug.cgi?id=138970
 

Copied: trunk/LayoutTests/fast/dom/resources/frame-with-throttled-timer-animating-element-other-document.html (from rev 176495, trunk/LayoutTests/fast/dom/resources/frame-with-throttled-timer.html) (0 => 176496)


--- trunk/LayoutTests/fast/dom/resources/frame-with-throttled-timer-animating-element-other-document.html	                        (rev 0)
+++ trunk/LayoutTests/fast/dom/resources/frame-with-throttled-timer-animating-element-other-document.html	2014-11-22 03:29:58 UTC (rev 176496)
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<body>
+<iframe id="testFrame"></iframe>
+<script>
+document.getElementById('testFrame').contentDocument.body.innerHTML = "<div id='testElement' style='display: none'>TEST</div>"
+
+setInterval(function() {
+  // Change the style of a display:none element.
+  var testFrame = document.getElementById("testFrame");
+  var testElement = testFrame.contentDocument.getElementById("testElement");
+  testElement.style["left"] = "" + Math.floor((Math.random() * 10) + 1) + "px";
+}, 0);
+</script>
+</body>

Added: trunk/LayoutTests/fast/dom/resources/frame-with-throttled-timer-animating-element-same-document.html (0 => 176496)


--- trunk/LayoutTests/fast/dom/resources/frame-with-throttled-timer-animating-element-same-document.html	                        (rev 0)
+++ trunk/LayoutTests/fast/dom/resources/frame-with-throttled-timer-animating-element-same-document.html	2014-11-22 03:29:58 UTC (rev 176496)
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<body>
+<div id='testElement' style='display: none'>TEST</div>
+<script>
+setInterval(function() {
+  // Change the style of a display:none element.
+  var testElement = document.getElementById("testElement");
+  testElement.style["left"] = "" + Math.floor((Math.random() * 10) + 1) + "px";
+}, 0);
+</script>
+</body>

Deleted: trunk/LayoutTests/fast/dom/resources/frame-with-throttled-timer.html (176495 => 176496)


--- trunk/LayoutTests/fast/dom/resources/frame-with-throttled-timer.html	2014-11-22 03:27:53 UTC (rev 176495)
+++ trunk/LayoutTests/fast/dom/resources/frame-with-throttled-timer.html	2014-11-22 03:29:58 UTC (rev 176496)
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<body>
-<iframe id="testFrame"></iframe>
-<script>
-document.getElementById('testFrame').contentDocument.body.innerHTML = "<div id='testElement' style='display: none'>TEST</div>"
-
-setInterval(function() {
-  // Change the style of a display:none element.
-  var testFrame = document.getElementById("testFrame");
-  var testElement = testFrame.contentDocument.getElementById("testElement");
-  testElement.style["left"] = "" + Math.floor((Math.random() * 10) + 1) + "px";
-}, 5);
-</script>
-</body>

Modified: trunk/LayoutTests/fast/dom/throttled-timer-running-on-document-destruction.html (176495 => 176496)


--- trunk/LayoutTests/fast/dom/throttled-timer-running-on-document-destruction.html	2014-11-22 03:27:53 UTC (rev 176495)
+++ trunk/LayoutTests/fast/dom/throttled-timer-running-on-document-destruction.html	2014-11-22 03:29:58 UTC (rev 176496)
@@ -1,22 +1,31 @@
 <!DOCTYPE html>
 <body>
 <script src=""
-<iframe id="testFrame" src=""
-
 <script>
 description("Test that we don't crash if a throttled timer is still running when the document is destroyed.");
 jsTestIsAsync = true;
 
-function removeFrame()
+var frameLoadedCount = 0;
+
+function removeFrames()
 {
-  document.body.removeChild(document.getElementById("testFrame"));
+  document.body.removeChild(document.getElementById("testFrame1"));
+  document.body.removeChild(document.getElementById("testFrame2"));
   gc();
   testPassed("Did not crash.");
   finishJSTest();
 }
 
-setTimeout(removeFrame, 300);
+function frameLoaded()
+{
+  ++frameLoadedCount;
+  if (frameLoadedCount == 2)
+    setTimeout(removeFrames, 100);
+}
 
 </script>
+<iframe id="testFrame1" src="" _onload_="frameLoaded()"></iframe>
+<iframe id="testFrame2" src="" _onload_="frameLoaded()"></iframe>
+
 <script src=""
 </body>

Modified: trunk/Source/WebCore/ChangeLog (176495 => 176496)


--- trunk/Source/WebCore/ChangeLog	2014-11-22 03:27:53 UTC (rev 176495)
+++ trunk/Source/WebCore/ChangeLog	2014-11-22 03:29:58 UTC (rev 176496)
@@ -1,3 +1,37 @@
+2014-11-21  Chris Dumez  <[email protected]>
+
+        Throttled DOMTimers can prevent their document from being garbage collected
+        https://bugs.webkit.org/show_bug.cgi?id=138915
+
+        Reviewed by Andreas Kling.
+
+        Throttled DOMTimers whose interval depend on viewport changes would
+        keep a Vector of elements outside viewport causing them to be throttled
+        so that we could check later on (upon scroll or layout) if those
+        elements are still outside viewport. The issue is that these elements
+        could potentially be removed from the document (and destroyed) after
+        the timer has fired. To handle this, DOMTimer was ref'ing the
+        elements. Unfortunately, this was causing us to leak the document
+        as the elements in the Vector would keep the document alive.
+
+        To handle this issue, this patch updates the DOMTimer Vector to use
+        weak pointers. The WeakPtrFactory is stored in ElementRareData to
+        avoid wasting memory for all kinds of Elements (it is a fair assumption
+        that the number of elements whose style is animated via timers is low).
+
+        Test: fast/dom/throttled-timer-running-on-document-destruction.html
+
+        * dom/Element.cpp:
+        (WebCore::Element::createWeakPtr):
+        * dom/Element.h:
+        * dom/ElementRareData.cpp:
+        * dom/ElementRareData.h:
+        (WebCore::ElementRareData::weakPtrFactory):
+        * page/DOMTimer.cpp:
+        (WebCore::DOMTimerFireState::elementsChangedOutsideViewport):
+        (WebCore::DOMTimer::updateThrottlingStateAfterViewportChange):
+        * page/DOMTimer.h:
+
 2014-11-21  Gyuyoung Kim  <[email protected]>
 
         Unreviewed, EFL build fix since r176459.

Modified: trunk/Source/WebCore/dom/Element.cpp (176495 => 176496)


--- trunk/Source/WebCore/dom/Element.cpp	2014-11-22 03:27:53 UTC (rev 176495)
+++ trunk/Source/WebCore/dom/Element.cpp	2014-11-22 03:29:58 UTC (rev 176496)
@@ -1195,6 +1195,11 @@
     return document().completeURL(stripLeadingAndTrailingHTMLSpaces(linkAttribute));
 }
 
+WeakPtr<Element> Element::createWeakPtr()
+{
+    return ensureElementRareData().weakPtrFactory().createWeakPtr();
+}
+
 // Returns true is the given attribute is an event handler.
 // We consider an event handler any attribute that begins with "on".
 // It is a simple solution that has the advantage of not requiring any

Modified: trunk/Source/WebCore/dom/Element.h (176495 => 176496)


--- trunk/Source/WebCore/dom/Element.h	2014-11-22 03:27:53 UTC (rev 176495)
+++ trunk/Source/WebCore/dom/Element.h	2014-11-22 03:29:58 UTC (rev 176496)
@@ -551,6 +551,7 @@
     void clearHoverAndActiveStatusBeforeDetachingRenderer();
 
     WEBCORE_EXPORT URL absoluteLinkURL() const;
+    WeakPtr<Element> createWeakPtr();
 
 protected:
     Element(const QualifiedName&, Document&, ConstructionType);

Modified: trunk/Source/WebCore/dom/ElementRareData.cpp (176495 => 176496)


--- trunk/Source/WebCore/dom/ElementRareData.cpp	2014-11-22 03:27:53 UTC (rev 176495)
+++ trunk/Source/WebCore/dom/ElementRareData.cpp	2014-11-22 03:29:58 UTC (rev 176496)
@@ -39,7 +39,7 @@
     RegionOversetState regionOversetState;
     LayoutSize sizeForResizing;
     IntSize scrollOffset;
-    void* pointers[7];
+    void* pointers[8];
 };
 
 static_assert(sizeof(ElementRareData) == sizeof(SameSizeAsElementRareData), "ElementRareData should stay small");

Modified: trunk/Source/WebCore/dom/ElementRareData.h (176495 => 176496)


--- trunk/Source/WebCore/dom/ElementRareData.h	2014-11-22 03:27:53 UTC (rev 176495)
+++ trunk/Source/WebCore/dom/ElementRareData.h	2014-11-22 03:29:58 UTC (rev 176496)
@@ -36,7 +36,7 @@
 
 class ElementRareData : public NodeRareData {
 public:
-    explicit ElementRareData(RenderElement*);
+    ElementRareData(Element&, RenderElement*);
     ~ElementRareData();
 
     void setBeforePseudoElement(PassRefPtr<PseudoElement>);
@@ -114,6 +114,8 @@
     bool hasPendingResources() const { return m_hasPendingResources; }
     void setHasPendingResources(bool has) { m_hasPendingResources = has; }
 
+    WeakPtrFactory<Element>& weakPtrFactory() { return m_weakPtrFactory; }
+
 private:
     short m_tabIndex;
     unsigned short m_childIndex;
@@ -147,6 +149,7 @@
 
     RefPtr<PseudoElement> m_beforePseudoElement;
     RefPtr<PseudoElement> m_afterPseudoElement;
+    WeakPtrFactory<Element> m_weakPtrFactory;
 
     void releasePseudoElement(PseudoElement*);
 };
@@ -156,7 +159,7 @@
     return IntSize(LayoutUnit::max(), LayoutUnit::max());
 }
 
-inline ElementRareData::ElementRareData(RenderElement* renderer)
+inline ElementRareData::ElementRareData(Element& element, RenderElement* renderer)
     : NodeRareData(renderer)
     , m_tabIndex(0)
     , m_childIndex(0)
@@ -175,6 +178,7 @@
     , m_childrenAffectedByPropertyBasedBackwardPositionalRules(false)
     , m_regionOversetState(RegionUndefined)
     , m_minimumSizeForResizing(defaultMinimumSizeForResizing())
+    , m_weakPtrFactory(&element)
 {
 }
 

Modified: trunk/Source/WebCore/dom/Node.cpp (176495 => 176496)


--- trunk/Source/WebCore/dom/Node.cpp	2014-11-22 03:27:53 UTC (rev 176495)
+++ trunk/Source/WebCore/dom/Node.cpp	2014-11-22 03:29:58 UTC (rev 176496)
@@ -350,7 +350,7 @@
 {
     NodeRareData* data;
     if (is<Element>(*this))
-        data = ""
+        data = "" downcast<RenderElement>(m_data.m_renderer)).release();
     else
         data = ""
     ASSERT(data);

Modified: trunk/Source/WebCore/page/DOMTimer.cpp (176495 => 176496)


--- trunk/Source/WebCore/page/DOMTimer.cpp	2014-11-22 03:27:53 UTC (rev 176495)
+++ trunk/Source/WebCore/page/DOMTimer.cpp	2014-11-22 03:29:58 UTC (rev 176496)
@@ -99,9 +99,12 @@
         return document && document->domTreeVersion() != m_initialDOMTreeVersion;
     }
 
-    void elementsChangedOutsideViewport(Vector<RefPtr<StyledElement>>& elements) const
+    void elementsChangedOutsideViewport(Vector<WeakPtr<Element>>& elements) const
     {
-        copyToVector(m_elementsChangedOutsideViewport, elements);
+        ASSERT(elements.isEmpty());
+        elements.reserveCapacity(m_elementsChangedOutsideViewport.size());
+        for (auto& element : m_elementsChangedOutsideViewport)
+            elements.uncheckedAppend(element->createWeakPtr());
     }
 
     static DOMTimerFireState* current;
@@ -471,9 +474,10 @@
 {
     ASSERT(isIntervalDependentOnViewport());
     // Check if the elements that caused this timer to be throttled are still outside the viewport.
-    for (auto& element : m_elementsCausingThrottling) {
+    for (auto& weakElementPtr : m_elementsCausingThrottling) {
+        Element* element = weakElementPtr.get();
         // Skip elements that were removed from the document.
-        if (!element->inDocument())
+        if (!element || !element->inDocument())
             continue;
 
         if (element->isInsideViewport(&visibleRect)) {

Modified: trunk/Source/WebCore/page/DOMTimer.h (176495 => 176496)


--- trunk/Source/WebCore/page/DOMTimer.h	2014-11-22 03:27:53 UTC (rev 176495)
+++ trunk/Source/WebCore/page/DOMTimer.h	2014-11-22 03:29:58 UTC (rev 176496)
@@ -31,11 +31,13 @@
 #include <memory>
 #include <wtf/HashSet.h>
 #include <wtf/RefCounted.h>
+#include <wtf/WeakPtr.h>
 
 namespace WebCore {
 
     class DOMTimerFireState;
     class Document;
+    class Element;
     class HTMLPlugInElement;
     class IntRect;
     class ScheduledAction;
@@ -91,9 +93,10 @@
         TimerThrottleState m_throttleState;
         double m_currentTimerInterval;
         bool m_shouldForwardUserGesture;
-        // Hold a reference to the elements in case they get removed from the
-        // Document after the timer is throttled.
-        Vector<RefPtr<StyledElement>> m_elementsCausingThrottling;
+        // Use WeakPtrs because we don't want to keep the elements alive but we
+        // still need to handle cases where the elements get destroyed after
+        // the timer has fired.
+        Vector<WeakPtr<Element>> m_elementsCausingThrottling;
     };
 
 } // namespace WebCore
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to