WebCore:
authordarin@apple.com <darin@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 17 Mar 2009 17:02:19 +0000 (17:02 +0000)
committerdarin@apple.com <darin@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 17 Mar 2009 17:02:19 +0000 (17:02 +0000)
2009-03-17  Darin Adler  <darin@apple.com>

        Reviewed by Alexey Proskuryakov.

        Bug 24624: Crash in imageLoadEventTimerFired after adoptNode used on <img>,
        seen with inspector, which uses adoptNode
        https://bugs.webkit.org/show_bug.cgi?id=24624
        rdar://problem/6422850

        Test: fast/dom/HTMLImageElement/image-load-cross-document.html

        * dom/Document.cpp:
        (WebCore::Document::Document): Removed m_imageLoadEventTimer.
        (WebCore::Document::detach): Removed m_imageLoadEventDispatchSoonList and
        m_imageLoadEventDispatchingList.
        (WebCore::Document::implicitClose): Called ImageLoader::dispatchPendingLoadEvents
        instead of dispatchImageLoadEventsNow.

        * dom/Document.h: Removed ImageLoader, dispatchImageLoadEventSoon,
        dispatchImageLoadEventsNow, removeImage, m_imageLoadEventDispatchSoonList,
        m_imageLoadEventDispatchingList, m_imageLoadEventTimer, and imageLoadEventTimerFired.

        * loader/ImageLoader.cpp:
        (WebCore::loadEventSender): Added. Returns the single global ImageLoadEventSender
        object used privately as the target of the load event timer.
        (WebCore::ImageLoader::~ImageLoader): Call ImageLoadEventSender::cancelLoadEvent
        rather than Document::removeImage.
        (WebCore::ImageLoader::setImage): Use m_element directly, not element().
        (WebCore::ImageLoader::updateFromElement): Ditto. Also name the local variable
        document instead of doc.
        (WebCore::ImageLoader::notifyFinished): Call ImageLoadEventSender::dispatchLoadEventSoon
        rather than Document::dispatchImageLoadEventSoon.
        (WebCore::ImageLoader::dispatchPendingLoadEvent): Added. Handles the common logic
        about when load events can be dispatched so that dispatchLoadEvent only has to
        have the specific part for each derived class. This includes a check that the
        document is attached, which used to be handled by having documents empty out the
        image load event vectors in the detach function.
        (WebCore::ImageLoader::dispatchPendingLoadEvents): Added. Calls the appropriate
        function on the ImageLoadEventSender, which avoids the need to have that class be
        public in the ImageLoader header.
        (WebCore::ImageLoadEventSender::ImageLoadEventSender): Added. Has the code that
        was previously in the Document constructor.
        (WebCore::ImageLoadEventSender::dispatchLoadEventSoon): Added. Has the code that
        was previously in Document::dispatchImageLoadEventSoon.
        (WebCore::ImageLoadEventSender::cancelLoadEvent): Added. Has the code that was
        previously in Document::removeImage.
        (WebCore::ImageLoadEventSender::dispatchPendingLoadEvents): Added. Has the code
        that was previously in Document::dispatchImageLoadEventsNow.
        (WebCore::ImageLoadEventSender::timerFired): Added. Calls dispatchPendingLoadEvents.

        * loader/ImageLoader.h: Improved comments. Made the virtual functions private
        or protected rather than public. Added static dispatchPendingLoadEvents function
        for use by Document and private dispatchPendingLoadEvent function for use by
        ImageLoadEventSender. Made setLoadingImage private and eliminated
        setHaveFiredLoadEvent since that can all be done inside the class without any
        member functions.

        * html/HTMLImageLoader.cpp:
        (WebCore::HTMLImageLoader::dispatchLoadEvent): Removed logic to check whether a
        load event already fired and whether image() is 0. These are now both base class
        responsibilities.
        * svg/SVGImageLoader.cpp:
        (WebCore::SVGImageLoader::dispatchLoadEvent): Ditto.
        * wml/WMLImageLoader.cpp:
        (WebCore::WMLImageLoader::dispatchLoadEvent): Ditto.

LayoutTests:

