Commit 4f84aa88 authored by falken@chromium.org's avatar falken@chromium.org

Make ServiceWorker an ActiveDOMObject

Before this patch, the ServiceWorker object returned by
navigator.serviceWorker.register() could be garbage collected
prematurely causing state change event handlers to never be invoked.
This patch makes ServiceWorker an ActiveDOMObject and keeps it alive
until either stop() is called on it (indicating detach of the parent
document) or it reaches the terminal "deactivated" state (soon to be
renamed "redundant").

For future work, it may be possible to be more clever and allow the SW
to die when it has no event handlers.

BUG=383972

Review URL: https://codereview.chromium.org/330173003

git-svn-id: svn://svn.chromium.org/blink/trunk@176197 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent e4f43999
...@@ -937,8 +937,6 @@ crbug.com/362992 storage/quota/storagequota-request-persistent-quota.html [ Skip ...@@ -937,8 +937,6 @@ crbug.com/362992 storage/quota/storagequota-request-persistent-quota.html [ Skip
crbug.com/362992 virtual/gpu/fast/canvas/canvas-createImageBitmap-invalid-args-in-workers.html [ Skip ] crbug.com/362992 virtual/gpu/fast/canvas/canvas-createImageBitmap-invalid-args-in-workers.html [ Skip ]
crbug.com/362992 virtual/gpu/fast/canvas/canvas-createImageBitmap-invalid-args.html [ Skip ] crbug.com/362992 virtual/gpu/fast/canvas/canvas-createImageBitmap-invalid-args.html [ Skip ]
crbug.com/381827 [ Linux Debug ] virtual/serviceworker/http/tests/serviceworker/fetch-event.html [ Pass Timeout ]
crbug.com/362434 [ Mac ] compositing/overflow/do-not-paint-outline-into-composited-scrolling-contents.html [ ImageOnlyFailure Pass ] crbug.com/362434 [ Mac ] compositing/overflow/do-not-paint-outline-into-composited-scrolling-contents.html [ ImageOnlyFailure Pass ]
crbug.com/362434 [ Mac ] virtual/gpu/compositedscrolling/overflow/do-not-paint-outline-into-composited-scrolling-contents.html [ ImageOnlyFailure Pass ] crbug.com/362434 [ Mac ] virtual/gpu/compositedscrolling/overflow/do-not-paint-outline-into-composited-scrolling-contents.html [ ImageOnlyFailure Pass ]
......
...@@ -68,7 +68,7 @@ var worker = 'resources/fetch-event-test-worker.js'; ...@@ -68,7 +68,7 @@ var worker = 'resources/fetch-event-test-worker.js';
(function() { (function() {
var t = async_test('Service Worker rejects fetch event'); var t = async_test('Service Worker rejects fetch event');
var scope = 'resources/simple.html?ignore'; var scope = 'resources/simple.html?reject';
service_worker_unregister_and_register(t, worker, scope, onRegister); service_worker_unregister_and_register(t, worker, scope, onRegister);
function onRegister(sw) { function onRegister(sw) {
......
Test that a registered Service Worker with an event handler is not garbage collected prematurely
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
FAIL Could not register worker: NotSupportedError: Service Worker support is disabled.
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE html>
<script src="/js-test-resources/js-test.js"></script>
<body>
<script>
window.jsTestIsAsync = true;
description('Test that a registered Service Worker with an event handler is not garbage collected prematurely');
swObservation = null;
scope = 'gc';
if (!window.internals) {
testFailed('This test requires internals.observeGC');
finishJSTest();
} else {
setup();
}
function setup() {
var worker = 'resources/empty-worker.js';
unregisterAndRegister(worker, scope).then(onRegister);
}
function unregisterAndRegister(url, scope) {
return navigator.serviceWorker.unregister(scope).then(function() {
return navigator.serviceWorker.register(url, { scope: scope });
}).catch(function(error) {
testFailed('Could not register worker: ' + error);
finishJSTest();
});
}
function assertServiceWorkerIsNotCollected() {
gc();
shouldBeFalse('swObservation.wasCollected')
}
function onRegister(sw) {
swObservation = internals.observeGC(sw);
sw.addEventListener('statechange', onStateChange);
setTimeout(assertServiceWorkerIsNotCollected, 0);
}
function onStateChange(event) {
assertServiceWorkerIsNotCollected();
if (event.target.state != 'active')
return;
navigator.serviceWorker.unregister(scope).then(onUnregister);
}
function onUnregister()
{
// FIXME: Assert that the ServiceWorker *is* collected when the 'redundant' state is implemented.
assertServiceWorkerIsNotCollected();
finishJSTest();
}
</script>
</body>
Test that a registered Service Worker with an event handler is not garbage collected prematurely
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS swObservation.wasCollected is false
PASS swObservation.wasCollected is false
PASS swObservation.wasCollected is false
PASS swObservation.wasCollected is false
PASS swObservation.wasCollected is false
PASS swObservation.wasCollected is false
PASS successfullyParsed is true
TEST COMPLETE
...@@ -86,7 +86,7 @@ void ServiceWorker::postMessage(PassRefPtr<SerializedScriptValue> message, const ...@@ -86,7 +86,7 @@ void ServiceWorker::postMessage(PassRefPtr<SerializedScriptValue> message, const
bool ServiceWorker::isReady() bool ServiceWorker::isReady()
{ {
return !m_isPromisePending; return m_proxyState == Ready;
} }
void ServiceWorker::dispatchStateChangeEvent() void ServiceWorker::dispatchStateChangeEvent()
...@@ -161,21 +161,58 @@ PassRefPtr<ServiceWorker> ServiceWorker::from(ScriptPromiseResolverWithContext* ...@@ -161,21 +161,58 @@ PassRefPtr<ServiceWorker> ServiceWorker::from(ScriptPromiseResolverWithContext*
return serviceWorker; return serviceWorker;
} }
void ServiceWorker::setProxyState(ProxyState state)
{
if (m_proxyState == state)
return;
switch (m_proxyState) {
case Initial:
ASSERT(state == RegisterPromisePending || state == ContextStopped);
break;
case RegisterPromisePending:
ASSERT(state == Ready || state == ContextStopped);
break;
case Ready:
ASSERT(state == ContextStopped);
break;
case ContextStopped:
ASSERT_NOT_REACHED();
break;
}
ProxyState oldState = m_proxyState;
m_proxyState = state;
if (oldState == Ready || state == Ready)
m_outerWorker->proxyReadyChanged();
}
void ServiceWorker::onPromiseResolved() void ServiceWorker::onPromiseResolved()
{ {
ASSERT(m_isPromisePending); if (m_proxyState == ContextStopped)
m_isPromisePending = false; return;
m_outerWorker->proxyReadyChanged(); setProxyState(Ready);
} }
void ServiceWorker::waitOnPromise(ScriptPromise promise) void ServiceWorker::waitOnPromise(ScriptPromise promise)
{ {
ASSERT(!m_isPromisePending); setProxyState(RegisterPromisePending);
m_isPromisePending = true;
m_outerWorker->proxyReadyChanged();
promise.then(ThenFunction::create(this)); promise.then(ThenFunction::create(this));
} }
bool ServiceWorker::hasPendingActivity() const
{
if (AbstractWorker::hasPendingActivity())
return true;
if (m_proxyState == ContextStopped)
return false;
return m_outerWorker->state() != blink::WebServiceWorkerStateDeactivated;
}
void ServiceWorker::stop()
{
setProxyState(ContextStopped);
}
PassRefPtr<ServiceWorker> ServiceWorker::create(ExecutionContext* executionContext, PassOwnPtr<blink::WebServiceWorker> outerWorker) PassRefPtr<ServiceWorker> ServiceWorker::create(ExecutionContext* executionContext, PassOwnPtr<blink::WebServiceWorker> outerWorker)
{ {
RefPtr<ServiceWorker> worker = adoptRef(new ServiceWorker(executionContext, outerWorker)); RefPtr<ServiceWorker> worker = adoptRef(new ServiceWorker(executionContext, outerWorker));
...@@ -187,7 +224,7 @@ ServiceWorker::ServiceWorker(ExecutionContext* executionContext, PassOwnPtr<blin ...@@ -187,7 +224,7 @@ ServiceWorker::ServiceWorker(ExecutionContext* executionContext, PassOwnPtr<blin
: AbstractWorker(executionContext) : AbstractWorker(executionContext)
, WebServiceWorkerProxy(this) , WebServiceWorkerProxy(this)
, m_outerWorker(worker) , m_outerWorker(worker)
, m_isPromisePending(false) , m_proxyState(Initial)
{ {
ScriptWrappable::init(this); ScriptWrappable::init(this);
ASSERT(m_outerWorker); ASSERT(m_outerWorker);
......
...@@ -77,13 +77,25 @@ public: ...@@ -77,13 +77,25 @@ public:
private: private:
class ThenFunction; class ThenFunction;
enum ProxyState {
Initial,
RegisterPromisePending,
Ready,
ContextStopped
};
static PassRefPtr<ServiceWorker> create(ExecutionContext*, PassOwnPtr<blink::WebServiceWorker>); static PassRefPtr<ServiceWorker> create(ExecutionContext*, PassOwnPtr<blink::WebServiceWorker>);
ServiceWorker(ExecutionContext*, PassOwnPtr<blink::WebServiceWorker>); ServiceWorker(ExecutionContext*, PassOwnPtr<blink::WebServiceWorker>);
void setProxyState(ProxyState);
void onPromiseResolved(); void onPromiseResolved();
void waitOnPromise(ScriptPromise); void waitOnPromise(ScriptPromise);
// ActiveDOMObject overrides.
virtual bool hasPendingActivity() const OVERRIDE;
virtual void stop() OVERRIDE;
OwnPtr<blink::WebServiceWorker> m_outerWorker; OwnPtr<blink::WebServiceWorker> m_outerWorker;
bool m_isPromisePending; ProxyState m_proxyState;
}; };
} // namespace WebCore } // namespace WebCore
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
[ [
ActiveDOMObject,
RuntimeEnabled=ServiceWorker RuntimeEnabled=ServiceWorker
] interface ServiceWorker : EventTarget { ] interface ServiceWorker : EventTarget {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment