Commit a0ca639a authored by Mugdha Lakhani's avatar Mugdha Lakhani Committed by Commit Bot

Add a list of BackgroundFetchRecord objects to BackgroundFetchRegistration.

When we allow access to active background fetches, BackgroundFetchRecords for
the fetch can be accessed from multiple places. It simplifies matters if
we return the same record for a request whenever it's accessed.

This CL adds a (private) list of BackgroundFetchRecords to the
BackgroundFetchRegistration object, and updates it every time match and
matchAll() are called.

The second change introduced here is to not immediately resolve
responseReady() if the fetch is active and a response for the request
isn't yet available. Once the fetch has completed, or there's a response
available for the request, we resolve pending promises. We also make sure to
return the same promise (resolved or unresolved) for a given record,
every time responseReady is called.

For a more detailed discussion, see the following doc:
https://docs.google.com/document/d/1CrbWrnnshhyp_SfiAeuODpnQX36GK3Bsi19rXQGez6Q/edit?usp=sharing

Bug: 875201

Change-Id: I8cb386efd19086c0993ad2be2fb2691ad90597ec
Reviewed-on: https://chromium-review.googlesource.com/c/1336151
Commit-Queue: Mugdha Lakhani <nator@chromium.org>
Reviewed-by: default avatarRayan Kanso <rayankans@chromium.org>
Reviewed-by: default avatarPeter Beverloo <peter@chromium.org>
Cr-Commit-Position: refs/heads/master@{#610410}
parent fa530e33
...@@ -14,5 +14,6 @@ PASS Using Background Fetch to fetch a non-existent resource should fail. ...@@ -14,5 +14,6 @@ PASS Using Background Fetch to fetch a non-existent resource should fail.
PASS Fetches with mixed content should fail. PASS Fetches with mixed content should fail.
PASS Matching to a single request should work PASS Matching to a single request should work
PASS Matching to a non-existing request should work PASS Matching to a non-existing request should work
PASS Matching multiple times on the same request works as expected.
Harness: the test ran to completion. Harness: the test ran to completion.
...@@ -300,3 +300,27 @@ backgroundFetchTest(async (test, backgroundFetch) => { ...@@ -300,3 +300,27 @@ backgroundFetchTest(async (test, backgroundFetch) => {
}, 'Matching to a non-existing request should work'); }, 'Matching to a non-existing request should work');
backgroundFetchTest(async (test, backgroundFetch) => {
const registrationId = 'matchexistingrequesttwice';
const registration =
await backgroundFetch.fetch(registrationId, 'resources/feature-name.txt');
assert_equals(registration.id, registrationId);
const {type, eventRegistration, results} = await getMessageFromServiceWorker();
assert_equals('backgroundfetchsuccess', type);
assert_equals(results.length, 2);
assert_equals(eventRegistration.id, registration.id);
assert_equals(eventRegistration.result, 'success');
assert_equals(eventRegistration.failureReason, '');
assert_true(results[0].url.includes('resources/feature-name.txt'));
assert_equals(results[0].status, 200);
assert_equals(results[0].text, 'Background Fetch');
assert_true(results[1].url.includes('resources/feature-name.txt'));
assert_equals(results[1].status, 200);
assert_equals(results[1].text, 'error');
}, 'Matching multiple times on the same request works as expected.');
...@@ -8,17 +8,25 @@ async function getFetchResult(record) { ...@@ -8,17 +8,25 @@ async function getFetchResult(record) {
return { return {
url: response.url, url: response.url,
status: response.status, status: response.status,
text: await response.text(), text: await response.text().catch(() => 'error'),
}; };
} }
function handleBackgroundFetchEvent(event) { function handleBackgroundFetchEvent(event) {
let matchFunction = null; let matchFunction = null;
switch (event.registration.id) { switch (event.registration.id) {
case 'matchexistingrequest': case 'matchexistingrequest':
matchFunction = event.registration.match.bind( matchFunction = event.registration.match.bind(
event.registration, '/background-fetch/resources/feature-name.txt'); event.registration, '/background-fetch/resources/feature-name.txt');
break; break;
case 'matchexistingrequesttwice':
matchFunction = (async () => {
const match1 = await event.registration.match('/background-fetch/resources/feature-name.txt');
const match2 = await event.registration.match('/background-fetch/resources/feature-name.txt');
return [match1, match2];
}).bind(event.registration);
break;
case 'matchmissingrequest': case 'matchmissingrequest':
matchFunction = event.registration.match.bind( matchFunction = event.registration.match.bind(
event.registration, '/background-fetch/resources/missing.txt'); event.registration, '/background-fetch/resources/missing.txt');
...@@ -38,7 +46,7 @@ function handleBackgroundFetchEvent(event) { ...@@ -38,7 +46,7 @@ function handleBackgroundFetchEvent(event) {
}) })
// Extract responses. // Extract responses.
.then(records => .then(records =>
Promise.all(records.map(record => getFetchResult(record)))) Promise.all(records.map(record => getFetchResult(record))))
// Clone registration and send message. // Clone registration and send message.
.then(results => { .then(results => {
const registrationCopy = cloneRegistration(event.registration); const registrationCopy = cloneRegistration(event.registration);
......
...@@ -3,21 +3,56 @@ ...@@ -3,21 +3,56 @@
// found in the LICENSE file. // found in the LICENSE file.
#include "third_party/blink/renderer/modules/background_fetch/background_fetch_record.h" #include "third_party/blink/renderer/modules/background_fetch/background_fetch_record.h"
#include "third_party/blink/renderer/core/fetch/request.h" #include "third_party/blink/renderer/core/fetch/request.h"
#include "third_party/blink/renderer/core/fetch/response.h" #include "third_party/blink/renderer/core/fetch/response.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
namespace blink { namespace blink {
BackgroundFetchRecord::BackgroundFetchRecord(Request* request, BackgroundFetchRecord::BackgroundFetchRecord(Request* request,
Response* response, ScriptState* script_state)
bool aborted) : request_(request), script_state_(script_state) {
: request_(request), response_(response), aborted_(aborted) {
DCHECK(request_); DCHECK(request_);
DCHECK(script_state_);
} }
BackgroundFetchRecord::~BackgroundFetchRecord() = default; BackgroundFetchRecord::~BackgroundFetchRecord() = default;
void BackgroundFetchRecord::ResolveResponseReadyProperty() {
if (!response_ready_property_ ||
response_ready_property_->GetState() !=
ScriptPromisePropertyBase::State::kPending) {
return;
}
switch (record_state_) {
case State::kPending:
return;
case State::kAborted:
response_ready_property_->Reject(DOMException::Create(
DOMExceptionCode::kAbortError,
"The fetch was aborted before the record was processed."));
return;
case State::kSettled:
if (response_) {
response_ready_property_->Resolve(response_);
return;
}
if (!script_state_->ContextIsValid())
return;
// TODO(crbug.com/875201):Per https://wicg.github.io/background-fetch/
// #background-fetch-response-exposed, this should be resolved with a
// TypeError. Figure out a way to do so.
// Rejecting this with a TypeError here doesn't work because the
// RejectedType is a DOMException. Update this with the correct error
// once confirmed, or change the RejectedType.
response_ready_property_->Reject(DOMException::Create(
DOMExceptionCode::kUnknownError, "The response is not available."));
}
}
ScriptPromise BackgroundFetchRecord::responseReady(ScriptState* script_state) { ScriptPromise BackgroundFetchRecord::responseReady(ScriptState* script_state) {
if (!response_ready_property_) { if (!response_ready_property_) {
response_ready_property_ = response_ready_property_ =
...@@ -25,34 +60,47 @@ ScriptPromise BackgroundFetchRecord::responseReady(ScriptState* script_state) { ...@@ -25,34 +60,47 @@ ScriptPromise BackgroundFetchRecord::responseReady(ScriptState* script_state) {
ResponseReadyProperty::kResponseReady); ResponseReadyProperty::kResponseReady);
} }
if (response_) { ResolveResponseReadyProperty();
DCHECK(response_);
response_ready_property_->Resolve(response_);
return response_ready_property_->Promise(script_state->World());
}
if (aborted_) {
return ScriptPromise::RejectWithDOMException(
script_state,
DOMException::Create(
DOMExceptionCode::kAbortError,
"The fetch was aborted before the record was processed."));
}
return ScriptPromise::Reject( return response_ready_property_->Promise(script_state->World());
script_state,
V8ThrowException::CreateTypeError(script_state->GetIsolate(),
"The response is not available."));
} }
Request* BackgroundFetchRecord::request() const { Request* BackgroundFetchRecord::request() const {
return request_; return request_;
} }
void BackgroundFetchRecord::UpdateState(
BackgroundFetchRecord::State updated_state) {
DCHECK_EQ(record_state_, State::kPending);
record_state_ = updated_state;
ResolveResponseReadyProperty();
}
void BackgroundFetchRecord::SetResponse(
mojom::blink::FetchAPIResponsePtr& response) {
DCHECK(record_state_ == State::kPending);
if (!response_) {
if (!script_state_->ContextIsValid())
return;
response_ = Response::Create(script_state_, *response);
}
DCHECK(response_);
ResolveResponseReadyProperty();
}
bool BackgroundFetchRecord::IsRecordPending() {
return record_state_ == State::kPending;
}
void BackgroundFetchRecord::Trace(blink::Visitor* visitor) { void BackgroundFetchRecord::Trace(blink::Visitor* visitor) {
visitor->Trace(request_); visitor->Trace(request_);
visitor->Trace(response_); visitor->Trace(response_);
visitor->Trace(response_ready_property_); visitor->Trace(response_ready_property_);
visitor->Trace(script_state_);
ScriptWrappable::Trace(visitor); ScriptWrappable::Trace(visitor);
} }
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_BACKGROUND_FETCH_BACKGROUND_FETCH_RECORD_H_ #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_BACKGROUND_FETCH_BACKGROUND_FETCH_RECORD_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_BACKGROUND_FETCH_BACKGROUND_FETCH_RECORD_H_ #define THIRD_PARTY_BLINK_RENDERER_MODULES_BACKGROUND_FETCH_BACKGROUND_FETCH_RECORD_H_
#include "third_party/blink/public/platform/modules/background_fetch/background_fetch.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h" #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_property.h" #include "third_party/blink/renderer/bindings/core/v8/script_promise_property.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h" #include "third_party/blink/renderer/core/dom/dom_exception.h"
...@@ -16,17 +17,35 @@ namespace blink { ...@@ -16,17 +17,35 @@ namespace blink {
class Request; class Request;
class Response; class Response;
class ScriptState;
class MODULES_EXPORT BackgroundFetchRecord final : public ScriptWrappable { class MODULES_EXPORT BackgroundFetchRecord final : public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO(); DEFINE_WRAPPERTYPEINFO();
public: public:
BackgroundFetchRecord(Request* request, Response* response, bool aborted); // The record can be in one of these states. |kSettled| can mean success or
// failure based on whether or not |response_| is a nullptr.
enum class State {
kPending,
kAborted,
kSettled,
};
BackgroundFetchRecord(Request* request, ScriptState* script_state);
~BackgroundFetchRecord() override; ~BackgroundFetchRecord() override;
Request* request() const; Request* request() const;
ScriptPromise responseReady(ScriptState* script_state); ScriptPromise responseReady(ScriptState* script_state);
// Updates |record_state_| from kPending to kAborted or kSettled. Must be
// called when |record_state_| is kPending.
void UpdateState(State updated_state);
// Sets |response_| to response. Must be called when |record_state_| is
// kPending.
void SetResponse(mojom::blink::FetchAPIResponsePtr& response);
bool IsRecordPending();
void Trace(blink::Visitor* visitor) override; void Trace(blink::Visitor* visitor) override;
private: private:
...@@ -34,12 +53,23 @@ class MODULES_EXPORT BackgroundFetchRecord final : public ScriptWrappable { ...@@ -34,12 +53,23 @@ class MODULES_EXPORT BackgroundFetchRecord final : public ScriptWrappable {
ScriptPromiseProperty<Member<BackgroundFetchRecord>, ScriptPromiseProperty<Member<BackgroundFetchRecord>,
Member<Response>, Member<Response>,
Member<DOMException>>; Member<DOMException>>;
// Resolves a pending |response_read_property_| with |response_|, if it's not
// null.
// If |response_| is null, we do nothing if the record isn't final yet. If
// |record_final_| is true in this case, we reject the promise.
// This is because the record will not be updated with a valid |response_|.
void ResolveResponseReadyProperty();
Member<Request> request_; Member<Request> request_;
Member<Response> response_; Member<Response> response_;
Member<ResponseReadyProperty> response_ready_property_; Member<ResponseReadyProperty> response_ready_property_;
// Whether this record belongs to a fetch that was aborted. // Since BackgroundFetchRecord can only be accessed from the world that
bool aborted_; // created it, there's no danger of ScriptState leaking across worlds.
Member<ScriptState> script_state_;
State record_state_ = State::kPending;
}; };
} // namespace blink } // namespace blink
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "third_party/blink/renderer/modules/manifest/image_resource.h" #include "third_party/blink/renderer/modules/manifest/image_resource.h"
#include "third_party/blink/renderer/modules/service_worker/service_worker_registration.h" #include "third_party/blink/renderer/modules/service_worker/service_worker_registration.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h" #include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/heap/heap_allocator.h"
namespace blink { namespace blink {
...@@ -87,6 +88,8 @@ void BackgroundFetchRegistration::OnProgress( ...@@ -87,6 +88,8 @@ void BackgroundFetchRegistration::OnProgress(
result_ = result; result_ = result;
failure_reason_ = failure_reason; failure_reason_ = failure_reason;
// TODO(crbug.com/875201): Update records in |records_|.
ExecutionContext* context = GetExecutionContext(); ExecutionContext* context = GetExecutionContext();
if (!context || context->IsContextDestroyed()) if (!context || context->IsContextDestroyed())
return; return;
...@@ -232,25 +235,30 @@ void BackgroundFetchRegistration::DidGetMatchingRequests( ...@@ -232,25 +235,30 @@ void BackgroundFetchRegistration::DidGetMatchingRequests(
ScriptPromiseResolver* resolver, ScriptPromiseResolver* resolver,
bool return_all, bool return_all,
Vector<mojom::blink::BackgroundFetchSettledFetchPtr> settled_fetches) { Vector<mojom::blink::BackgroundFetchSettledFetchPtr> settled_fetches) {
DCHECK(resolver);
ScriptState* script_state = resolver->GetScriptState(); ScriptState* script_state = resolver->GetScriptState();
// Do not remove this, |scope| is needed for calling ToV8() // Do not remove this, |scope| is needed for calling ToV8()
ScriptState::Scope scope(script_state); ScriptState::Scope scope(script_state);
HeapVector<Member<BackgroundFetchRecord>> to_return; HeapVector<Member<BackgroundFetchRecord>> to_return;
to_return.ReserveInitialCapacity(settled_fetches.size()); to_return.ReserveInitialCapacity(settled_fetches.size());
for (const auto& fetch : settled_fetches) { for (auto& fetch : settled_fetches) {
Request* request = Request::Create(script_state, *(fetch->request)); // If there isn't a record for this fetch in records_ already, create one.
auto iter = records_.find(fetch->request->url);
Response* response = fetch->response BackgroundFetchRecord* record = nullptr;
? Response::Create(script_state, *fetch->response) if (iter == records_.end()) {
: nullptr; Request* request = Request::Create(script_state, *(fetch->request));
auto* new_record = new BackgroundFetchRecord(request, script_state);
bool aborted = DCHECK(new_record);
failure_reason_ == records_.Set(request->url(), new_record);
mojom::BackgroundFetchFailureReason::CANCELLED_FROM_UI ||
failure_reason_ == record = new_record;
mojom::BackgroundFetchFailureReason::CANCELLED_BY_DEVELOPER; } else {
record = iter->value;
}
to_return.push_back(new BackgroundFetchRecord(request, response, aborted)); UpdateRecord(record, fetch->response);
to_return.push_back(record);
} }
if (!return_all) { if (!return_all) {
...@@ -267,6 +275,38 @@ void BackgroundFetchRegistration::DidGetMatchingRequests( ...@@ -267,6 +275,38 @@ void BackgroundFetchRegistration::DidGetMatchingRequests(
resolver->Resolve(to_return); resolver->Resolve(to_return);
} }
void BackgroundFetchRegistration::UpdateRecord(
BackgroundFetchRecord* record,
mojom::blink::FetchAPIResponsePtr& response) {
DCHECK(record);
if (!record->IsRecordPending())
return;
// Per the spec, resolve with a valid response, if there is one available,
// even if the fetch has been aborted.
if (response) {
record->SetResponse(response);
record->UpdateState(BackgroundFetchRecord::State::kSettled);
return;
}
if (IsAborted()) {
record->UpdateState(BackgroundFetchRecord::State::kAborted);
return;
}
if (result_ != mojom::blink::BackgroundFetchResult::UNSET)
record->UpdateState(BackgroundFetchRecord::State::kSettled);
}
bool BackgroundFetchRegistration::IsAborted() {
return failure_reason_ ==
mojom::BackgroundFetchFailureReason::CANCELLED_FROM_UI ||
failure_reason_ ==
mojom::BackgroundFetchFailureReason::CANCELLED_BY_DEVELOPER;
}
void BackgroundFetchRegistration::DidAbort( void BackgroundFetchRegistration::DidAbort(
ScriptPromiseResolver* resolver, ScriptPromiseResolver* resolver,
mojom::blink::BackgroundFetchError error) { mojom::blink::BackgroundFetchError error) {
...@@ -333,6 +373,7 @@ void BackgroundFetchRegistration::Dispose() { ...@@ -333,6 +373,7 @@ void BackgroundFetchRegistration::Dispose() {
void BackgroundFetchRegistration::Trace(Visitor* visitor) { void BackgroundFetchRegistration::Trace(Visitor* visitor) {
visitor->Trace(registration_); visitor->Trace(registration_);
visitor->Trace(records_);
EventTargetWithInlineData::Trace(visitor); EventTargetWithInlineData::Trace(visitor);
} }
......
...@@ -12,10 +12,12 @@ ...@@ -12,10 +12,12 @@
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h" #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h" #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/heap/handle.h" #include "third_party/blink/renderer/platform/heap/handle.h"
#include "third_party/blink/renderer/platform/weborigin/kurl_hash.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace blink { namespace blink {
class BackgroundFetchRecord;
class CacheQueryOptions; class CacheQueryOptions;
class ScriptPromiseResolver; class ScriptPromiseResolver;
class ScriptState; class ScriptState;
...@@ -111,8 +113,18 @@ class BackgroundFetchRegistration final ...@@ -111,8 +113,18 @@ class BackgroundFetchRegistration final
bool return_all, bool return_all,
Vector<mojom::blink::BackgroundFetchSettledFetchPtr> settled_fetches); Vector<mojom::blink::BackgroundFetchSettledFetchPtr> settled_fetches);
// Updates the |record| with a |response|, if one is available, else marks
// the |record|'s request as aborted or failed.
void UpdateRecord(BackgroundFetchRecord* record,
mojom::blink::FetchAPIResponsePtr& response);
bool IsAborted();
Member<ServiceWorkerRegistration> registration_; Member<ServiceWorkerRegistration> registration_;
// TODO(crbug.com/774054): Update the key once we support duplicate requests.
HeapHashMap<KURL, Member<BackgroundFetchRecord>> records_;
// Corresponds to IDL 'id' attribute. Not unique - an active registration can // Corresponds to IDL 'id' attribute. Not unique - an active registration can
// have the same |developer_id_| as one or more inactive registrations. // have the same |developer_id_| as one or more inactive registrations.
String developer_id_; String developer_id_;
......
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