2009-03-17  Darin Adler  <darin@apple.com>

        Reviewed by Alexey Proskuryakov.

        Bug 24624: Crash in imageLoadEventTimerFired after adoptNode used on <img>,
        seen with inspector, which uses adoptNode
        https://bugs.webkit.org/show_bug.cgi?id=24624
        rdar://problem/6422850

        This test has one significant disadvantage. When it fails, the crash typically
        occurs during a subsequent test, not this one. It would be great if someone figured
        out at some point how to improve that.

        * fast/dom/HTMLImageElement/image-load-cross-document-expected.txt: Added.
        * fast/dom/HTMLImageElement/image-load-cross-document.html: Added.
        * fast/dom/HTMLImageElement/resources/image-load-subframe.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@41766 268f45cc-cd09-0410-ab3c-d52691b4dbfc

12 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/dom/HTMLImageElement/image-load-cross-document-expected.txt [new file with mode: 0644]
LayoutTests/fast/dom/HTMLImageElement/image-load-cross-document.html [new file with mode: 0644]
LayoutTests/fast/dom/HTMLImageElement/resources/image-load-subframe.html [new file with mode: 0644]
WebCore/ChangeLog
WebCore/dom/Document.cpp
WebCore/dom/Document.h
WebCore/html/HTMLImageLoader.cpp
WebCore/loader/ImageLoader.cpp
WebCore/loader/ImageLoader.h
WebCore/svg/SVGImageLoader.cpp
WebCore/wml/WMLImageLoader.cpp

index e95fafd..99073c4 100644 (file)
@@ -1,3 +1,20 @@
+2009-03-17  Darin Adler  <darin@apple.com>
+
+        Reviewed by Alexey Proskuryakov.
+
+        Bug 24624: Crash in imageLoadEventTimerFired after adoptNode used on <img>,
+        seen with inspector, which uses adoptNode
+        https://bugs.webkit.org/show_bug.cgi?id=24624
+        rdar://problem/6422850
+
+        This test has one significant disadvantage. When it fails, the crash typically
+        occurs during a subsequent test, not this one. It would be great if someone figured
+        out at some point how to improve that.
+
+        * fast/dom/HTMLImageElement/image-load-cross-document-expected.txt: Added.
+        * fast/dom/HTMLImageElement/image-load-cross-document.html: Added.
+        * fast/dom/HTMLImageElement/resources/image-load-subframe.html: Added.
+
 2009-03-17  Alexey Proskuryakov  <ap@webkit.org>
 
         Reviewed by Sam Weinig.
