WebCore:
authorabarth@webkit.org <abarth@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 1 Jun 2009 09:00:00 +0000 (09:00 +0000)
committerabarth@webkit.org <abarth@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 1 Jun 2009 09:00:00 +0000 (09:00 +0000)
2009-06-01  Drew Wilson  <atwilson@google.com>

        Reviewed by Darin Adler.  Landed (and tweaked) by Adam Barth.

        https://bugs.webkit.org/show_bug.cgi?id=25902

        Added WorkerContext.close()

        Test: fast/workers/worker-close.html

        * workers/WorkerContext.cpp:
        (WebCore::WorkerContext::close):
        * workers/WorkerContext.h:
        * workers/WorkerContext.idl:
        * workers/WorkerMessagingProxy.cpp:
        (WebCore::WorkerMessagingProxy::workerContextDestroyedInternal):

LayoutTests:

2009-06-01  Drew Wilson  <atwilson@google.com>

        Reviewed by Darin Adler.  Landed by Adam Barth.

        https://bugs.webkit.org/show_bug.cgi?id=25902

        Added WorkerContext.close()

        * fast/workers/worker-close-expected.txt: Added.
        * fast/workers/worker-close.html: Added.
        * fast/workers/resources/worker-close.js: Added.
        * http/tests/xmlhttprequest/workers/close.html: Added.

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

