| Index: third_party/WebKit/Source/modules/serviceworkers/WaitUntilObserver.cpp
|
| diff --git a/third_party/WebKit/Source/modules/serviceworkers/WaitUntilObserver.cpp b/third_party/WebKit/Source/modules/serviceworkers/WaitUntilObserver.cpp
|
| index d8056ae69e40194bdf9fdb55b9627aaf8768753c..264e4b9991265a2a69ee5d05aafb20856595c118 100644
|
| --- a/third_party/WebKit/Source/modules/serviceworkers/WaitUntilObserver.cpp
|
| +++ b/third_party/WebKit/Source/modules/serviceworkers/WaitUntilObserver.cpp
|
| @@ -12,6 +12,7 @@
|
| #include "core/dom/ExecutionContext.h"
|
| #include "modules/serviceworkers/ServiceWorkerGlobalScope.h"
|
| #include "platform/LayoutTestSupport.h"
|
| +#include "platform/bindings/Microtask.h"
|
| #include "platform/wtf/Assertions.h"
|
| #include "public/platform/Platform.h"
|
| #include "public/platform/modules/serviceworker/WebServiceWorkerEventResult.h"
|
| @@ -41,10 +42,13 @@ class WaitUntilObserver::ThenFunction final : public ScriptFunction {
|
| kRejected,
|
| };
|
|
|
| - static v8::Local<v8::Function> CreateFunction(ScriptState* script_state,
|
| - WaitUntilObserver* observer,
|
| - ResolveType type) {
|
| - ThenFunction* self = new ThenFunction(script_state, observer, type);
|
| + static v8::Local<v8::Function> CreateFunction(
|
| + ScriptState* script_state,
|
| + WaitUntilObserver* observer,
|
| + ResolveType type,
|
| + std::unique_ptr<PromiseSettledCallback> callback) {
|
| + ThenFunction* self =
|
| + new ThenFunction(script_state, observer, type, std::move(callback));
|
| return self->BindToV8Function();
|
| }
|
|
|
| @@ -56,20 +60,40 @@ class WaitUntilObserver::ThenFunction final : public ScriptFunction {
|
| private:
|
| ThenFunction(ScriptState* script_state,
|
| WaitUntilObserver* observer,
|
| - ResolveType type)
|
| + ResolveType type,
|
| + std::unique_ptr<PromiseSettledCallback> callback)
|
| : ScriptFunction(script_state),
|
| observer_(observer),
|
| - resolve_type_(type) {}
|
| + resolve_type_(type),
|
| + callback_(std::move(callback)) {}
|
|
|
| ScriptValue Call(ScriptValue value) override {
|
| DCHECK(observer_);
|
| DCHECK(resolve_type_ == kFulfilled || resolve_type_ == kRejected);
|
| + if (callback_)
|
| + (*callback_)(value);
|
| + // According from step 4 of ExtendableEvent::waitUntil() in spec:
|
| + // https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil
|
| + // "Upon fulfillment or rejection of f, queue a microtask to run these
|
| + // substeps: Decrement the pending promises count by one."
|
| +
|
| + // At this time point the microtask A running resolve/reject function of
|
| + // this promise has already been queued, in order to allow microtask A to
|
| + // call waitUntil, we enqueue another microtask B to delay the promise
|
| + // settled notification to |observer_|, thus A will run before B so A can
|
| + // call waitUntil well, but any other microtask C possibly enqueued by A
|
| + // will run after B so C maybe can't call waitUntil if there has no any
|
| + // extend lifetime promise at that time.
|
| if (resolve_type_ == kRejected) {
|
| - observer_->OnPromiseRejected();
|
| + Microtask::EnqueueMicrotask(
|
| + WTF::Bind(&WaitUntilObserver::OnPromiseRejected,
|
| + WrapPersistent(observer_.Get())));
|
| value =
|
| ScriptPromise::Reject(value.GetScriptState(), value).GetScriptValue();
|
| } else {
|
| - observer_->OnPromiseFulfilled();
|
| + Microtask::EnqueueMicrotask(
|
| + WTF::Bind(&WaitUntilObserver::OnPromiseFulfilled,
|
| + WrapPersistent(observer_.Get())));
|
| }
|
| observer_ = nullptr;
|
| return value;
|
| @@ -77,6 +101,7 @@ class WaitUntilObserver::ThenFunction final : public ScriptFunction {
|
|
|
| Member<WaitUntilObserver> observer_;
|
| ResolveType resolve_type_;
|
| + std::unique_ptr<PromiseSettledCallback> callback_;
|
| };
|
|
|
| WaitUntilObserver* WaitUntilObserver::Create(ExecutionContext* context,
|
| @@ -90,28 +115,56 @@ void WaitUntilObserver::WillDispatchEvent() {
|
| // When handling a notificationclick or paymentrequest event, we want to
|
| // allow one window to be focused or opened. These calls are allowed between
|
| // the call to willDispatchEvent() and the last call to
|
| - // decrementPendingActivity(). If waitUntil() isn't called, that means
|
| + // DecrementPendingPromiseCount(). If waitUntil() isn't called, that means
|
| // between willDispatchEvent() and didDispatchEvent().
|
| if (type_ == kNotificationClick || type_ == kPaymentRequest)
|
| execution_context_->AllowWindowInteraction();
|
|
|
| - IncrementPendingActivity();
|
| + DCHECK_EQ(EventDispatchState::kInitial, event_dispatch_state_);
|
| + event_dispatch_state_ = EventDispatchState::kDispatching;
|
| }
|
|
|
| void WaitUntilObserver::DidDispatchEvent(bool event_dispatch_failed) {
|
| event_dispatch_state_ = event_dispatch_failed
|
| ? EventDispatchState::kFailed
|
| - : EventDispatchState::kCompleted;
|
| - DecrementPendingActivity();
|
| + : EventDispatchState::kDispatched;
|
| + MaybeCompleteEvent();
|
| }
|
|
|
| -void WaitUntilObserver::WaitUntil(ScriptState* script_state,
|
| - ScriptPromise script_promise,
|
| - ExceptionState& exception_state) {
|
| - if (event_dispatch_state_ != EventDispatchState::kInitial) {
|
| - exception_state.ThrowDOMException(kInvalidStateError,
|
| - "The event handler is already finished.");
|
| - return;
|
| +void WaitUntilObserver::WaitUntil(
|
| + ScriptState* script_state,
|
| + ScriptPromise script_promise,
|
| + ExceptionState& exception_state,
|
| + std::unique_ptr<PromiseSettledCallback> on_promise_fulfilled,
|
| + std::unique_ptr<PromiseSettledCallback> on_promise_rejected) {
|
| + if (pending_promises_ == 0) {
|
| + switch (event_dispatch_state_) {
|
| + case EventDispatchState::kInitial:
|
| + NOTREACHED();
|
| + return;
|
| + case EventDispatchState::kDispatching:
|
| + if (!v8::MicrotasksScope::IsRunningMicrotasks(
|
| + script_state->GetIsolate())) {
|
| + break;
|
| + }
|
| + // Fall through:
|
| + // didDispatchEvent() is called after both the event handler
|
| + // execution finished and microtasks queued by the event handler execution
|
| + // finished, it's hard to get the precise time point between the 2
|
| + // execution phases.
|
| + // So even in EventDispatchState::kDispatching state at this time point,
|
| + // running microtask indicates that event handler execution has actually
|
| + // finished, in such case if there aren't any outstanding extend lifetime
|
| + // promises, we should throw here.
|
| + case EventDispatchState::kDispatched:
|
| + case EventDispatchState::kFailed:
|
| + exception_state.ThrowDOMException(
|
| + kInvalidStateError,
|
| + "The event handler is already finished "
|
| + "and no extend lifetime promises are "
|
| + "outstanding.");
|
| + return;
|
| + }
|
| }
|
|
|
| if (!execution_context_)
|
| @@ -126,11 +179,12 @@ void WaitUntilObserver::WaitUntil(ScriptState* script_state,
|
| consume_window_interaction_timer_.StartOneShot(WindowInteractionTimeout(),
|
| BLINK_FROM_HERE);
|
|
|
| - IncrementPendingActivity();
|
| - script_promise.Then(ThenFunction::CreateFunction(script_state, this,
|
| - ThenFunction::kFulfilled),
|
| - ThenFunction::CreateFunction(script_state, this,
|
| - ThenFunction::kRejected));
|
| + IncrementPendingPromiseCount();
|
| + script_promise.Then(
|
| + ThenFunction::CreateFunction(script_state, this, ThenFunction::kFulfilled,
|
| + std::move(on_promise_fulfilled)),
|
| + ThenFunction::CreateFunction(script_state, this, ThenFunction::kRejected,
|
| + std::move(on_promise_rejected)));
|
| }
|
|
|
| WaitUntilObserver::WaitUntilObserver(ExecutionContext* context,
|
| @@ -145,25 +199,47 @@ WaitUntilObserver::WaitUntilObserver(ExecutionContext* context,
|
| &WaitUntilObserver::ConsumeWindowInteraction) {}
|
|
|
| void WaitUntilObserver::OnPromiseFulfilled() {
|
| - DecrementPendingActivity();
|
| + DecrementPendingPromiseCount();
|
| }
|
|
|
| void WaitUntilObserver::OnPromiseRejected() {
|
| has_rejected_promise_ = true;
|
| - DecrementPendingActivity();
|
| + DecrementPendingPromiseCount();
|
| +}
|
| +
|
| +void WaitUntilObserver::IncrementPendingPromiseCount() {
|
| + ++pending_promises_;
|
| }
|
|
|
| -void WaitUntilObserver::IncrementPendingActivity() {
|
| - ++pending_activity_;
|
| +void WaitUntilObserver::DecrementPendingPromiseCount() {
|
| + DCHECK_GT(pending_promises_, 0);
|
| + --pending_promises_;
|
| + MaybeCompleteEvent();
|
| }
|
|
|
| -void WaitUntilObserver::DecrementPendingActivity() {
|
| - DCHECK_GT(pending_activity_, 0);
|
| - if (!execution_context_ ||
|
| - (event_dispatch_state_ != EventDispatchState::kFailed &&
|
| - --pending_activity_))
|
| +void WaitUntilObserver::MaybeCompleteEvent() {
|
| + if (!execution_context_)
|
| return;
|
|
|
| + switch (event_dispatch_state_) {
|
| + case EventDispatchState::kInitial:
|
| + NOTREACHED();
|
| + return;
|
| + case EventDispatchState::kDispatching:
|
| + // Still dispatching, do not complete the event.
|
| + return;
|
| + case EventDispatchState::kDispatched:
|
| + // Still waiting for a promise, do not complete the event.
|
| + if (pending_promises_ != 0)
|
| + return;
|
| + // Dispatch finished and there are no pending promises, complete the
|
| + // event.
|
| + break;
|
| + case EventDispatchState::kFailed:
|
| + // Dispatch had some error, complete the event immediatelly.
|
| + break;
|
| + }
|
| +
|
| ServiceWorkerGlobalScopeClient* client =
|
| ServiceWorkerGlobalScopeClient::From(execution_context_);
|
| WebServiceWorkerEventResult result =
|
|
|