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
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 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.
......@@ -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>
</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) {
}));
}
function handleFetch(event) {
event.respondWith(fetch('other.html'));
}
self.addEventListener('fetch', function(event) {
var url = event.request.url;
var handlers = [
{ pattern: 'helloworld', fn: handleHelloWorld },
{ pattern: '?ignore', fn: function() {} },
{ pattern: '?null', fn: handleNullBody },
{ pattern: '?reject', fn: handleReject }
{ pattern: '?reject', fn: handleReject },
{ pattern: '?fetch', fn: handleFetch }
];
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
PASS Service Worker does not respond to fetch event
PASS Service Worker responds to fetch event with null response body
PASS Service Worker rejects fetch event
PASS Service Worker fetches other file in fetch event
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 {
bool isClosing() { return m_closing; }
virtual void stopFetch() { }
bool idleNotification();
double timeOrigin() const { return m_timeOrigin; }
......
......@@ -204,7 +204,7 @@ public:
virtual void performTask(ExecutionContext *context)
{
WorkerGlobalScope* workerGlobalScope = toWorkerGlobalScope(context);
workerGlobalScope->stopFetch();
workerGlobalScope->stopActiveDOMObjects();
// Event listeners would keep DOMWrapperWorld objects alive for too long. Also, they have references to JS objects,
......
......@@ -653,6 +653,8 @@
'serviceworkers/Client.h',
'serviceworkers/FetchEvent.cpp',
'serviceworkers/FetchEvent.h',
'serviceworkers/FetchManager.cpp',
'serviceworkers/FetchManager.h',
'serviceworkers/HeaderMap.cpp',
'serviceworkers/HeaderMap.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 @@
#include "core/dom/DOMURLUtilsReadOnly.h"
#include "modules/serviceworkers/RequestInit.h"
#include "platform/NotImplemented.h"
#include "platform/network/ResourceRequest.h"
#include "public/platform/WebServiceWorkerRequest.h"
namespace WebCore {
......@@ -43,6 +44,14 @@ String Request::origin() const
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)
: m_url(KURL(ParsedURLString, requestInit.url))
, m_method(requestInit.method)
......
......@@ -18,6 +18,7 @@ namespace blink { class WebServiceWorkerRequest; }
namespace WebCore {
struct RequestInit;
class ResourceRequest;
class Request FINAL : public ScriptWrappable, public RefCounted<Request> {
public:
......@@ -34,6 +35,8 @@ public:
String origin() const;
PassRefPtr<HeaderMap> headers() const { return m_headers; }
PassOwnPtr<ResourceRequest> createResourceRequest() const;
private:
explicit Request(const RequestInit&);
explicit Request(const blink::WebServiceWorkerRequest&);
......
......@@ -33,12 +33,18 @@
#include "CachePolyfill.h"
#include "CacheStoragePolyfill.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/WorkerThreadStartupData.h"
#include "modules/EventTargetModules.h"
#include "modules/serviceworkers/FetchManager.h"
#include "modules/serviceworkers/Request.h"
#include "modules/serviceworkers/ServiceWorkerClients.h"
#include "modules/serviceworkers/ServiceWorkerGlobalScopeClient.h"
#include "modules/serviceworkers/ServiceWorkerThread.h"
#include "platform/network/ResourceRequest.h"
#include "platform/weborigin/KURL.h"
#include "public/platform/WebURL.h"
#include "wtf/CurrentTime.h"
......@@ -60,6 +66,7 @@ PassRefPtrWillBeRawPtr<ServiceWorkerGlobalScope> ServiceWorkerGlobalScope::creat
ServiceWorkerGlobalScope::ServiceWorkerGlobalScope(const KURL& url, const String& userAgent, ServiceWorkerThread* thread, double timeOrigin, PassOwnPtrWillBeRawPtr<WorkerClients> workerClients)
: WorkerGlobalScope(url, userAgent, thread, timeOrigin, workerClients)
, m_fetchManager(adoptPtr(new FetchManager(this)))
{
ScriptWrappable::init(this);
}
......@@ -68,11 +75,32 @@ ServiceWorkerGlobalScope::~ServiceWorkerGlobalScope()
{
}
void ServiceWorkerGlobalScope::stopFetch()
{
m_fetchManager.clear();
}
String ServiceWorkerGlobalScope::scope(ExecutionContext* context)
{
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()
{
if (!m_clients)
......
......@@ -36,6 +36,10 @@
namespace WebCore {
class FetchManager;
class Request;
class ScriptPromise;
class ScriptState;
class ServiceWorkerThread;
class ServiceWorkerClients;
class WorkerThreadStartupData;
......@@ -46,10 +50,13 @@ public:
virtual ~ServiceWorkerGlobalScope();
virtual bool isServiceWorkerGlobalScope() const OVERRIDE { return true; }
virtual void stopFetch() OVERRIDE;
// ServiceWorkerGlobalScope.idl
PassRefPtr<ServiceWorkerClients> clients();
String scope(ExecutionContext*);
ScriptPromise fetch(ScriptState*, Request*);
ScriptPromise fetch(ScriptState*, const String&);
// EventTarget
virtual const AtomicString& interfaceName() const OVERRIDE;
......@@ -66,6 +73,7 @@ private:
ServiceWorkerGlobalScope(const KURL&, const String& userAgent, ServiceWorkerThread*, double timeOrigin, PassOwnPtrWillBeRawPtr<WorkerClients>);
RefPtr<ServiceWorkerClients> m_clients;
OwnPtr<FetchManager> m_fetchManager;
};
} // namespace WebCore
......
......@@ -36,6 +36,9 @@
readonly attribute ServiceWorkerClients clients;
[CallWith=ExecutionContext, Unforgeable] readonly attribute DOMString scope;
[CallWith=ScriptState] Promise fetch(DOMString request);
[CallWith=ScriptState] Promise fetch(Request request);
attribute EventHandler onactivate;
attribute EventHandler onfetch;
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