Commit 85e6f911 authored by horo@chromium.org's avatar horo@chromium.org

Initial implementation of ServiceWorkerGlobalScope.fetch()

BUG=373120

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

git-svn-id: svn://svn.chromium.org/blink/trunk@176186 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 49f003e1
...@@ -3,5 +3,6 @@ FAIL Service Worker responds to fetch event with blob body assert_unreached: Unr ...@@ -3,5 +3,6 @@ FAIL Service Worker responds to fetch event with blob body assert_unreached: Unr
FAIL Service Worker does not respond to fetch event assert_unreached: Unregister should not fail: NotSupportedError Reached unreachable code FAIL Service Worker does not respond to fetch event assert_unreached: Unregister should not fail: NotSupportedError Reached unreachable code
FAIL Service Worker responds to fetch event with null response body assert_unreached: Unregister should not fail: NotSupportedError Reached unreachable code FAIL Service Worker responds to fetch event with null response body assert_unreached: Unregister should not fail: NotSupportedError Reached unreachable code
FAIL Service Worker rejects fetch event assert_unreached: Unregister should not fail: NotSupportedError Reached unreachable code FAIL Service Worker rejects fetch event assert_unreached: Unregister should not fail: NotSupportedError Reached unreachable code
FAIL Service Worker fetches other file in fetch event assert_unreached: Unregister should not fail: NotSupportedError Reached unreachable code
Harness: the test ran to completion. Harness: the test ran to completion.
...@@ -85,5 +85,25 @@ var worker = 'resources/fetch-event-test-worker.js'; ...@@ -85,5 +85,25 @@ var worker = 'resources/fetch-event-test-worker.js';
})); }));
} }
}()); }());
(function() {
var t = async_test('Service Worker fetches other file in fetch event');
var scope = 'resources/simple.html?fetch';
service_worker_unregister_and_register(t, worker, scope, onRegister);
function onRegister(sw) {
sw.addEventListener('statechange', t.step_func(onStateChange));
}
function onStateChange(event) {
if (event.target.state != 'active')
return;
with_iframe(scope, t.step_func(function(frame) {
assert_equals(frame.contentDocument.body.textContent, 'Here\'s an other html file.\n',
'Response should come from fetched other file');
service_worker_unregister_and_done(t, scope);
}));
}
}());
</script> </script>
</body> </body>
This is a testharness.js-based test.
FAIL Verify fetch() in a Service Worker assert_unreached: Unregister should not fail: NotSupportedError Reached unreachable code
Harness: the test ran to completion.
<!DOCTYPE html>
<title>Service Worker: fetch()</title>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="resources/test-helpers.js"></script>
<script>
var test = async_test('Verify fetch() in a Service Worker');
test.step(function() {
var scope = 'resources/blank.html';
service_worker_unregister_and_register(
test, 'resources/fetch-worker.js', scope, onRegister);
function onRegister(worker) {
var messageChannel = new MessageChannel();
messageChannel.port1.onmessage = test.step_func(onMessage);
worker.postMessage({port: messageChannel.port2}, [messageChannel.port2]);
}
var result = [];
var expected = [
'Resolved: other.html',
'Rejected: http:// : Invalid URL',
'Rejected: http://www.example.com/foo : Failed to fetch',
];
function onMessage(e) {
var message = e.data;
if (message == 'quit') {
assert_array_equals(result, expected, 'Worker should post back expected values.');
service_worker_unregister_and_done(test, scope);
} else {
result.push(message);
}
}
});
</script>
...@@ -12,13 +12,18 @@ function handleReject(event) { ...@@ -12,13 +12,18 @@ function handleReject(event) {
})); }));
} }
function handleFetch(event) {
event.respondWith(fetch('other.html'));
}
self.addEventListener('fetch', function(event) { self.addEventListener('fetch', function(event) {
var url = event.request.url; var url = event.request.url;
var handlers = [ var handlers = [
{ pattern: 'helloworld', fn: handleHelloWorld }, { pattern: 'helloworld', fn: handleHelloWorld },
{ pattern: '?ignore', fn: function() {} }, { pattern: '?ignore', fn: function() {} },
{ pattern: '?null', fn: handleNullBody }, { pattern: '?null', fn: handleNullBody },
{ pattern: '?reject', fn: handleReject } { pattern: '?reject', fn: handleReject },
{ pattern: '?fetch', fn: handleFetch }
]; ];
var handler = null; var handler = null;
......
self.onmessage = function(e) {
var message = e.data;
if ('port' in message) {
port = message.port;
doNextFetchTest(port);
}
};
var testTargets = [
'other.html',
'http://',
'http://www.example.com/foo'
];
function doNextFetchTest(port) {
if (testTargets.length == 0) {
port.postMessage('quit');
// Destroying the execution context while fetch is happening should not cause a crash.
fetch('dummy.html').then(function() {}).catch(function() {});
self.close();
return;
}
var target = testTargets.shift();
fetch(target)
.then(function() {
port.postMessage('Resolved: ' + target);
doNextFetchTest(port);
}).catch(function(e) {
port.postMessage('Rejected: ' + target + ' : '+ e.message);
doNextFetchTest(port);
});
};
<!DOCTYPE html>
<title>Other</title>
Here's an other html file.
...@@ -4,5 +4,6 @@ PASS Service Worker responds to fetch event with blob body ...@@ -4,5 +4,6 @@ PASS Service Worker responds to fetch event with blob body
PASS Service Worker does not respond to fetch event PASS Service Worker does not respond to fetch event
PASS Service Worker responds to fetch event with null response body PASS Service Worker responds to fetch event with null response body
PASS Service Worker rejects fetch event PASS Service Worker rejects fetch event
PASS Service Worker fetches other file in fetch event
Harness: the test ran to completion. Harness: the test ran to completion.
This is a testharness.js-based test.
PASS Verify fetch() in a Service Worker
Harness: the test ran to completion.
...@@ -129,6 +129,8 @@ namespace WebCore { ...@@ -129,6 +129,8 @@ namespace WebCore {
bool isClosing() { return m_closing; } bool isClosing() { return m_closing; }
virtual void stopFetch() { }
bool idleNotification(); bool idleNotification();
double timeOrigin() const { return m_timeOrigin; } double timeOrigin() const { return m_timeOrigin; }
......
...@@ -204,7 +204,7 @@ public: ...@@ -204,7 +204,7 @@ public:
virtual void performTask(ExecutionContext *context) virtual void performTask(ExecutionContext *context)
{ {
WorkerGlobalScope* workerGlobalScope = toWorkerGlobalScope(context); WorkerGlobalScope* workerGlobalScope = toWorkerGlobalScope(context);
workerGlobalScope->stopFetch();
workerGlobalScope->stopActiveDOMObjects(); workerGlobalScope->stopActiveDOMObjects();
// Event listeners would keep DOMWrapperWorld objects alive for too long. Also, they have references to JS objects, // Event listeners would keep DOMWrapperWorld objects alive for too long. Also, they have references to JS objects,
......
...@@ -653,6 +653,8 @@ ...@@ -653,6 +653,8 @@
'serviceworkers/Client.h', 'serviceworkers/Client.h',
'serviceworkers/FetchEvent.cpp', 'serviceworkers/FetchEvent.cpp',
'serviceworkers/FetchEvent.h', 'serviceworkers/FetchEvent.h',
'serviceworkers/FetchManager.cpp',
'serviceworkers/FetchManager.h',
'serviceworkers/HeaderMap.cpp', 'serviceworkers/HeaderMap.cpp',
'serviceworkers/HeaderMap.h', 'serviceworkers/HeaderMap.h',
'serviceworkers/HeaderMapForEachCallback.h', 'serviceworkers/HeaderMapForEachCallback.h',
......
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "config.h"
#include "FetchManager.h"
#include "bindings/v8/ScriptPromiseResolverWithContext.h"
#include "bindings/v8/ScriptState.h"
#include "bindings/v8/V8ThrowException.h"
#include "core/dom/ExceptionCode.h"
#include "core/fileapi/Blob.h"
#include "core/loader/ThreadableLoader.h"
#include "core/loader/ThreadableLoaderClient.h"
#include "modules/serviceworkers/Response.h"
#include "platform/network/ResourceRequest.h"
#include "wtf/HashSet.h"
namespace WebCore {
class FetchManager::Loader : public ThreadableLoaderClient {
public:
Loader(ExecutionContext*, FetchManager*, PassRefPtr<ScriptPromiseResolverWithContext>, PassOwnPtr<ResourceRequest>);
~Loader();
virtual void didReceiveResponse(unsigned long, const ResourceResponse&);
virtual void didFinishLoading(unsigned long, double);
virtual void didFail(const ResourceError&);
virtual void didFailAccessControlCheck(const ResourceError&);
virtual void didFailRedirectCheck();
virtual void didDownloadData(int);
void start();
void cleanup();
private:
void failed();
void notifyFinished();
ExecutionContext* m_executionContext;
FetchManager* m_fetchManager;
RefPtr<ScriptPromiseResolverWithContext> m_resolver;
OwnPtr<ResourceRequest> m_request;
RefPtr<ThreadableLoader> m_loader;
ResourceResponse m_response;
long long m_downloadedBlobLength;
bool m_failed;
};
FetchManager::Loader::Loader(ExecutionContext* executionContext, FetchManager* fetchManager, PassRefPtr<ScriptPromiseResolverWithContext> resolver, PassOwnPtr<ResourceRequest> request)
: m_executionContext(executionContext)
, m_fetchManager(fetchManager)
, m_resolver(resolver)
, m_request(request)
, m_downloadedBlobLength(0)
, m_failed(false)
{
}
FetchManager::Loader::~Loader()
{
if (m_loader)
m_loader->cancel();
}
void FetchManager::Loader::didReceiveResponse(unsigned long, const ResourceResponse& response)
{
m_response = response;
}
void FetchManager::Loader::didFinishLoading(unsigned long, double)
{
OwnPtr<BlobData> blobData = BlobData::create();
String filePath = m_response.downloadedFilePath();
if (!filePath.isEmpty() && m_downloadedBlobLength) {
blobData->appendFile(filePath);
// FIXME: Set the ContentType correctly.
}
Dictionary options;
// FIXME: fill options.
RefPtrWillBeRawPtr<Blob> blob = Blob::create(BlobDataHandle::create(blobData.release(), m_downloadedBlobLength));
// FIXME: Handle response status correctly.
m_resolver->resolve(Response::create(blob.get(), options));
notifyFinished();
}
void FetchManager::Loader::didFail(const ResourceError& error)
{
failed();
}
void FetchManager::Loader::didFailAccessControlCheck(const ResourceError& error)
{
failed();
}
void FetchManager::Loader::didFailRedirectCheck()
{
failed();
}
void FetchManager::Loader::didDownloadData(int dataLength)
{
m_downloadedBlobLength += dataLength;
}
void FetchManager::Loader::start()
{
m_request->setDownloadToFile(true);
ThreadableLoaderOptions options;
// FIXME: Fill options.
ResourceLoaderOptions resourceLoaderOptions;
resourceLoaderOptions.dataBufferingPolicy = DoNotBufferData;
// FIXME: Fill resourceLoaderOptions.
m_loader = ThreadableLoader::create(*m_executionContext, this, *m_request, options, resourceLoaderOptions);
}
void FetchManager::Loader::cleanup()
{
// Prevent notification
m_fetchManager = 0;
if (m_loader) {
m_loader->cancel();
m_loader.clear();
}
}
void FetchManager::Loader::failed()
{
if (m_failed)
return;
m_failed = true;
ScriptState* state = m_resolver->scriptState();
ScriptState::Scope scope(state);
m_resolver->reject(V8ThrowException::createTypeError("Failed to fetch", state->isolate()));
notifyFinished();
}
void FetchManager::Loader::notifyFinished()
{
if (m_fetchManager)
m_fetchManager->onLoaderFinished(this);
}
FetchManager::FetchManager(ExecutionContext* executionContext)
: m_executionContext(executionContext)
{
}
FetchManager::~FetchManager()
{
for (HashSet<OwnPtr<Loader> >::iterator it = m_loaders.begin(); it != m_loaders.end(); ++it) {
(*it)->cleanup();
}
}
ScriptPromise FetchManager::fetch(ScriptState* scriptState, PassOwnPtr<ResourceRequest> request)
{
RefPtr<ScriptPromiseResolverWithContext> resolver = ScriptPromiseResolverWithContext::create(scriptState);
ScriptPromise promise = resolver->promise();
OwnPtr<Loader> loader(adoptPtr(new Loader(m_executionContext, this, resolver.release(), request)));
(*m_loaders.add(loader.release()).storedValue)->start();
return promise;
}
void FetchManager::onLoaderFinished(Loader* loader)
{
m_loaders.remove(loader);
}
} // namespace WebCore
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FetchManager_h
#define FetchManager_h
#include "bindings/v8/ScriptPromise.h"
#include "wtf/HashSet.h"
#include "wtf/OwnPtr.h"
namespace WebCore {
class ExecutionContext;
class ScriptState;
class ResourceRequest;
class FetchManager {
public:
FetchManager(ExecutionContext*);
~FetchManager();
ScriptPromise fetch(ScriptState*, PassOwnPtr<ResourceRequest>);
private:
class Loader;
// Removes loader from |m_loaders|.
void onLoaderFinished(Loader*);
ExecutionContext* m_executionContext;
HashSet<OwnPtr<Loader> > m_loaders;
};
} // namespace WebCore
#endif // FetchManager_h
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "core/dom/DOMURLUtilsReadOnly.h" #include "core/dom/DOMURLUtilsReadOnly.h"
#include "modules/serviceworkers/RequestInit.h" #include "modules/serviceworkers/RequestInit.h"
#include "platform/NotImplemented.h" #include "platform/NotImplemented.h"
#include "platform/network/ResourceRequest.h"
#include "public/platform/WebServiceWorkerRequest.h" #include "public/platform/WebServiceWorkerRequest.h"
namespace WebCore { namespace WebCore {
...@@ -43,6 +44,14 @@ String Request::origin() const ...@@ -43,6 +44,14 @@ String Request::origin() const
return DOMURLUtilsReadOnly::origin(m_url); return DOMURLUtilsReadOnly::origin(m_url);
} }
PassOwnPtr<ResourceRequest> Request::createResourceRequest() const
{
OwnPtr<ResourceRequest> request = adoptPtr(new ResourceRequest(m_url));
request->setHTTPMethod("GET");
// FIXME: Fill more info.
return request.release();
}
Request::Request(const RequestInit& requestInit) Request::Request(const RequestInit& requestInit)
: m_url(KURL(ParsedURLString, requestInit.url)) : m_url(KURL(ParsedURLString, requestInit.url))
, m_method(requestInit.method) , m_method(requestInit.method)
......
...@@ -18,6 +18,7 @@ namespace blink { class WebServiceWorkerRequest; } ...@@ -18,6 +18,7 @@ namespace blink { class WebServiceWorkerRequest; }
namespace WebCore { namespace WebCore {
struct RequestInit; struct RequestInit;
class ResourceRequest;
class Request FINAL : public ScriptWrappable, public RefCounted<Request> { class Request FINAL : public ScriptWrappable, public RefCounted<Request> {
public: public:
...@@ -34,6 +35,8 @@ public: ...@@ -34,6 +35,8 @@ public:
String origin() const; String origin() const;
PassRefPtr<HeaderMap> headers() const { return m_headers; } PassRefPtr<HeaderMap> headers() const { return m_headers; }
PassOwnPtr<ResourceRequest> createResourceRequest() const;
private: private:
explicit Request(const RequestInit&); explicit Request(const RequestInit&);
explicit Request(const blink::WebServiceWorkerRequest&); explicit Request(const blink::WebServiceWorkerRequest&);
......
...@@ -33,12 +33,18 @@ ...@@ -33,12 +33,18 @@
#include "CachePolyfill.h" #include "CachePolyfill.h"
#include "CacheStoragePolyfill.h" #include "CacheStoragePolyfill.h"
#include "FetchPolyfill.h" #include "FetchPolyfill.h"
#include "bindings/v8/ScriptPromise.h"
#include "bindings/v8/ScriptState.h"
#include "bindings/v8/V8ThrowException.h"
#include "core/workers/WorkerClients.h" #include "core/workers/WorkerClients.h"
#include "core/workers/WorkerThreadStartupData.h" #include "core/workers/WorkerThreadStartupData.h"
#include "modules/EventTargetModules.h" #include "modules/EventTargetModules.h"
#include "modules/serviceworkers/FetchManager.h"
#include "modules/serviceworkers/Request.h"
#include "modules/serviceworkers/ServiceWorkerClients.h" #include "modules/serviceworkers/ServiceWorkerClients.h"
#include "modules/serviceworkers/ServiceWorkerGlobalScopeClient.h" #include "modules/serviceworkers/ServiceWorkerGlobalScopeClient.h"
#include "modules/serviceworkers/ServiceWorkerThread.h" #include "modules/serviceworkers/ServiceWorkerThread.h"
#include "platform/network/ResourceRequest.h"
#include "platform/weborigin/KURL.h" #include "platform/weborigin/KURL.h"
#include "public/platform/WebURL.h" #include "public/platform/WebURL.h"
#include "wtf/CurrentTime.h" #include "wtf/CurrentTime.h"
...@@ -60,6 +66,7 @@ PassRefPtrWillBeRawPtr<ServiceWorkerGlobalScope> ServiceWorkerGlobalScope::creat ...@@ -60,6 +66,7 @@ PassRefPtrWillBeRawPtr<ServiceWorkerGlobalScope> ServiceWorkerGlobalScope::creat
ServiceWorkerGlobalScope::ServiceWorkerGlobalScope(const KURL& url, const String& userAgent, ServiceWorkerThread* thread, double timeOrigin, PassOwnPtrWillBeRawPtr<WorkerClients> workerClients) ServiceWorkerGlobalScope::ServiceWorkerGlobalScope(const KURL& url, const String& userAgent, ServiceWorkerThread* thread, double timeOrigin, PassOwnPtrWillBeRawPtr<WorkerClients> workerClients)
: WorkerGlobalScope(url, userAgent, thread, timeOrigin, workerClients) : WorkerGlobalScope(url, userAgent, thread, timeOrigin, workerClients)
, m_fetchManager(adoptPtr(new FetchManager(this)))
{ {
ScriptWrappable::init(this); ScriptWrappable::init(this);
} }
...@@ -68,11 +75,32 @@ ServiceWorkerGlobalScope::~ServiceWorkerGlobalScope() ...@@ -68,11 +75,32 @@ ServiceWorkerGlobalScope::~ServiceWorkerGlobalScope()
{ {
} }
void ServiceWorkerGlobalScope::stopFetch()
{
m_fetchManager.clear();
}
String ServiceWorkerGlobalScope::scope(ExecutionContext* context) String ServiceWorkerGlobalScope::scope(ExecutionContext* context)
{ {
return ServiceWorkerGlobalScopeClient::from(context)->scope().string(); return ServiceWorkerGlobalScopeClient::from(context)->scope().string();
} }
ScriptPromise ServiceWorkerGlobalScope::fetch(ScriptState* scriptState, Request* request)
{
OwnPtr<ResourceRequest> resourceRequest(request->createResourceRequest());
return m_fetchManager->fetch(scriptState, resourceRequest.release());
}
ScriptPromise ServiceWorkerGlobalScope::fetch(ScriptState* scriptState, const String& urlstring)
{
KURL url = completeURL(urlstring);
if (!url.isValid())
return ScriptPromise::reject(scriptState, V8ThrowException::createTypeError("Invalid URL", scriptState->isolate()));
OwnPtr<ResourceRequest> resourceRequest = adoptPtr(new ResourceRequest(url));
resourceRequest->setHTTPMethod("GET");
return m_fetchManager->fetch(scriptState, resourceRequest.release());
}
PassRefPtr<ServiceWorkerClients> ServiceWorkerGlobalScope::clients() PassRefPtr<ServiceWorkerClients> ServiceWorkerGlobalScope::clients()
{ {
if (!m_clients) if (!m_clients)
......
...@@ -36,6 +36,10 @@ ...@@ -36,6 +36,10 @@
namespace WebCore { namespace WebCore {
class FetchManager;
class Request;
class ScriptPromise;
class ScriptState;
class ServiceWorkerThread; class ServiceWorkerThread;
class ServiceWorkerClients; class ServiceWorkerClients;
class WorkerThreadStartupData; class WorkerThreadStartupData;
...@@ -46,10 +50,13 @@ public: ...@@ -46,10 +50,13 @@ public:
virtual ~ServiceWorkerGlobalScope(); virtual ~ServiceWorkerGlobalScope();
virtual bool isServiceWorkerGlobalScope() const OVERRIDE { return true; } virtual bool isServiceWorkerGlobalScope() const OVERRIDE { return true; }
virtual void stopFetch() OVERRIDE;
// ServiceWorkerGlobalScope.idl // ServiceWorkerGlobalScope.idl
PassRefPtr<ServiceWorkerClients> clients(); PassRefPtr<ServiceWorkerClients> clients();
String scope(ExecutionContext*); String scope(ExecutionContext*);
ScriptPromise fetch(ScriptState*, Request*);
ScriptPromise fetch(ScriptState*, const String&);
// EventTarget // EventTarget
virtual const AtomicString& interfaceName() const OVERRIDE; virtual const AtomicString& interfaceName() const OVERRIDE;
...@@ -66,6 +73,7 @@ private: ...@@ -66,6 +73,7 @@ private:
ServiceWorkerGlobalScope(const KURL&, const String& userAgent, ServiceWorkerThread*, double timeOrigin, PassOwnPtrWillBeRawPtr<WorkerClients>); ServiceWorkerGlobalScope(const KURL&, const String& userAgent, ServiceWorkerThread*, double timeOrigin, PassOwnPtrWillBeRawPtr<WorkerClients>);
RefPtr<ServiceWorkerClients> m_clients; RefPtr<ServiceWorkerClients> m_clients;
OwnPtr<FetchManager> m_fetchManager;
}; };
} // namespace WebCore } // namespace WebCore
......
...@@ -36,6 +36,9 @@ ...@@ -36,6 +36,9 @@
readonly attribute ServiceWorkerClients clients; readonly attribute ServiceWorkerClients clients;
[CallWith=ExecutionContext, Unforgeable] readonly attribute DOMString scope; [CallWith=ExecutionContext, Unforgeable] readonly attribute DOMString scope;
[CallWith=ScriptState] Promise fetch(DOMString request);
[CallWith=ScriptState] Promise fetch(Request request);
attribute EventHandler onactivate; attribute EventHandler onactivate;
attribute EventHandler onfetch; attribute EventHandler onfetch;
attribute EventHandler oninstall; attribute EventHandler oninstall;
......
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