diff --git a/LayoutTests/fast/dom/HTMLImageElement/image-load-cross-document-expected.txt b/LayoutTests/fast/dom/HTMLImageElement/image-load-cross-document-expected.txt
new file mode 100644 (file)
index 0000000..c0dd444
--- /dev/null
@@ -0,0 +1,6 @@
+This tests the case where an image is loaded in one document and then moved to another before the load event fires.
+
+PASS: Test complete and passed unless a subsequent test crashes.
+
+
+
diff --git a/LayoutTests/fast/dom/HTMLImageElement/image-load-cross-document.html b/LayoutTests/fast/dom/HTMLImageElement/image-load-cross-document.html
new file mode 100644 (file)
index 0000000..9f50136
--- /dev/null
@@ -0,0 +1,50 @@
+<head>
+<script>
+
+var isDocumentLoaded = false;
+var isFrameLoaded = false;
+
+function everythingLoaded()
+{
+    var otherDocument = document.getElementById("frame").contentDocument;
+    var imageURL = document.URL.replace("image-load-cross-document.html", "resources/blue_rect.jpg");
+    otherDocument.body.innerHTML = "<p id='container'><img src='" + imageURL + "'></p>";
+    var container = otherDocument.getElementById("container");
+    document.adoptNode(container);
+    document.body.appendChild(container);
+    container.innerHTML = "";
+
+    document.getElementById("result").innerHTML = "PASS: Test complete and passed unless a subsequent test crashes.";
+
+    if (window.layoutTestController)
+        layoutTestController.notifyDone();
+}
+
+function documentLoaded()
+{
+    if (window.layoutTestController) {
+        layoutTestController.dumpAsText();
+        layoutTestController.waitUntilDone();
+    }
+
+    isDocumentLoaded = true;
+
+    if (isFrameLoaded)
+        everythingLoaded();
+}
+
+function frameLoaded()
+{
+    isFrameLoaded = true;
+
+    if (isDocumentLoaded)
+        everythingLoaded();
+}
+
+</script>
+</head>
+<body onload="documentLoaded()">
+<p>This tests the case where an image is loaded in one document and then moved to another before the load event fires.</p>
+<p id="result">TEST NOT COMPLETE YET</p>
+<iframe id="frame" src="resources/image-load-subframe.html" onload="frameLoaded()"></iframe>
+</body>
diff --git a/LayoutTests/fast/dom/HTMLImageElement/resources/image-load-subframe.html b/LayoutTests/fast/dom/HTMLImageElement/resources/image-load-subframe.html
new file mode 100644 (file)
index 0000000..c888fd7
--- /dev/null
@@ -0,0 +1 @@
+<body><img src="blue_rect.jpg"></body>
index 81ad98d..4b75d57 100644 (file)
@@ -1,3 +1,69 @@
+2009-03-17  Darin Adler  <darin@apple.com>
+
+        Reviewed by Alexey Proskuryakov.
+
+        Bug 24624: Crash in imageLoadEventTimerFired after adoptNode used on <img>,
+        seen with inspector, which uses adoptNode
+        https://bugs.webkit.org/show_bug.cgi?id=24624
+        rdar://problem/6422850
+
+        Test: fast/dom/HTMLImageElement/image-load-cross-document.html
+
+        * dom/Document.cpp:
+        (WebCore::Document::Document): Removed m_imageLoadEventTimer.
+        (WebCore::Document::detach): Removed m_imageLoadEventDispatchSoonList and
+        m_imageLoadEventDispatchingList.
+        (WebCore::Document::implicitClose): Called ImageLoader::dispatchPendingLoadEvents
+        instead of dispatchImageLoadEventsNow.
+
+        * dom/Document.h: Removed ImageLoader, dispatchImageLoadEventSoon,
+        dispatchImageLoadEventsNow, removeImage, m_imageLoadEventDispatchSoonList,
+        m_imageLoadEventDispatchingList, m_imageLoadEventTimer, and imageLoadEventTimerFired.
+
+        * loader/ImageLoader.cpp:
+        (WebCore::loadEventSender): Added. Returns the single global ImageLoadEventSender
+        object used privately as the target of the load event timer.
+        (WebCore::ImageLoader::~ImageLoader): Call ImageLoadEventSender::cancelLoadEvent
+        rather than Document::removeImage.
+        (WebCore::ImageLoader::setImage): Use m_element directly, not element().
+        (WebCore::ImageLoader::updateFromElement): Ditto. Also name the local variable
+        document instead of doc.
+        (WebCore::ImageLoader::notifyFinished): Call ImageLoadEventSender::dispatchLoadEventSoon
+        rather than Document::dispatchImageLoadEventSoon.
+        (WebCore::ImageLoader::dispatchPendingLoadEvent): Added. Handles the common logic
+        about when load events can be dispatched so that dispatchLoadEvent only has to
+        have the specific part for each derived class. This includes a check that the
+        document is attached, which used to be handled by having documents empty out the
+        image load event vectors in the detach function.
+        (WebCore::ImageLoader::dispatchPendingLoadEvents): Added. Calls the appropriate
+        function on the ImageLoadEventSender, which avoids the need to have that class be
+        public in the ImageLoader header.
+        (WebCore::ImageLoadEventSender::ImageLoadEventSender): Added. Has the code that
+        was previously in the Document constructor.
+        (WebCore::ImageLoadEventSender::dispatchLoadEventSoon): Added. Has the code that
+        was previously in Document::dispatchImageLoadEventSoon.
+        (WebCore::ImageLoadEventSender::cancelLoadEvent): Added. Has the code that was
+        previously in Document::removeImage.
+        (WebCore::ImageLoadEventSender::dispatchPendingLoadEvents): Added. Has the code
+        that was previously in Document::dispatchImageLoadEventsNow.
+        (WebCore::ImageLoadEventSender::timerFired): Added. Calls dispatchPendingLoadEvents.
+
+        * loader/ImageLoader.h: Improved comments. Made the virtual functions private
+        or protected rather than public. Added static dispatchPendingLoadEvents function
+        for use by Document and private dispatchPendingLoadEvent function for use by
+        ImageLoadEventSender. Made setLoadingImage private and eliminated
+        setHaveFiredLoadEvent since that can all be done inside the class without any
+        member functions.
+
+        * html/HTMLImageLoader.cpp:
+        (WebCore::HTMLImageLoader::dispatchLoadEvent): Removed logic to check whether a
+        load event already fired and whether image() is 0. These are now both base class
+        responsibilities.
+        * svg/SVGImageLoader.cpp:
+        (WebCore::SVGImageLoader::dispatchLoadEvent): Ditto.
+        * wml/WMLImageLoader.cpp:
+        (WebCore::WMLImageLoader::dispatchLoadEvent): Ditto.
+
 2009-03-17  Dimitri Glazkov  <dglazkov@chromium.org>
 
         Reviewed by Timothy Hatcher.
index da566f6..db095ec 100644 (file)
@@ -284,7 +284,6 @@ Document::Document(Frame* frame, bool isXHTML)
     , m_frameElementsShouldIgnoreScrolling(false)
     , m_title("")
     , m_titleSetExplicitly(false)
-    , m_imageLoadEventTimer(this, &Document::imageLoadEventTimerFired)
     , m_updateFocusAppearanceTimer(this, &Document::updateFocusAppearanceTimerFired)
 #if ENABLE(XSLT)
     , m_transformSource(0)
@@ -1296,13 +1295,7 @@ void Document::detach()
     
     // indicate destruction mode,  i.e. attached() but renderer == 0
     setRenderer(0);
-    
-    // Empty out these lists as a performance optimization, since detaching
-    // all the individual render objects will cause all the RenderImage
-    // objects to remove themselves from the lists.
-    m_imageLoadEventDispatchSoonList.clear();
-    m_imageLoadEventDispatchingList.clear();
-    
+
     m_hoverNode = 0;
     m_focusedNode = 0;
     m_activeNode = 0;
@@ -1582,8 +1575,8 @@ void Document::implicitClose()
     if (f)
         f->animation()->resumeAnimations(this);
 
-    dispatchImageLoadEventsNow();
-    this->dispatchWindowEvent(eventNames().loadEvent, false, false);
+    ImageLoader::dispatchPendingLoadEvents();
+    dispatchWindowEvent(eventNames().loadEvent, false, false);
     if (f)
         f->loader()->handledOnloadEvents();
 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
@@ -2876,56 +2869,6 @@ void Document::setWindowInlineEventListenerForTypeAndAttribute(const AtomicStrin
     setWindowInlineEventListenerForType(eventType, createEventListener(attr->localName().string(), attr->value(), 0));
 }
 