13 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/workers/resources/worker-close.js [new file with mode: 0644]
LayoutTests/fast/workers/resources/worker-common.js
LayoutTests/fast/workers/worker-close-expected.txt [new file with mode: 0644]
LayoutTests/fast/workers/worker-close.html [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/workers/close-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/workers/close.html [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/workers/resources/close.js [new file with mode: 0644]
WebCore/ChangeLog
WebCore/workers/WorkerContext.cpp
WebCore/workers/WorkerContext.h
WebCore/workers/WorkerContext.idl
WebCore/workers/WorkerMessagingProxy.cpp

index a840c7d..19b0019 100644 (file)
@@ -1,3 +1,16 @@
+2009-06-01  Drew Wilson  <atwilson@google.com>
+
+        Reviewed by Darin Adler.  Landed by Adam Barth.
+
+        https://bugs.webkit.org/show_bug.cgi?id=25902
+
+        Added WorkerContext.close()
+
+        * fast/workers/worker-close-expected.txt: Added.
+        * fast/workers/worker-close.html: Added.
+        * fast/workers/resources/worker-close.js: Added.
+        * http/tests/xmlhttprequest/workers/close.html: Added.
+
 2009-06-01  Alexey Proskuryakov  <ap@webkit.org>
 
         Reviewed by Darin Adler.
diff --git a/LayoutTests/fast/workers/resources/worker-close.js b/LayoutTests/fast/workers/resources/worker-close.js
new file mode 100644 (file)
index 0000000..1b04b05
--- /dev/null
@@ -0,0 +1,28 @@
+// Check to see if the worker handles pending events. Messages after close() will not be sent to the parent page, so we use exceptions instead to report failures after close().
+onmessage = function(evt)
+{
+    if (evt.data == "closeWithPendingEvents") {
+        // Set a timer to generate an event - minimum timeout is 1ms.
+        setTimeout(function() {
+                postMessage("pending event processed");
+                throw "should not be executed";
+            }, 1);
+        var start = new Date().getTime();
+        // Loop for 10 ms so the timer is ready to fire
+        while (new Date().getTime() - start < 100)
+            ;
+        // Now close - timer should not fire
+        close();
+    } else if (evt.data == "typeofClose") {
+        postMessage("typeof close: " + (typeof close));
+    } else if (evt.data == "close") {
+        close();
+        postMessage("Should not be delivered");
+    } else if (evt.data == "ping") {
+        postMessage("pong");
+    } else if (evt.data == "throw") {
+        throw "should never be executed";
+    } else {
+        postMessage("FAIL: Unknown message type: " + evt.data);
+    }
+}
index cfeabe2..e05f7ec 100644 (file)
@@ -13,6 +13,8 @@ onmessage = function(evt)
         postMessage("pong");
     else if (evt.data == "freeze")
         while (1) {}
+    else if (evt.data == "close")
+        close();
     else if (/eval.+/.test(evt.data)) {
         try {
             postMessage(evt.data.substr(5) + ": " + eval(evt.data.substr(5)));
diff --git a/LayoutTests/fast/workers/worker-close-expected.txt b/LayoutTests/fast/workers/worker-close-expected.txt
new file mode 100644 (file)
index 0000000..cdf40c8
--- /dev/null
@@ -0,0 +1,8 @@
+Test WorkerContext.close functionality. Should print a series of PASS messages, followed with DONE.
+
+PASS: typeof close: function
+PASS: received message before close
+PASS: messages sent after close() are ignored
+PASS: close() did not dispatch pending events
+DONE
+
diff --git a/LayoutTests/fast/workers/worker-close.html b/LayoutTests/fast/workers/worker-close.html
new file mode 100644 (file)
index 0000000..9d60b8b
--- /dev/null
@@ -0,0 +1,101 @@
+<body>
+<p>Test WorkerContext.close functionality. Should print a series of PASS messages, followed with DONE.</p>
+<div id=result></div>
+<script>
+function log(message)
+{
+    document.getElementById("result").innerHTML += message + "<br>";
+}
+
+if (window.layoutTestController)
+{
+    layoutTestController.dumpAsText();
+    layoutTestController.waitUntilDone();
+}
+
+var worker = new Worker('resources/worker-close.js');
+var timeout = 0;
+
+worker.postMessage("typeofClose");
+worker.onmessage = testTypeofClose;
+
+function testTypeofClose(evt)
+{
+    if (evt.data == "typeof close: function")
+        log("PASS: " + evt.data);
+    else
+        log("FAIL: " + evt.data);
+    worker.onmessage = testMessageAfterClose;
+    worker.postMessage("ping");
+}
+
+function testMessageAfterClose(evt) {
+    if (evt.data == "pong")
+        log("PASS: received message before close");
+    else
+        log("FAIL: received unknown response: " + evt.data);
+
+    // Tell the worker to close, then send a followup message
+    // which should not be delivered.
+    worker.postMessage("close");
+    worker.onmessage = function(evt) {
+        log("FAIL: Received message after worker closed: " + evt.data);
+        done();
+    };
+    // Make sure that messages don't arrive at the remote end - since they
+    // can't send back response messages, we'll have the worker throw an
+    // exception instead (errors are still propagated to the caller even after
+    // close()).
+    worker.postMessage("throw");
+    worker.onerror = function(evt) {
+        log("FAIL: message delivered after close(): " + evt.message);
+        done();
+    }
+    timeout = setTimeout(testPendingEvents, 1000);
+}
+
+function testPendingEvents()
+{
+    log("PASS: messages sent after close() are ignored");
+
+    // Now test that workers do not deliver pending events
+    worker = new Worker('resources/worker-close.js');
+    worker.postMessage("closeWithPendingEvents");
+    worker.onmessage = function(evt) {
+        log("FAIL: pending events should not fire:" + evt.data);
+        done();
+    }
+    worker.onerror = function(evt) {
+        log("FAIL: pending events should not fire:" + evt.message);
+        done();
+    }
+    timeout = setTimeout(testTerminateAfterClose, 500);
+}
+
+function testTerminateAfterClose()
+{
+    log("PASS: close() did not dispatch pending events");
+    worker = new Worker('resources/worker-class.js');
+    worker.postMessage("close");
+    worker.onmessage = function(evt) {
+        log("FAIL: Received message after worker closed: " + evt.data);
+        done();
+    };
+    // Give worker a chance to close first, then terminate it.
+    timeout = setTimeout(function() {
+        worker.terminate();
+        done();
+    }, 500);
+}
+
+function done() {
+    if (timeout)
+        clearTimeout(timeout);
+    log("DONE");
+    if (window.layoutTestController)
+        layoutTestController.notifyDone();
+}
+</script>
+</body>
+</html>
+
diff --git a/LayoutTests/http/tests/xmlhttprequest/workers/close-expected.txt b/LayoutTests/http/tests/xmlhttprequest/workers/close-expected.txt
new file mode 100644 (file)
index 0000000..09886ed
--- /dev/null
@@ -0,0 +1,6 @@
+Tests invoking close() in the middle of an XMLHttpRequest.
+
+PASS: Async test
+PASS: sync test
+DONE
+
diff --git a/LayoutTests/http/tests/xmlhttprequest/workers/close.html b/LayoutTests/http/tests/xmlhttprequest/workers/close.html
new file mode 100644 (file)
index 0000000..964ffc5
--- /dev/null
@@ -0,0 +1,72 @@
+<body>
+<p>Tests invoking close() in the middle of an XMLHttpRequest.</p>
+<div id=result></div>
+<script>
+    if (window.layoutTestController) {
+        layoutTestController.dumpAsText();
+        layoutTestController.waitUntilDone();
+    }
+
+    function log(message)
+    {
+        document.getElementById("result").innerHTML += message + "<br>";
+    }
+
+    var timeout = 0;
+    // Start with async tests
+    testAsync();
+
+    function testAsync() {
+        var worker = new Worker('resources/close.js');
+        worker.onerror = handleException;
+        worker.postMessage("async");
+        worker.onmessage = function(evt)
+        {
+            if (/DONE/.test(evt.data)) {
+                // Give worker a chance to complete the shutdown
+                setTimeout(function() {
+                    log("PASS: Async test");
+                    testSync();
+                }, 1000);
+            } else {
+                log("ERROR");
+                done();
+            }
+        }
+    }
+
+    function testSync() {
+        var worker = new Worker('resources/close.js');
+        worker.onerror = handleException;
+        worker.postMessage("sync");
+        worker.onmessage = function(evt)
+        {
+            if (/DONE/.test(evt.data)) {
+                // Give worker a chance to complete the shutdown
+                setTimeout(function() {
+                    log("PASS: sync test");
+                    log("DONE");
+                    done();
+                }, 1000);
+            } else {
+                log("ERROR");
+                done();
+            }
+        }
+    }
+
+   function done()
+    {
+        clearTimeout(timeout);
+        if (window.layoutTestController)
+            layoutTestController.notifyDone();
+    }
+
+    function handleException(evt)
+    {
+        log("ERROR - exception thrown: " + evt.message);
+        done();
+    }
+</script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/xmlhttprequest/workers/resources/close.js b/LayoutTests/http/tests/xmlhttprequest/workers/resources/close.js
new file mode 100644 (file)
index 0000000..726d393
--- /dev/null
@@ -0,0 +1,27 @@
+function done()
+{
+    postMessage("DONE");
+}
+
+onmessage = function(evt) {
+    req = new XMLHttpRequest();
+    req.onreadystatechange = processStateChange;
+    req.open("GET", "methods.cgi", evt.data == "async");
+    req.send("");
+}
+
+var failIfCalled = false;
+function processStateChange()
+{
+    if (failIfCalled)
+        // FIXME: XMLHttpRequest::didReceiveData() calls multiple event handlers without returning to the event loop. We need some way to stop active XHR requests, but calling stopActiveDOMObjects() is too draconian (stops everything, including nested workers).
+        //  throw "FAIL: processStateChange(" + req.readyState + ") called after close()";
+        return;
+
+    if (req.readyState > 1) {
+        failIfCalled = true;
+        done();
+        close();
+    }
+}
+
index b79e296..04898df 100644 (file)
@@ -1,3 +1,20 @@
+2009-06-01  Drew Wilson  <atwilson@google.com>
+
+        Reviewed by Darin Adler.  Landed (and tweaked) by Adam Barth.
+
+        https://bugs.webkit.org/show_bug.cgi?id=25902
+
+        Added WorkerContext.close()
+
+        Test: fast/workers/worker-close.html
+
+        * workers/WorkerContext.cpp:
+        (WebCore::WorkerContext::close):
+        * workers/WorkerContext.h:
+        * workers/WorkerContext.idl:
+        * workers/WorkerMessagingProxy.cpp:
+        (WebCore::WorkerMessagingProxy::workerContextDestroyedInternal):
+
 2009-06-01  Alexey Proskuryakov  <ap@webkit.org>
 
         Reviewed by Darin Adler.
index 5882ddb..59168c7 100644 (file)
@@ -58,6 +58,7 @@ WorkerContext::WorkerContext(const KURL& url, const String& userAgent, WorkerThr
     , m_userAgent(userAgent)
     , m_script(new WorkerScriptController(this))
     , m_thread(thread)
+    , m_closing(false)
 {
     setSecurityOrigin(SecurityOrigin::create(url));
 }
@@ -106,6 +107,15 @@ WorkerLocation* WorkerContext::location() const
     return m_location.get();
 }
 
+void WorkerContext::close()
+{
+    if (m_closing)
+        return;
+
+    m_closing = true;
+    m_thread->stop();
+}
+
 WorkerNavigator* WorkerContext::navigator() const
 {
     if (!m_navigator)
@@ -148,6 +158,9 @@ void WorkerContext::scriptImported(unsigned long, const String&)
 
 void WorkerContext::postMessage(const String& message)
 {
+    if (m_closing)
+        return;
+
     m_thread->workerObjectProxy()->postMessageToWorkerObject(message);
 }
 
@@ -229,6 +242,8 @@ void WorkerContext::clearInterval(int timeoutId)
 
 void WorkerContext::dispatchMessage(const String& message)
 {
+    // Since close() stops the thread event loop, this should not ever get called while closing.
+    ASSERT(!m_closing);
     RefPtr<Event> evt = MessageEvent::create(message, "", "", 0, 0);
 
     if (m_onmessageListener.get()) {
index bfddef1..ce782f8 100644 (file)
@@ -84,6 +84,7 @@ namespace WebCore {
         // WorkerGlobalScope
         WorkerContext* self() { return this; }
         WorkerLocation* location() const;
+        void close();
 
         // WorkerUtils
         void importScripts(const Vector<String>& urls, const String& callerURL, int callerLine, ExceptionCode&);
@@ -142,6 +143,8 @@ namespace WebCore {
 
         RefPtr<EventListener> m_onmessageListener;
         EventListenersMap m_eventListeners;
+
+        bool m_closing;
     };
 
 } // namespace WebCore
index d91c6e7..60568fb 100644 (file)
@@ -40,7 +40,7 @@ module threads {
                  attribute [Replaceable] WorkerContext self;
 #endif
                  attribute [Replaceable] WorkerLocation location;
-        // void close();
+        void close();
         //         attribute EventListener onclose;
         //         attribute EventListener onerror;
 
index 4b34658..07ee4f9 100644 (file)
@@ -296,6 +296,7 @@ void WorkerMessagingProxy::workerContextDestroyedInternal()
 {
     // WorkerContextDestroyedTask is always the last to be performed, so the proxy is not needed for communication
     // in either side any more. However, the Worker object may still exist, and it assumes that the proxy exists, too.
+    m_askedToTerminate = true;
     m_workerThread = 0;
     if (!m_workerObject)
         delete this;