-void Document::dispatchImageLoadEventSoon(ImageLoader* image)
-{
-    m_imageLoadEventDispatchSoonList.append(image);
-    if (!m_imageLoadEventTimer.isActive())
-        m_imageLoadEventTimer.startOneShot(0);
-}
-
-void Document::removeImage(ImageLoader* image)
-{
-    // Remove instances of this image from both lists.
-    // Use loops because we allow multiple instances to get into the lists.
-    size_t size = m_imageLoadEventDispatchSoonList.size();
-    for (size_t i = 0; i < size; ++i) {
-        if (m_imageLoadEventDispatchSoonList[i] == image)
-            m_imageLoadEventDispatchSoonList[i] = 0;
-    }
-    size = m_imageLoadEventDispatchingList.size();
-    for (size_t i = 0; i < size; ++i) {
-        if (m_imageLoadEventDispatchingList[i] == image)
-            m_imageLoadEventDispatchingList[i] = 0;
-    }
-    if (m_imageLoadEventDispatchSoonList.isEmpty())
-        m_imageLoadEventTimer.stop();
-}
-
-void Document::dispatchImageLoadEventsNow()
-{
-    // Need to avoid re-entering this function; if new dispatches are
-    // scheduled before the parent finishes processing the list, they
-    // will set a timer and eventually be processed.
-    if (!m_imageLoadEventDispatchingList.isEmpty())
-        return;
-
-    m_imageLoadEventTimer.stop();
-
-    m_imageLoadEventDispatchingList = m_imageLoadEventDispatchSoonList;
-    m_imageLoadEventDispatchSoonList.clear();
-    size_t size = m_imageLoadEventDispatchingList.size();
-    for (size_t i = 0; i < size; ++i) {
-        if (ImageLoader* image = m_imageLoadEventDispatchingList[i])
-            image->dispatchLoadEvent();
-    }
-    m_imageLoadEventDispatchingList.clear();
-}
-
-void Document::imageLoadEventTimerFired(Timer<Document>*)
-{
-    dispatchImageLoadEventsNow();
-}
-
 Element* Document::ownerElement() const
 {
     if (!frame())
index 3770c09..e53374b 100644 (file)
@@ -83,7 +83,6 @@ namespace WebCore {
     class HTMLHeadElement;
     class HTMLInputElement;
     class HTMLMapElement;
-    class ImageLoader;
     class IntPoint;
     class JSNode;
     class MouseEventWithHitTestResults;
@@ -639,10 +638,6 @@ public:
      */
     void processHttpEquiv(const String& equiv, const String& content);
     
-    void dispatchImageLoadEventSoon(ImageLoader*);
-    void dispatchImageLoadEventsNow();
-    void removeImage(ImageLoader*);
-    
     // Returns the owning element in the parent document.
     // Returns 0 if this is the top level document.
     Element* ownerElement() const;
@@ -921,10 +916,6 @@ private:
 
     mutable AXObjectCache* m_axObjectCache;
     
-    Vector<ImageLoader*> m_imageLoadEventDispatchSoonList;
-    Vector<ImageLoader*> m_imageLoadEventDispatchingList;
-    Timer<Document> m_imageLoadEventTimer;
-
     Timer<Document> m_updateFocusAppearanceTimer;
 
     Element* m_cssTarget;
@@ -1036,7 +1027,6 @@ protected:
 private:
     void updateTitle();
     void removeAllDisconnectedNodeEventListeners();
-    void imageLoadEventTimerFired(Timer<Document>*);
     void updateFocusAppearanceTimerFired(Timer<Document>*);
     void updateBaseURL();
 
index 22e3abc..5dac8bf 100644 (file)
@@ -42,10 +42,7 @@ HTMLImageLoader::~HTMLImageLoader()
 
 void HTMLImageLoader::dispatchLoadEvent()
 {
-    if (!haveFiredLoadEvent() && image()) {
-        setHaveFiredLoadEvent(true);
-        element()->dispatchEventForType(image()->errorOccurred() ? eventNames().errorEvent : eventNames().loadEvent, false, false);
-    }
+    element()->dispatchEventForType(image()->errorOccurred() ? eventNames().errorEvent : eventNames().loadEvent, false, false);
 }
 
 String HTMLImageLoader::sourceURI(const AtomicString& attr) const
index d8ebcab..e0868d2 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
- * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
+ * Copyright (C) 2004, 2005, 2006, 2007, 2009 Apple Inc. All rights reserved.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
 
 namespace WebCore {
 
-ImageLoader::ImageLoader(Element* elt)
-    : m_element(elt)
+class ImageLoadEventSender {
+public:
+    ImageLoadEventSender();
+
+    void dispatchLoadEventSoon(ImageLoader*);
+    void cancelLoadEvent(ImageLoader*);
+
+    void dispatchPendingLoadEvents();
+
+private:
+    ~ImageLoadEventSender();
+
+    void timerFired(Timer<ImageLoadEventSender>*);
+
+    Timer<ImageLoadEventSender> m_timer;
+    Vector<ImageLoader*> m_dispatchSoonList;
+    Vector<ImageLoader*> m_dispatchingList;
+};
+
+static ImageLoadEventSender& loadEventSender()
+{
+    DEFINE_STATIC_LOCAL(ImageLoadEventSender, sender, ());
+    return sender;
+}
+
+ImageLoader::ImageLoader(Element* element)
+    : m_element(element)
     , m_image(0)
     , m_firedLoad(true)
     , m_imageComplete(true)
@@ -44,7 +69,7 @@ ImageLoader::~ImageLoader()
 {
     if (m_image)
         m_image->removeClient(this);
-    m_element->document()->removeImage(this);
+    loadEventSender().cancelLoadEvent(this);
 }
 
 void ImageLoader::setImage(CachedImage* newImage)
@@ -61,7 +86,7 @@ void ImageLoader::setImage(CachedImage* newImage)
             oldImage->removeClient(this);
     }
 
-    if (RenderObject* renderer = element()->renderer()) {
+    if (RenderObject* renderer = m_element->renderer()) {
         if (!renderer->isImage())
             return;
 
@@ -80,12 +105,11 @@ void ImageLoader::updateFromElement()
 {
     // If we're not making renderers for the page, then don't load images.  We don't want to slow
     // down the raw HTML parsing case by loading images we don't intend to display.
-    Element* elem = element();
-    Document* doc = elem->document();
-    if (!doc->renderer())
+    Document* document = m_element->document();
+    if (!document->renderer())
         return;
 
-    AtomicString attr = elem->getAttribute(elem->imageSourceAttributeName());
+    AtomicString attr = m_element->getAttribute(m_element->imageSourceAttributeName());
 
     if (attr == m_failedLoadURL)
         return;
@@ -95,15 +119,15 @@ void ImageLoader::updateFromElement()
     // a quirk that preserves old behavior that Dashboard widgets
     // need (<rdar://problem/5994621>).
     CachedImage* newImage = 0;
-    if (!(attr.isNull() || attr.isEmpty() && doc->baseURI().isLocalFile())) {
+    if (!(attr.isNull() || attr.isEmpty() && document->baseURI().isLocalFile())) {
         if (m_loadManually) {
-            doc->docLoader()->setAutoLoadImages(false);
+            document->docLoader()->setAutoLoadImages(false);
             newImage = new CachedImage(sourceURI(attr));
             newImage->setLoading(true);
-            newImage->setDocLoader(doc->docLoader());
-            doc->docLoader()->m_documentResources.set(newImage->url(), newImage);
+            newImage->setDocLoader(document->docLoader());
+            document->docLoader()->m_documentResources.set(newImage->url(), newImage);
         } else
-            newImage = doc->docLoader()->requestImage(sourceURI(attr));
+            newImage = document->docLoader()->requestImage(sourceURI(attr));
 
         // If we do not have an image here, it means that a cross-site
         // violation occurred.
@@ -119,7 +143,7 @@ void ImageLoader::updateFromElement()
             oldImage->removeClient(this);
     }
 
-    if (RenderObject* renderer = elem->renderer()) {
+    if (RenderObject* renderer = m_element->renderer()) {
         if (!renderer->isImage())
             return;
 
@@ -139,10 +163,9 @@ void ImageLoader::notifyFinished(CachedResource*)
     ASSERT(m_failedLoadURL.isEmpty());
     m_imageComplete = true;
 
-    Element* elem = element();
-    elem->document()->dispatchImageLoadEventSoon(this);
+    loadEventSender().dispatchLoadEventSoon(this);
 
-    if (RenderObject* renderer = elem->renderer()) {
+    if (RenderObject* renderer = m_element->renderer()) {
         if (!renderer->isImage())
             return;
 
@@ -150,4 +173,75 @@ void ImageLoader::notifyFinished(CachedResource*)
     }
 }
 
+void ImageLoader::dispatchPendingLoadEvent()
+{
+    if (m_firedLoad)
+        return;
+    if (!m_image)
+        return;
+    if (!m_element->document()->attached())
+        return;
+    m_firedLoad = true;
+    dispatchLoadEvent();
+}
+
+void ImageLoader::dispatchPendingLoadEvents()
+{
+    loadEventSender().dispatchPendingLoadEvents();
+}
+
+ImageLoadEventSender::ImageLoadEventSender()
+    : m_timer(this, &ImageLoadEventSender::timerFired)
+{
+}
+
+void ImageLoadEventSender::dispatchLoadEventSoon(ImageLoader* loader)
+{
+    m_dispatchSoonList.append(loader);
+    if (!m_timer.isActive())
+        m_timer.startOneShot(0);
+}
+
+void ImageLoadEventSender::cancelLoadEvent(ImageLoader* loader)
+{
+    // Remove instances of this loader from both lists.
+    // Use loops because we allow multiple instances to get into the lists.
+    size_t size = m_dispatchSoonList.size();
+    for (size_t i = 0; i < size; ++i) {
+        if (m_dispatchSoonList[i] == loader)
+            m_dispatchSoonList[i] = 0;
+    }
+    size = m_dispatchingList.size();
+    for (size_t i = 0; i < size; ++i) {
+        if (m_dispatchingList[i] == loader)
+            m_dispatchingList[i] = 0;
+    }
+    if (m_dispatchSoonList.isEmpty())
+        m_timer.stop();
+}
+
+void ImageLoadEventSender::dispatchPendingLoadEvents()
+{
+    // Need to avoid re-entering this function; if new dispatches are
+    // scheduled before the parent finishes processing the list, they
+    // will set a timer and eventually be processed.
+    if (!m_dispatchingList.isEmpty())
+        return;
+
+    m_timer.stop();
+
+    m_dispatchingList.swap(m_dispatchSoonList);
+    size_t size = m_dispatchingList.size();
+    for (size_t i = 0; i < size; ++i) {
+        if (ImageLoader* loader = m_dispatchingList[i])
+            loader->dispatchPendingLoadEvent();
+    }
+    m_dispatchingList.clear();
+}
+
+void ImageLoadEventSender::timerFired(Timer<ImageLoadEventSender>*)
+{
+    dispatchPendingLoadEvents();
+}
+
 }
index fc3a58a..3496f75 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
- * Copyright (C) 2004 Apple Computer, Inc.
+ * Copyright (C) 2004, 2009 Apple Inc. All rights reserved.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
 namespace WebCore {
 
 class Element;
+class ImageLoadEventSender;
 
 class ImageLoader : public CachedResourceClient {
 public:
     ImageLoader(Element*);
     virtual ~ImageLoader();
 
+    // This function should be called when the element is attached to a document; starts
+    // loading if a load hasn't already been started.
     void updateFromElement();
 
-    // This method should be called after the 'src' attribute
-    // is set (even when it is not modified) to force the update
-    // and match Firefox and Opera.
+    // This function should be called whenever the 'src' attribute is set, even if its value
+    // doesn't change; starts new load unconditionally (matches Firefox and Opera behavior).
     void updateFromElementIgnoringPreviousError();
 
-    virtual void dispatchLoadEvent() = 0;
-    virtual String sourceURI(const AtomicString&) const = 0;
-
     Element* element() const { return m_element; }
     bool imageComplete() const { return m_imageComplete; }
 
@@ -54,16 +53,22 @@ public:
 
     void setLoadManually(bool loadManually) { m_loadManually = loadManually; }
 
-    // CachedResourceClient API
-    virtual void notifyFinished(CachedResource*);
-
     bool haveFiredLoadEvent() const { return m_firedLoad; }
+
+    static void dispatchPendingLoadEvents();
+
 protected:
-    void setLoadingImage(CachedImage*);
-    
-    void setHaveFiredLoadEvent(bool firedLoad) { m_firedLoad = firedLoad; }
+    virtual void notifyFinished(CachedResource*);
 
 private:
+    virtual void dispatchLoadEvent() = 0;
+    virtual String sourceURI(const AtomicString&) const = 0;
+
+    friend class ImageLoadEventSender;
+    void dispatchPendingLoadEvent();
+
+    void setLoadingImage(CachedImage*);
+
     Element* m_element;
     CachedResourceHandle<CachedImage> m_image;
     AtomicString m_failedLoadURL;
index 6e0915d..4b15acb 100644 (file)
@@ -42,16 +42,12 @@ SVGImageLoader::~SVGImageLoader()
 
 void SVGImageLoader::dispatchLoadEvent()
 {
-    if (!haveFiredLoadEvent() && image()) {
-        setHaveFiredLoadEvent(true);
-        
-        if (image()->errorOccurred())
-            element()->dispatchEventForType(eventNames().errorEvent, false, false);
-        else {
-            SVGImageElement* imageElement = static_cast<SVGImageElement*>(element());
-            if (imageElement->externalResourcesRequiredBaseValue())
-                imageElement->sendSVGLoadEventIfPossible(true);
-        }
+    if (image()->errorOccurred())
+        element()->dispatchEventForType(eventNames().errorEvent, false, false);
+    else {
+        SVGImageElement* imageElement = static_cast<SVGImageElement*>(element());
+        if (imageElement->externalResourcesRequiredBaseValue())
+            imageElement->sendSVGLoadEventIfPossible(true);
     }
 }
 
index a1a020d..c77b511 100644 (file)
@@ -45,10 +45,6 @@ WMLImageLoader::~WMLImageLoader()
 void WMLImageLoader::dispatchLoadEvent()
 {
     // WML doesn't fire any events.
-    if (haveFiredLoadEvent())
-        return;
-
-    setHaveFiredLoadEvent(true);
 }
 
 String WMLImageLoader::sourceURI(const AtomicString& attr) const