Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

async waitUntil microtask issue #1213

Closed
wanderview opened this issue Oct 20, 2017 · 35 comments

Comments

Projects
None yet
9 participants
@wanderview
Copy link
Member

commented Oct 20, 2017

As you may be aware the firefox Promise implementation has not been using proper microtasks and we're trying to fix that. Part of that effort uncovered an issue with a test for async waitUntil():

https://bugzilla.mozilla.org/show_bug.cgi?id=1409979

Apparently the spec is correct, but the test follows an incorrect interpretation based on our bogus microstask handling. Chrome matched the test expectation by doing this workaround:

https://cs.chromium.org/chromium/src/third_party/WebKit/Source/modules/serviceworkers/WaitUntilObserver.cpp?l=145&rcl=c2665c0a9bfec6379c90d3ea36199ee6a3e9f024

We would like to correct the test to follow the spec and real microtask definition, but wanted to make sure everyone was ok with that since chrome matches behavior.

We think this example from the original issue is incorrect: #1039 (comment)

And this WPT test condition should return OK and not throw an InvalidStateError:

https://github.com/w3c/web-platform-tests/blob/a16738c524a65946b0ff7df6be37ae076ff5e860/service-workers/service-worker/resources/extendable-event-async-waituntil.js#L20

@mattto

This comment has been minimized.

Copy link
Member

commented Oct 20, 2017

I'm OK with changing the test as I suspect you're right about the spec. Just not sure why we got it wrong in the first place. Did we all just read the spec wrong when discussing #1039 ? It's very hard for me to read the Service Worker spec and DOM spec and piece together what the expected behavior is.. easier to understand via WPT tests and examples.

@aliams

This comment has been minimized.

Copy link
Contributor

commented Oct 20, 2017

I also agree with changing the test. Thanks for spotting this @wanderview!

@wanderview

This comment has been minimized.

Copy link
Member Author

commented Oct 20, 2017

Actually, now I'm confused again. I would think step 5 here would mean we throw since we should queue the microtask to decrement the number of pending promises:

https://w3c.github.io/ServiceWorker/#wait-until-method

@wanderview

This comment has been minimized.

Copy link
Member Author

commented Oct 20, 2017

Olli suggests that the test should be saying "OK" there because the dispatch flag is set, I think:

https://bugzilla.mozilla.org/show_bug.cgi?id=1409979#c4

Because we are running the test from an event handler.

Would we throw InvalidStateError if we tried to do it from a setTimeout()?

I'm not sure I understand why the dispatch flag matters here.

@smaug----

This comment has been minimized.

Copy link

commented Oct 20, 2017

Yes, setTimeout creates its own task, so the event isn't being dispatched during that time.

@wanderview

This comment has been minimized.

Copy link
Member Author

commented Oct 20, 2017

I think the intent of checking the dispatch flag is to allow the initial waitUntil() to be called on first dispatch of the functional event.

@wanderview

This comment has been minimized.

Copy link
Member Author

commented Oct 20, 2017

I guess the setTimeout() case is already tested here:

https://github.com/w3c/web-platform-tests/blob/a16738c524a65946b0ff7df6be37ae076ff5e860/service-workers/service-worker/resources/extendable-event-async-waituntil.js#L17

So I guess we can just mark this test case as returning OK:

https://github.com/w3c/web-platform-tests/blob/a16738c524a65946b0ff7df6be37ae076ff5e860/service-workers/service-worker/resources/extendable-event-async-waituntil.js#L20

Maybe with a comment that its ok since the dispatching flag is set.

If we really wanted to we could add a 3rd test case that adds a setTimeout() before the async_microtask_waituntil to show that it should fail if the dispatch flag is not set.

@mattto

This comment has been minimized.

Copy link
Member

commented Oct 20, 2017

Just a question: is the dispatch flag set whenever you are in a microtask "chained" to the original event dispatch? e.g., does this throw?

onfetch = e => {
  Promise.resolve().then(_ => Promise.resolve()).then(_ => Promise.resolve()).then(_ => e.waitUntil(p));
};

Similarly how about this one:

onfetch = e => {
  e.waitUntil(some_promise);
  some_promise.then(_ => Promise.resolve()).then(_ => Promise.resolve()).then(_ => e.waitUntil(another_promise));
};
@smaug----

This comment has been minimized.

Copy link

commented Oct 21, 2017

If more microtask stuff is added during a microtask checkpoint, they all are handled during that checkpoint. Microtasks are synchronous from browser point of view.
See the while loop in https://html.spec.whatwg.org/#perform-a-microtask-checkpoint

@jungkees

This comment has been minimized.

Copy link
Collaborator

commented Oct 23, 2017

So I guess we can just mark this test case as returning OK:

https://github.com/w3c/web-platform-tests/blob/a16738c524a65946b0ff7df6be37ae076ff5e860/service-workers/service-worker/resources/extendable-event-async-waituntil.js#L20

I think the current test is what we settled on. I.e., it should throw in that case.

We think this example from the original issue is incorrect: #1039 (comment)

I think #1039 (comment) is the expected behavior we agreed. That is, we wanted to disallow a further lifetime extension in any async task when there's no holding extension promise.

Olli suggests that the test should be saying "OK" there because the dispatch flag is set, I think:

I don't think the dispatch flag is set when the waitUntil is called in the microtask. The microtask checkpoint is performed after the event-dispatcher task is finished.

@smaug----

This comment has been minimized.

Copy link

commented Oct 23, 2017

The microtask checkpoint is performed after the event-dispatcher task is finished.

No it isn't, except if there is some other JS on stack too (like, if the event is dispatched from JS).
Microtask checkpoint happens at the end of the outermost script execution and at the end of a task
(+ couple of special cases, like right before <script> execution)

@jungkees

This comment has been minimized.

Copy link
Collaborator

commented Oct 23, 2017

I thought if dispatch is invoked in a task, any microtasks queued in the task wouldn't be scheduled until the dispatch steps run to completion. Am I understanding it incorrectly?

@smaug----

This comment has been minimized.

Copy link

commented Oct 23, 2017

If there isn't any other script on stack, microtask checkpoint happens at the end of the each event listener call. So, if there are several listeners for an event, there will be several microtask checkpoints per event dispatch.

@jungkees

This comment has been minimized.

Copy link
Collaborator

commented Oct 23, 2017

I think those microtask checkpoints are preceded by the completion of the dispatch algorithm (where the dispatch flag's unset) regardless how many other tasks would be queued in the interim.

@bevis-tseng

This comment has been minimized.

Copy link

commented Oct 24, 2017

No, the dispatch flag is unset after microtask checkpoints of event listeners are performed, according to https://dom.spec.whatwg.org/#dispatching-events:

  1. the dispatch flag is set at step 1 and is unset and step 14.
  2. the listener callbacks could be invoked in step 12.4 and step 13.6.
  3. in https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke, each callback invocation (step 16.2 of [1]) contains a microtask checkpoint specified in [2].
    [1] https://heycam.github.io/webidl/#call-a-user-objects-operation
    [2] https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script
@jungkees

This comment has been minimized.

Copy link
Collaborator

commented Oct 24, 2017

  1. in https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke, each callback invocation (step 16.2 of [1]) contains a microtask checkpoint specified in [2].

I didn't realize the execution of each listener callback consumes the microtasks. That considered, I agree the WPT test (calling waitUntil() in a microtask) returns OK and doesn't throw an InvalidStateError. Thanks for the pointers. Sorry for my confusion.

@jungkees

This comment has been minimized.

Copy link
Collaborator

commented Oct 27, 2017

Reviewing the other cases again, I'm confused about the current-extension-expired-same-microtask-turn-extra case. Shouldn't it return OK because all the microtasks in the example will be consumed before the dispatch flag is unset?

@wanderview

This comment has been minimized.

Copy link
Member Author

commented Oct 27, 2017

Reviewing the other cases again, I'm confused about the current-extension-expired-same-microtask-turn-extra case. Shouldn't it return OK because all the microtasks in the example will be consumed before the dispatch flag is unset?

Per the spec language I think you are right.

Looking at why our implementationdoesn't do that, we end up removing the "keep alive token" via a microtask:

https://searchfox.org/mozilla-central/source/dom/workers/ServiceWorkerPrivate.cpp#386
https://searchfox.org/mozilla-central/source/dom/workers/ServiceWorkerPrivate.cpp#356

This then means when the next WaitUntil() is called in a later microtask in the same dispatching task the "keep alive token" is gone.

I was thinking maybe we could make it respect the dispatch flag here, but the "keep alive token" is created on another thread. It would be very difficult to do this in firefox.

What would people think about adding another flag that says "extension promises were added and now all consumed"? If that flag is set, then we don't allow new extension promises to be added even if we are still dispatching the event. This might meet our original intent here?

@jakearchibald

This comment has been minimized.

Copy link
Contributor

commented Nov 2, 2017

I think our spec wording is a little wrong here.

Upon fulfillment or rejection of f, queue a microtask to run these substeps:

Fulfillment already queues a microtask, so we may be doing something redundant. Unless we're intentionally adding a microtask to a microtask, which I don't think we are.

I'm struggling to get my head around this. I don't think this should work:

const p = doSomething();
event.waitUntil(p);
p.then(() => event.waitUntil(doSomethingElse()));

Because the pending promises count will be 0 when event.waitUntil is called. This is because the spec's microtask is in the queue before the p.then handler. Whereas:

const p = doSomething();
p.then(() => event.waitUntil(doSomethingElse()));
event.waitUntil(p);

…would work, since the p.then handler is in the queue before the spec's microtask.

I guess it's a bit of a gotcha, but it'll fail consistently.

@jakearchibald

This comment has been minimized.

Copy link
Contributor

commented Nov 7, 2017

F2F: My comment above is wrong. The double microtask is intentional. The use of the dispatch flag is intentional and catches an edge case with multiple event listeners:

addEventListener('fetch', event => {
  event.waitUntil(Promise.resolve());
});

addEventListener('fetch', event => {
  // This should work, and it's the dispatch flag that makes it work.
  event.waitUntil(whatever());
});

Action: Review the tests for this.

https://github.com/w3c/web-platform-tests/blob/a16738c524a65946b0ff7df6be37ae076ff5e860/service-workers/service-worker/resources/extendable-event-async-waituntil.js#L20 - waitUntil shouldn't throw here.

@jungkees

This comment has been minimized.

Copy link
Collaborator

commented Jan 17, 2018

Thanks for clarifying with the tests, @aliams. I think we mostly agree on the current spec behavior for waitUntil(), but would like to clarify some points:

I was thinking maybe we could make it respect the dispatch flag here, but the "keep alive token" is created on another thread. It would be very difficult to do this in firefox.

@wanderview, do you still have this issue or is it implementable? I thought of using event's extend lifetime promises for checking "extension promises were added" condition in your comment, but we still don't solve #1213 (comment) case without the dispatch flag.

Having thought on it, (throwing when the dispatch flag is unset && count == 0) explains the desired behavior well I think. That is, the event is basically extendable during its own task turn and during when the extension opportunity isn't closed.

Other than that, I'm a bit uncomfortable with the following cases. What do you think about them?

const p = async_task_waituntil();
event.waitUntil(p);
p.then(() => { Promise.resolve().then(() => event.waitUntil(q)) }); // Throw.

This throws since the count decrement microtask queued by waitUntil step 5 will run before the microtask queued by p.then callback.

const p = async_task_waituntil();
p.then(() => { Promise.resolve().then(() => event.waitUntil(q)) }); // OK.
event.waitUntil(p);

This runs OK since the count decrement microtask queued by waitUntil step 5 will run after the microtask queued by p.then callback.

@aliams

This comment has been minimized.

Copy link
Contributor

commented Jan 19, 2018

Is that to say that the test calling waitUntil in a different microtask without an existing extension should still throw. And that the test calling waitUntil at the end of the microtask turn still throws too? I believe due to the dispatch flag, these should actually be succeeding.

@aliams

This comment has been minimized.

Copy link
Contributor

commented Jan 19, 2018

@jungkees, are these what the tests would look like for what you are describing in your examples?

  1.  waitPromise = async_task_waituntil(event);
     event.waitUntil(waitPromise);
     waitPromise.then(() => {
         return Promise.resolve().then(() => sync_waituntil(event))
     }).then(reportResultExpecting('InvalidStateError'));
  2.  waitPromise = async_task_waituntil(event);
     waitPromise.then(() => {
         return Promise.resolve().then(() => sync_waituntil(event))
     }).then(reportResultExpecting('OK'));
     event.waitUntil(waitPromise);
@jungkees

This comment has been minimized.

Copy link
Collaborator

commented Jan 22, 2018

@aliams, yes, the tests look equivalent to the examples I gave above. I was talking about the cases where the promise (waitPromise in your example) resolves in an async task. The result of the waitUtil called in the extra microtask turn depends on whether the microtask for decrementing the pending lifetime promise count was called or not. The question was whether this would be expected behaviors?

hubot pushed a commit to WebKit/webkit that referenced this issue Aug 6, 2018

utatane.tea@gmail.com
Add support for microtasks in workers
https://bugs.webkit.org/show_bug.cgi?id=188246

Reviewed by Darin Adler.

LayoutTests/imported/w3c:

This test is wrong[1,2]. It starts failing since our worker starts using correct microtask queues.

[1]: web-platform-tests/wpt#8936
[2]: w3c/ServiceWorker#1213 (comment)

* web-platform-tests/service-workers/service-worker/extendable-event-async-waituntil.https-expected.txt:

Source/WebCore:

This patch adds the microtask mechanism to workers. To adopt the existing microtask mechanism from the main thread,
we extend JSMainThreadExecState for non-main-threads. We rename it to JSExecState, and store stacked
ExecState* data in thread local storage in ThreadGlobalData instead of a static variable s_mainThreadState.

We add MicrotaskQueue to WorkerGlobalScope since each worker has its own thread and it should have its
own microtask queue.

* Modules/encryptedmedia/legacy/LegacyCDMSessionClearKey.cpp:
* Sources.txt:
* WebCore.xcodeproj/project.pbxproj:
* bindings/js/JSBindingsAllInOne.cpp:
* bindings/js/JSCallbackData.cpp:
(WebCore::JSCallbackData::invokeCallback):
* bindings/js/JSCustomElementInterface.cpp:
(WebCore::constructCustomElementSynchronously):
(WebCore::JSCustomElementInterface::upgradeElement):
(WebCore::JSCustomElementInterface::invokeCallback):
* bindings/js/JSCustomXPathNSResolver.cpp:
(WebCore::JSCustomXPathNSResolver::lookupNamespaceURI):
* bindings/js/JSDOMGlobalObjectTask.cpp:
* bindings/js/JSDOMWindowBase.cpp:
(WebCore::JSDOMWindowBase::queueTaskToEventLoop):
Queue a microtask to MicrotaskQueue instead of posting a macrotask.

(WebCore::JSDOMWindowMicrotaskCallback::create): Deleted.
(WebCore::JSDOMWindowMicrotaskCallback::call): Deleted.
(WebCore::JSDOMWindowMicrotaskCallback::JSDOMWindowMicrotaskCallback): Deleted.
(): Deleted.
Extract JSDOMWindowMicrotaskCallback as JSMicrotaskCallback and create a new file for it.

* bindings/js/JSErrorHandler.cpp:
(WebCore::JSErrorHandler::handleEvent):
* bindings/js/JSEventListener.cpp:
(WebCore::JSEventListener::handleEvent):
* bindings/js/JSExecState.cpp: Renamed from Source/WebCore/bindings/js/JSMainThreadExecState.cpp.
(WebCore::JSExecState::didLeaveScriptContext):
If we are in a main thread, we consume main thread microtask queue. If we are in worker thread,
we consume a microtask queue per worker.

(WebCore::functionCallHandlerFromAnyThread):
(WebCore::evaluateHandlerFromAnyThread):
* bindings/js/JSExecState.h: Renamed from Source/WebCore/bindings/js/JSMainThreadExecState.h.
(WebCore::JSExecState::currentState):
(WebCore::JSExecState::call):
(WebCore::JSExecState::evaluate):
(WebCore::JSExecState::profiledCall):
(WebCore::JSExecState::profiledEvaluate):
(WebCore::JSExecState::runTask):
(WebCore::JSExecState::loadModule):
(WebCore::JSExecState::linkAndEvaluateModule):
(WebCore::JSExecState::JSExecState):
(WebCore::JSExecState::~JSExecState):
(WebCore::JSExecState::setCurrentState):
Store and load ExecState in thread local storage, ThreadGlobalData. This allows us to use it for workers.

(WebCore::JSMainThreadNullState::JSMainThreadNullState):
(WebCore::JSMainThreadNullState::~JSMainThreadNullState):
We keep this name "JSMainThreadNullState" since CustomElementReactionStack should be stick to the main thread.
And this class is only used in the main thread.

* bindings/js/JSExecStateInstrumentation.h: Renamed from Source/WebCore/bindings/js/JSMainThreadExecStateInstrumentation.h.
(WebCore::JSExecState::instrumentFunctionInternal):
(WebCore::JSExecState::instrumentFunctionCall):
(WebCore::JSExecState::instrumentFunctionConstruct):
* bindings/js/JSMicrotaskCallback.h: Copied from Source/WebKitLegacy/mac/DOM/DOMHTMLBaseElement.mm.
(WebCore::JSMicrotaskCallback::create):
(WebCore::JSMicrotaskCallback::call):
(WebCore::JSMicrotaskCallback::JSMicrotaskCallback):
* bindings/js/JSWorkerGlobalScopeBase.cpp:
(WebCore::JSWorkerGlobalScopeBase::queueTaskToEventLoop):
* bindings/js/ScheduledAction.cpp:
(WebCore::ScheduledAction::executeFunctionInContext):
* bindings/js/ScriptController.cpp:
(WebCore::ScriptController::evaluateInWorld):
(WebCore::ScriptController::loadModuleScriptInWorld):
(WebCore::ScriptController::linkAndEvaluateModuleScriptInWorld):
(WebCore::ScriptController::canAccessFromCurrentOrigin):
* bindings/js/WorkerScriptController.cpp:
(WebCore::WorkerScriptController::evaluate):
* bridge/objc/WebScriptObject.mm:
(-[WebScriptObject callWebScriptMethod:withArguments:]):
(-[WebScriptObject evaluateWebScript:]):
* html/canvas/WebGLRenderingContextBase.cpp:
(WebCore::WebGLRenderingContextBase::compileShader):
(WebCore::WebGLRenderingContextBase::printToConsole):
* inspector/InspectorCanvas.cpp:
(WebCore::InspectorCanvas::buildObjectForCanvas):
(WebCore::InspectorCanvas::buildAction):
* inspector/InspectorController.cpp:
* inspector/InspectorFrontendHost.cpp:
* inspector/TimelineRecordFactory.cpp:
(WebCore::TimelineRecordFactory::createGenericRecord):
* inspector/WorkerInspectorController.cpp:
* inspector/agents/InspectorCanvasAgent.cpp:
* inspector/agents/InspectorNetworkAgent.cpp:
(WebCore::InspectorNetworkAgent::buildInitiatorObject):
* page/DOMWindow.cpp:
(WebCore::DOMWindow::postMessage):
* page/PageConsoleClient.cpp:
(WebCore::PageConsoleClient::addMessage):
* page/csp/ContentSecurityPolicy.cpp:
(WebCore::ContentSecurityPolicy::reportViolation const):
* platform/ThreadGlobalData.h:
(WebCore::ThreadGlobalData::ThreadGlobalData::currentState const):
(WebCore::ThreadGlobalData::ThreadGlobalData::setCurrentState):
* workers/WorkerGlobalScope.cpp:
(WebCore::WorkerGlobalScope::WorkerGlobalScope):
(WebCore::WorkerGlobalScope::removeMicrotaskQueue):
* workers/WorkerGlobalScope.h:
(WebCore::WorkerGlobalScope::microtaskQueue const):
* workers/WorkerThread.cpp:
(WebCore::WorkerThread::stop):
* workers/service/ExtendableEvent.cpp:
(WebCore::ExtendableEvent::addExtendLifetimePromise):
When dispatching an "install" event from service worker, we first create an event,
dispatch it, and set a handler which is called when a pending promise count becomes zero.
However, the old code checked pending promise count in a queued microtask. It worked
previously because microtask is actually a macrotask in the service worker. So this check
is done after a handler is set. But this patch introduces real microtask, and this check
happens before a handler is set because dispatching an event can exhaust microtask queue.
According to the spec, this check should not be done in microtask[1]. We make this checking
part as a macrotask. We note that algorithm noted as FIXMEs should be done in this
microtask while the checking part should not be done.

[1]: https://w3c.github.io/ServiceWorker/#installation-algorithm

Source/WebKit:

Rename JSMainThreadExecState.h to JSExecState.h.

* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMAttr.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMBlob.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMCDATASection.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMCSSRule.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMCSSRuleList.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMCSSStyleDeclaration.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMCSSStyleSheet.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMCSSValue.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMCharacterData.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMClientRect.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMClientRectList.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMComment.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMDOMImplementation.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMDOMSelection.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMDOMTokenList.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMDOMWindow.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMDeprecated.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMDocumentFragment.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMDocumentGtk.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMDocumentType.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMElementGtk.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMEvent.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMFile.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMFileList.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLAnchorElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLAppletElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLAreaElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLBRElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLBaseElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLBodyElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLButtonElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLCanvasElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLCollection.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLDListElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLDirectoryElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLDivElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLDocument.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLEmbedElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLFieldSetElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLFontElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLFormElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLFrameElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLFrameSetElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLHRElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLHeadElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLHeadingElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLHtmlElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLIFrameElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLImageElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLInputElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLLIElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLLabelElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLLegendElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLLinkElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLMapElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLMarqueeElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLMenuElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLMetaElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLModElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLOListElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLObjectElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLOptGroupElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLOptionElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLOptionsCollection.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLParagraphElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLParamElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLPreElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLQuoteElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLScriptElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLSelectElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLStyleElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLTableCaptionElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLTableCellElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLTableColElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLTableElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLTableRowElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLTableSectionElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLTextAreaElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLTitleElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMHTMLUListElement.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMKeyboardEvent.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMMediaList.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMMouseEvent.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMNamedNodeMap.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMNodeGtk.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMNodeIterator.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMNodeList.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMProcessingInstruction.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMRange.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMStyleSheet.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMStyleSheetList.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMText.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMTreeWalker.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMUIEvent.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMWheelEvent.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMXPathExpression.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMXPathNSResolver.cpp:
* WebProcess/InjectedBundle/API/gtk/DOM/WebKitDOMXPathResult.cpp:

Source/WebKitLegacy/mac:

Rename JSMainThreadExecState.h to JSExecState.h.

* DOM/DOMAttr.mm:
* DOM/DOMBlob.mm:
* DOM/DOMCDATASection.mm:
* DOM/DOMCSSCharsetRule.mm:
* DOM/DOMCSSFontFaceRule.mm:
* DOM/DOMCSSImportRule.mm:
* DOM/DOMCSSMediaRule.mm:
* DOM/DOMCSSPageRule.mm:
* DOM/DOMCSSPrimitiveValue.mm:
* DOM/DOMCSSRule.mm:
* DOM/DOMCSSRuleList.mm:
* DOM/DOMCSSStyleDeclaration.mm:
* DOM/DOMCSSStyleRule.mm:
* DOM/DOMCSSStyleSheet.mm:
* DOM/DOMCSSUnknownRule.mm:
* DOM/DOMCSSValue.mm:
* DOM/DOMCSSValueList.mm:
* DOM/DOMCharacterData.mm:
* DOM/DOMComment.mm:
* DOM/DOMCounter.mm:
* DOM/DOMDocument.mm:
* DOM/DOMDocumentFragment.mm:
* DOM/DOMDocumentType.mm:
* DOM/DOMElement.mm:
* DOM/DOMEvent.mm:
* DOM/DOMFile.mm:
* DOM/DOMFileList.mm:
* DOM/DOMHTML.mm:
* DOM/DOMHTMLAnchorElement.mm:
* DOM/DOMHTMLAppletElement.mm:
* DOM/DOMHTMLAreaElement.mm:
* DOM/DOMHTMLBRElement.mm:
* DOM/DOMHTMLBaseElement.mm:
* DOM/DOMHTMLBaseFontElement.mm:
* DOM/DOMHTMLBodyElement.mm:
* DOM/DOMHTMLButtonElement.mm:
* DOM/DOMHTMLCanvasElement.mm:
* DOM/DOMHTMLCollection.mm:
* DOM/DOMHTMLDListElement.mm:
* DOM/DOMHTMLDirectoryElement.mm:
* DOM/DOMHTMLDivElement.mm:
* DOM/DOMHTMLDocument.mm:
* DOM/DOMHTMLElement.mm:
* DOM/DOMHTMLEmbedElement.mm:
* DOM/DOMHTMLFieldSetElement.mm:
* DOM/DOMHTMLFontElement.mm:
* DOM/DOMHTMLFormElement.mm:
* DOM/DOMHTMLFrameElement.mm:
* DOM/DOMHTMLFrameSetElement.mm:
* DOM/DOMHTMLHRElement.mm:
* DOM/DOMHTMLHeadElement.mm:
* DOM/DOMHTMLHeadingElement.mm:
* DOM/DOMHTMLHtmlElement.mm:
* DOM/DOMHTMLIFrameElement.mm:
* DOM/DOMHTMLImageElement.mm:
* DOM/DOMHTMLInputElement.mm:
* DOM/DOMHTMLLIElement.mm:
* DOM/DOMHTMLLabelElement.mm:
* DOM/DOMHTMLLegendElement.mm:
* DOM/DOMHTMLLinkElement.mm:
* DOM/DOMHTMLMapElement.mm:
* DOM/DOMHTMLMarqueeElement.mm:
* DOM/DOMHTMLMediaElement.mm:
* DOM/DOMHTMLMenuElement.mm:
* DOM/DOMHTMLMetaElement.mm:
* DOM/DOMHTMLModElement.mm:
* DOM/DOMHTMLOListElement.mm:
* DOM/DOMHTMLObjectElement.mm:
* DOM/DOMHTMLOptGroupElement.mm:
* DOM/DOMHTMLOptionElement.mm:
* DOM/DOMHTMLOptionsCollection.mm:
* DOM/DOMHTMLParagraphElement.mm:
* DOM/DOMHTMLParamElement.mm:
* DOM/DOMHTMLPreElement.mm:
* DOM/DOMHTMLQuoteElement.mm:
* DOM/DOMHTMLScriptElement.mm:
* DOM/DOMHTMLSelectElement.mm:
* DOM/DOMHTMLStyleElement.mm:
* DOM/DOMHTMLTableCaptionElement.mm:
* DOM/DOMHTMLTableCellElement.mm:
* DOM/DOMHTMLTableColElement.mm:
* DOM/DOMHTMLTableElement.mm:
* DOM/DOMHTMLTableRowElement.mm:
* DOM/DOMHTMLTableSectionElement.mm:
* DOM/DOMHTMLTextAreaElement.mm:
* DOM/DOMHTMLTitleElement.mm:
* DOM/DOMHTMLUListElement.mm:
* DOM/DOMHTMLVideoElement.mm:
* DOM/DOMImplementation.mm:
* DOM/DOMKeyboardEvent.mm:
* DOM/DOMMediaError.mm:
* DOM/DOMMediaList.mm:
* DOM/DOMMouseEvent.mm:
* DOM/DOMMutationEvent.mm:
* DOM/DOMNamedNodeMap.mm:
* DOM/DOMNode.mm:
* DOM/DOMNodeIterator.mm:
* DOM/DOMNodeList.mm:
* DOM/DOMOverflowEvent.mm:
* DOM/DOMProcessingInstruction.mm:
* DOM/DOMProgressEvent.mm:
* DOM/DOMRGBColor.mm:
* DOM/DOMRange.mm:
* DOM/DOMRect.mm:
* DOM/DOMStyleSheet.mm:
* DOM/DOMStyleSheetList.mm:
* DOM/DOMText.mm:
* DOM/DOMTextEvent.mm:
* DOM/DOMTimeRanges.mm:
* DOM/DOMTokenList.mm:
* DOM/DOMTreeWalker.mm:
* DOM/DOMUIEvent.mm:
* DOM/DOMWheelEvent.mm:
* DOM/DOMXPathExpression.mm:
* DOM/DOMXPathResult.mm:
* DOM/ObjCEventListener.mm:

LayoutTests:

* http/wpt/workers/microtasks.any-expected.txt: Added.
* http/wpt/workers/microtasks.any.html: Added.
* http/wpt/workers/microtasks.any.js: Added.
(promise_test):
* http/wpt/workers/microtasks.any.worker-expected.txt: Added.
* http/wpt/workers/microtasks.any.worker.html: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@234586 268f45cc-cd09-0410-ab3c-d52691b4dbfc
@mattto

This comment has been minimized.

Copy link
Member

commented Mar 15, 2019

I am looking at Chrome’s implementation in this area again.

I don’t understand why 1 and 2 would be different in #1213 (comment). I think those would both throw. Upon waitPromise fulfilling, the spec queues a microtask to decrement the promise count, and I’d expect that microtask to run before the waitUntil() in both cases.

I’m looking at the current WPT expectations at it looks aligned with this discussion except for one case. Here are the interesting ones I looked at:

Test: waitUntil in a different microtask without an existing extension throws

addEventListener('fetch', event => {
  Promise.resolve().then(event.waitUntil(p));
});
  • Correct expectation: Does not throw (current expectation is wrong)
  • Browsers: Throws: Chrome; Does not throw: Firefox, Edge, Safari

Rationale (from #1394 (comment)): The event dispatch flag is still set while in the microtask. The microtask checkpoint is in "Cleanup After Running Scripts" here:
https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script

This is called from step 16.2 here:
https://heycam.github.io/webidl/#call-a-user-objects-operation

Which in turn is called from the DOM spec's "Inner Invoke" to call event targets:
https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke

Test: waitUntil at the end of the microtask turn throws

addEventListener('fetch', event => {
  event.waitUntil(p);
  p.then(() => {
    Promise.resolve().then(() => { event.waitUntil(q); });
  });
});
  • Correct expectation: Throws (EDIT: should be "Does not throw", see below comments).
  • Browsers: Throws: Chrome, Firefox; Does not throw: Edge, Safari

Rationale: Upon fulfillment of p, the spec queues a microtask to decrement the pending promise count at step 5 here:
https://w3c.github.io/ServiceWorker/#wait-until-method

This microtask runs before waitUntil() is called, so the promise count reaches zero, and the event is not active.

Test: waitUntil asynchronously inside microtask of respondWith promise.

addEventListener(‘fetch’, event => {
  event.respondWith(p);
  p.then(() => {
    Promise.resolve().then(() => { event.waitUntil(q); });
  });     
  • Correct expectation: Throws (EDIT: should be "Does not throw", see below comments).
  • Browsers: Throws: Chrome, Firefox; Does not throw: Edge, Safari

Rationale: Upon fulfillment of p, the spec queues a microtask to decrement the pending promise count at step 5 here:
https://w3c.github.io/ServiceWorker/#fetch-event-respondwith

This microtask runs before waitUntil() is called, so the promise count reaches zero, and the event is not active.

@mattto

This comment has been minimized.

Copy link
Member

commented Mar 15, 2019

Ohhh I may be wrong about this. All the above tests in my last comment actually should not throw.

The reason is the microtask checkpoint at the end of the event dispatcher does not finish until there are no queued microtasks. So in these test cases, the event dispatch flag keeps the event active even though pending promise count reaches 0 at some points.

@mattto

This comment has been minimized.

Copy link
Member

commented Mar 15, 2019

Sorry no that can't be right. p could take a long time and it doesn't make sense for the event dispatch flag to still be set in that case. I wonder if it matters that the test is using p = Promise.resolve() instead of a longer promise like one that resolves after setTimeout.

@mattto

This comment has been minimized.

Copy link
Member

commented Mar 15, 2019

To anyone interested, I have a patch with proposed test changes in https://chromium-review.googlesource.com/c/chromium/src/+/1524393.

chromium-wpt-export-bot added a commit to web-platform-tests/wpt that referenced this issue Mar 15, 2019

service worker: Improve WPT tests for async respondWith/waitUntil.
See discussion at [1] and [2].

This makes the following changes.

1.
Adds a test for:

self.addEventListener('fetch', e => {
  Promise.resolve().then(() => {
    e.respondWith(new Response('hi'));
  });
});

This should not throw because when respondWith() is called the
event dispatch flag is still set.

The microtask checkpoint is in "Cleanup After Running Scripts" here:
https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script

This is called from step 16.2 here:
https://heycam.github.io/webidl/#call-a-user-objects-operation

Which in turn is called from the DOM spec's "Inner Invoke" to call event
targets:
https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke

2.
Changes the expectation for:

addEventListener('message', event => {
  Promise.resolve().then(event.waitUntil(p));
});

From throws to not throws, for the same reasoning as above.

3.
Changes the expectation for:

addEventListener('message', event => {
  waitPromise = Promise.resolve();
  event.waitUntil(waitPromise);
  waitPromise.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  });
});

From throws to not throws. This is subtle. Because all the promises
are just Promise.resolve(), the event dispatch flag is still set
by the time the second waitUntil() is called.

4.
To test what 3. originally intended, a new test is
added which makes waitPromise a promise that does not immediately
resolve.

5.
Changes the expectation for:

addEventListener(‘fetch’, event => {
  response = Promise.resolve(new Response('RESP'));
  event.respondWith(response);
  response.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  })
});

Again this is because the promises used resolve immediately,
so the event dispatch flag is still set.

Similarly, a new test is added to cover the original intent.

These WPT changes appear to match the behavior of Safari and Edge while
diverging from Chrome and (partially) Firefox.

[1] w3c/ServiceWorker#1213
[2] w3c/ServiceWorker#1394

Bug: 942414
Change-Id: I9a4a56d71d3919ed614ff78df2bdc6cc0251dadd

chromium-wpt-export-bot added a commit to web-platform-tests/wpt that referenced this issue Mar 15, 2019

service worker: Improve WPT tests for async respondWith/waitUntil.
See discussion at [1] and [2].

This makes the following changes.

1.
Adds a test for:

self.addEventListener('fetch', e => {
  Promise.resolve().then(() => {
    e.respondWith(new Response('hi'));
  });
});

This should not throw because when respondWith() is called the
event dispatch flag is still set.

The microtask checkpoint is in "Cleanup After Running Scripts" here:
https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script

This is called from step 16.2 here:
https://heycam.github.io/webidl/#call-a-user-objects-operation

Which in turn is called from the DOM spec's "Inner Invoke" to call event
targets:
https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke

2.
Changes the expectation for:

addEventListener('message', event => {
  Promise.resolve().then(event.waitUntil(p));
});

From throws to not throws, for the same reasoning as above.

3.
Changes the expectation for:

addEventListener('message', event => {
  waitPromise = Promise.resolve();
  event.waitUntil(waitPromise);
  waitPromise.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  });
});

From throws to not throws. This is subtle. Because all the promises
are just Promise.resolve(), the event dispatch flag is still set
by the time the second waitUntil() is called.

4.
To test what 3. originally intended, a new test is
added which makes waitPromise a promise that does not immediately
resolve.

5.
Changes the expectation for:

addEventListener(‘fetch’, event => {
  response = Promise.resolve(new Response('RESP'));
  event.respondWith(response);
  response.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  })
});

Again this is because the promises used resolve immediately,
so the event dispatch flag is still set.

Similarly, a new test is added to cover the original intent.

These WPT changes appear to match the behavior of Safari and Edge while
diverging from Chrome and (partially) Firefox.

[1] w3c/ServiceWorker#1213
[2] w3c/ServiceWorker#1394

Bug: 942414
Change-Id: I9a4a56d71d3919ed614ff78df2bdc6cc0251dadd
@wanderview

This comment has been minimized.

Copy link
Member Author

commented Mar 15, 2019

Sorry no that can't be right. p could take a long time and it doesn't make sense for the event dispatch flag to still be set in that case. I wonder if it matters that the test is using p = Promise.resolve() instead of a longer promise like one that resolves after setTimeout.

FWIW, I think this analysis here is correct and your test looks good. Thanks!

@annevk

This comment has been minimized.

Copy link
Member

commented Mar 15, 2019

I wonder if it matters

As far as I know you cannot trigger this situation in other ways (except via similar promise APIs that have near-identical semantics, if those exist). Certainly not with setTimeout.

@wanderview

This comment has been minimized.

Copy link
Member Author

commented Mar 15, 2019

I think in theory something like a fetch() to a data URL could resolve in a single microtask like Promise.resolve(). It wouldn't be guaranteed, though, of course.

@asutherland

This comment has been minimized.

Copy link

commented Mar 15, 2019

Thanks for the clarification and the test changes! I think the initially referenced Firefox bug https://bugzilla.mozilla.org/show_bug.cgi?id=1409979 and the failures in that WPT PR for Firefox are now just about Firefox's ExtendableEvent.waitUntil() failing to check the dispatch flag.

@annevk

This comment has been minimized.

Copy link
Member

commented Mar 16, 2019

@wanderview no, that should at the very least queue a task from the fetch layer.

chromium-wpt-export-bot added a commit to web-platform-tests/wpt that referenced this issue Mar 18, 2019

service worker: Improve WPT tests for async respondWith/waitUntil.
See discussion at [1] and [2].

This makes the following changes.

1.
Adds a test for:

self.addEventListener('fetch', e => {
  Promise.resolve().then(() => {
    e.respondWith(new Response('hi'));
  });
});

This should not throw because when respondWith() is called while the
event dispatch flag is still set.

The microtask checkpoint is in "Cleanup After Running Scripts" here:
https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script

This is called from step 16.2 here:
https://heycam.github.io/webidl/#call-a-user-objects-operation

Which in turn is called from the DOM spec's "Inner Invoke" to call event
targets:
https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke

2.
Changes the expectation for:

addEventListener('message', event => {
  Promise.resolve().then(event.waitUntil(p));
});

From throws to not throws, for the same reasoning as above.

3.
Changes the expectation for:

addEventListener('message', event => {
  waitPromise = Promise.resolve();
  event.waitUntil(waitPromise);
  waitPromise.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  });
});

From throws to not throws. This is subtle. Because all the promises
are just Promise.resolve(), the event dispatch flag is still set
by the time the second waitUntil() is called.

4.
To test what 3. originally intended, a new test is
added which makes waitPromise a promise that does not immediately
resolve.

5.
Changes the expectation for:

addEventListener(‘fetch’, event => {
  response = Promise.resolve(new Response('RESP'));
  event.respondWith(response);
  response.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  })
});

Again this is because the promises used resolve immediately,
so the event dispatch flag is still set.

Similarly, a new test is added to cover the original intent.

These WPT changes appear to match the behavior of Safari and Edge while
diverging from Chrome and (partially) Firefox.

[1] w3c/ServiceWorker#1213
[2] w3c/ServiceWorker#1394

Bug: 942414
Change-Id: I9a4a56d71d3919ed614ff78df2bdc6cc0251dadd

chromium-wpt-export-bot added a commit to web-platform-tests/wpt that referenced this issue Mar 18, 2019

service worker: Improve WPT tests for async respondWith/waitUntil.
See discussion at [1] and [2].

This makes the following changes.

1.
Adds a test for:

self.addEventListener('fetch', e => {
  Promise.resolve().then(() => {
    e.respondWith(new Response('hi'));
  });
});

This should not throw because when respondWith() is called while the
event dispatch flag is still set.

The microtask checkpoint is in "Cleanup After Running Scripts" here:
https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script

This is called from step 16.2 here:
https://heycam.github.io/webidl/#call-a-user-objects-operation

Which in turn is called from the DOM spec's "Inner Invoke" to call event
targets:
https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke

2.
Changes the expectation for:

addEventListener('message', event => {
  Promise.resolve().then(event.waitUntil(p));
});

From throws to not throws, for the same reasoning as above.

3.
Changes the expectation for:

addEventListener('message', event => {
  waitPromise = Promise.resolve();
  event.waitUntil(waitPromise);
  waitPromise.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  });
});

From throws to not throws. This is subtle. Because all the promises
are just Promise.resolve(), the event dispatch flag is still set
by the time the second waitUntil() is called.

4.
To test what 3. originally intended, a new test is
added which makes waitPromise a promise that does not immediately
resolve.

5.
Changes the expectation for:

addEventListener(‘fetch’, event => {
  response = Promise.resolve(new Response('RESP'));
  event.respondWith(response);
  response.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  })
});

Again this is because the promises used resolve immediately,
so the event dispatch flag is still set.

Similarly, a new test is added to cover the original intent.

These WPT changes appear to match the behavior of Safari and Edge while
diverging from Chrome and (partially) Firefox.

[1] w3c/ServiceWorker#1213
[2] w3c/ServiceWorker#1394

Bug: 942414
Change-Id: I9a4a56d71d3919ed614ff78df2bdc6cc0251dadd

chromium-wpt-export-bot added a commit to web-platform-tests/wpt that referenced this issue Mar 18, 2019

service worker: Improve WPT tests for async respondWith/waitUntil.
See discussion at [1] and [2].

This makes the following changes.

1.
Adds a test for:

self.addEventListener('fetch', e => {
  Promise.resolve().then(() => {
    e.respondWith(new Response('hi'));
  });
});

This should not throw because respondWith() is called while the event
dispatch flag is still set.

The microtask checkpoint is in "Cleanup After Running Scripts" here:
https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script

This is called from step 16.2 here:
https://heycam.github.io/webidl/#call-a-user-objects-operation

Which in turn is called from the DOM spec's "Inner Invoke" to call event
targets:
https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke

2.
Changes the expectation for:

addEventListener('message', event => {
  Promise.resolve().then(event.waitUntil(p));
});

From throws to not throws, for the same reasoning as above.

3.
Changes the expectation for:

addEventListener('message', event => {
  waitPromise = Promise.resolve();
  event.waitUntil(waitPromise);
  waitPromise.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  });
});

From throws to not throws. This is subtle. Because all the promises
are just Promise.resolve(), the event dispatch flag is still set
by the time the second waitUntil() is called.

4.
To test what 3. originally intended, a new test is
added which makes waitPromise a promise that does not immediately
resolve.

5.
Changes the expectation for:

addEventListener(‘fetch’, event => {
  response = Promise.resolve(new Response('RESP'));
  event.respondWith(response);
  response.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  })
});

Again this is because the promises used resolve immediately,
so the event dispatch flag is still set.

Similarly, a new test is added to cover the original intent.

These WPT changes appear to match the behavior of Safari and Edge while
diverging from Chrome and (partially) Firefox.

[1] w3c/ServiceWorker#1213
[2] w3c/ServiceWorker#1394

Bug: 942414
Change-Id: I9a4a56d71d3919ed614ff78df2bdc6cc0251dadd

chromium-wpt-export-bot added a commit to web-platform-tests/wpt that referenced this issue Mar 18, 2019

service worker: Improve WPT tests for async respondWith/waitUntil.
See discussion at [1] and [2].

This makes the following changes.

1.
Adds a test for:

self.addEventListener('fetch', e => {
  Promise.resolve().then(() => {
    e.respondWith(new Response('hi'));
  });
});

This should not throw because respondWith() is called while the event
dispatch flag is still set.

The microtask checkpoint is in "Cleanup After Running Scripts" here:
https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script

This is called from step 16.2 here:
https://heycam.github.io/webidl/#call-a-user-objects-operation

Which in turn is called from the DOM spec's "Inner Invoke" to call event
targets:
https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke

2.
Changes the expectation for:

addEventListener('message', event => {
  Promise.resolve().then(event.waitUntil(p));
});

From throws to not throws, for the same reasoning as above.

3.
Changes the expectation for:

addEventListener('message', event => {
  waitPromise = Promise.resolve();
  event.waitUntil(waitPromise);
  waitPromise.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  });
});

From throws to not throws. This is subtle. Because all the promises
are just Promise.resolve(), the event dispatch flag is still set
by the time the second waitUntil() is called.

4.
To test what 3. originally intended, a new test is
added which makes waitPromise a promise that does not immediately
resolve.

5.
Changes the expectation for:

addEventListener(‘fetch’, event => {
  response = Promise.resolve(new Response('RESP'));
  event.respondWith(response);
  response.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  })
});

Again this is because the promises used resolve immediately,
so the event dispatch flag is still set.

Similarly, a new test is added to cover the original intent.

These WPT changes appear to match the behavior of Safari and Edge while
diverging from Chrome and (partially) Firefox.

[1] w3c/ServiceWorker#1213
[2] w3c/ServiceWorker#1394

Bug: 942414
Change-Id: I9a4a56d71d3919ed614ff78df2bdc6cc0251dadd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1524393
Commit-Queue: Matt Falkenhagen <falken@chromium.org>
Reviewed-by: Ben Kelly <wanderview@chromium.org>
Cr-Commit-Position: refs/heads/master@{#641514}

aarongable pushed a commit to chromium/chromium that referenced this issue Mar 18, 2019

Matt Falkenhagen Commit Bot
service worker: Improve WPT tests for async respondWith/waitUntil.
See discussion at [1] and [2].

This makes the following changes.

1.
Adds a test for:

self.addEventListener('fetch', e => {
  Promise.resolve().then(() => {
    e.respondWith(new Response('hi'));
  });
});

This should not throw because respondWith() is called while the event
dispatch flag is still set.

The microtask checkpoint is in "Cleanup After Running Scripts" here:
https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script

This is called from step 16.2 here:
https://heycam.github.io/webidl/#call-a-user-objects-operation

Which in turn is called from the DOM spec's "Inner Invoke" to call event
targets:
https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke

2.
Changes the expectation for:

addEventListener('message', event => {
  Promise.resolve().then(event.waitUntil(p));
});

From throws to not throws, for the same reasoning as above.

3.
Changes the expectation for:

addEventListener('message', event => {
  waitPromise = Promise.resolve();
  event.waitUntil(waitPromise);
  waitPromise.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  });
});

From throws to not throws. This is subtle. Because all the promises
are just Promise.resolve(), the event dispatch flag is still set
by the time the second waitUntil() is called.

4.
To test what 3. originally intended, a new test is
added which makes waitPromise a promise that does not immediately
resolve.

5.
Changes the expectation for:

addEventListener(‘fetch’, event => {
  response = Promise.resolve(new Response('RESP'));
  event.respondWith(response);
  response.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  })
});

Again this is because the promises used resolve immediately,
so the event dispatch flag is still set.

Similarly, a new test is added to cover the original intent.

These WPT changes appear to match the behavior of Safari and Edge while
diverging from Chrome and (partially) Firefox.

[1] w3c/ServiceWorker#1213
[2] w3c/ServiceWorker#1394

Bug: 942414
Change-Id: I9a4a56d71d3919ed614ff78df2bdc6cc0251dadd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1524393
Commit-Queue: Matt Falkenhagen <falken@chromium.org>
Reviewed-by: Ben Kelly <wanderview@chromium.org>
Cr-Commit-Position: refs/heads/master@{#641514}

foolip added a commit to web-platform-tests/wpt that referenced this issue Mar 18, 2019

service worker: Improve WPT tests for async respondWith/waitUntil. (#…
…15852)

See discussion at [1] and [2].

This makes the following changes.

1.
Adds a test for:

self.addEventListener('fetch', e => {
  Promise.resolve().then(() => {
    e.respondWith(new Response('hi'));
  });
});

This should not throw because respondWith() is called while the event
dispatch flag is still set.

The microtask checkpoint is in "Cleanup After Running Scripts" here:
https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script

This is called from step 16.2 here:
https://heycam.github.io/webidl/#call-a-user-objects-operation

Which in turn is called from the DOM spec's "Inner Invoke" to call event
targets:
https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke

2.
Changes the expectation for:

addEventListener('message', event => {
  Promise.resolve().then(event.waitUntil(p));
});

From throws to not throws, for the same reasoning as above.

3.
Changes the expectation for:

addEventListener('message', event => {
  waitPromise = Promise.resolve();
  event.waitUntil(waitPromise);
  waitPromise.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  });
});

From throws to not throws. This is subtle. Because all the promises
are just Promise.resolve(), the event dispatch flag is still set
by the time the second waitUntil() is called.

4.
To test what 3. originally intended, a new test is
added which makes waitPromise a promise that does not immediately
resolve.

5.
Changes the expectation for:

addEventListener(‘fetch’, event => {
  response = Promise.resolve(new Response('RESP'));
  event.respondWith(response);
  response.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  })
});

Again this is because the promises used resolve immediately,
so the event dispatch flag is still set.

Similarly, a new test is added to cover the original intent.

These WPT changes appear to match the behavior of Safari and Edge while
diverging from Chrome and (partially) Firefox.

[1] w3c/ServiceWorker#1213
[2] w3c/ServiceWorker#1394

Bug: 942414
Change-Id: I9a4a56d71d3919ed614ff78df2bdc6cc0251dadd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1524393
Commit-Queue: Matt Falkenhagen <falken@chromium.org>
Reviewed-by: Ben Kelly <wanderview@chromium.org>
Cr-Commit-Position: refs/heads/master@{#641514}
@mattto

This comment has been minimized.

Copy link
Member

commented Mar 19, 2019

Thanks all! the WPT tests have been updated. The tricky part was indeed that some tests were using Promise.resolve() which meant the event dispatch flag was still set when the test intended to test the pending promises count instead. I've added test cases for those now.

I think we can now close this.

@mattto mattto closed this Mar 19, 2019

moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this issue Apr 23, 2019

Bug 1536619 [wpt PR 15852] - service worker: Improve WPT tests for as…
…ync respondWith/waitUntil., a=testonly

Automatic update from web-platform-tests
service worker: Improve WPT tests for async respondWith/waitUntil. (#15852)

See discussion at [1] and [2].

This makes the following changes.

1.
Adds a test for:

self.addEventListener('fetch', e => {
  Promise.resolve().then(() => {
    e.respondWith(new Response('hi'));
  });
});

This should not throw because respondWith() is called while the event
dispatch flag is still set.

The microtask checkpoint is in "Cleanup After Running Scripts" here:
https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script

This is called from step 16.2 here:
https://heycam.github.io/webidl/#call-a-user-objects-operation

Which in turn is called from the DOM spec's "Inner Invoke" to call event
targets:
https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke

2.
Changes the expectation for:

addEventListener('message', event => {
  Promise.resolve().then(event.waitUntil(p));
});

From throws to not throws, for the same reasoning as above.

3.
Changes the expectation for:

addEventListener('message', event => {
  waitPromise = Promise.resolve();
  event.waitUntil(waitPromise);
  waitPromise.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  });
});

From throws to not throws. This is subtle. Because all the promises
are just Promise.resolve(), the event dispatch flag is still set
by the time the second waitUntil() is called.

4.
To test what 3. originally intended, a new test is
added which makes waitPromise a promise that does not immediately
resolve.

5.
Changes the expectation for:

addEventListener(‘fetch’, event => {
  response = Promise.resolve(new Response('RESP'));
  event.respondWith(response);
  response.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  })
});

Again this is because the promises used resolve immediately,
so the event dispatch flag is still set.

Similarly, a new test is added to cover the original intent.

These WPT changes appear to match the behavior of Safari and Edge while
diverging from Chrome and (partially) Firefox.

[1] w3c/ServiceWorker#1213
[2] w3c/ServiceWorker#1394

Bug: 942414
Change-Id: I9a4a56d71d3919ed614ff78df2bdc6cc0251dadd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1524393
Commit-Queue: Matt Falkenhagen <falken@chromium.org>
Reviewed-by: Ben Kelly <wanderview@chromium.org>
Cr-Commit-Position: refs/heads/master@{#641514}
--

wpt-commits: cecb3eba4dae3d795876f7b4be71bd49afa03356
wpt-pr: 15852

moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this issue Apr 24, 2019

Bug 1536619 [wpt PR 15852] - service worker: Improve WPT tests for as…
…ync respondWith/waitUntil., a=testonly

Automatic update from web-platform-tests
service worker: Improve WPT tests for async respondWith/waitUntil. (#15852)

See discussion at [1] and [2].

This makes the following changes.

1.
Adds a test for:

self.addEventListener('fetch', e => {
  Promise.resolve().then(() => {
    e.respondWith(new Response('hi'));
  });
});

This should not throw because respondWith() is called while the event
dispatch flag is still set.

The microtask checkpoint is in "Cleanup After Running Scripts" here:
https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script

This is called from step 16.2 here:
https://heycam.github.io/webidl/#call-a-user-objects-operation

Which in turn is called from the DOM spec's "Inner Invoke" to call event
targets:
https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke

2.
Changes the expectation for:

addEventListener('message', event => {
  Promise.resolve().then(event.waitUntil(p));
});

From throws to not throws, for the same reasoning as above.

3.
Changes the expectation for:

addEventListener('message', event => {
  waitPromise = Promise.resolve();
  event.waitUntil(waitPromise);
  waitPromise.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  });
});

From throws to not throws. This is subtle. Because all the promises
are just Promise.resolve(), the event dispatch flag is still set
by the time the second waitUntil() is called.

4.
To test what 3. originally intended, a new test is
added which makes waitPromise a promise that does not immediately
resolve.

5.
Changes the expectation for:

addEventListener(‘fetch’, event => {
  response = Promise.resolve(new Response('RESP'));
  event.respondWith(response);
  response.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  })
});

Again this is because the promises used resolve immediately,
so the event dispatch flag is still set.

Similarly, a new test is added to cover the original intent.

These WPT changes appear to match the behavior of Safari and Edge while
diverging from Chrome and (partially) Firefox.

[1] w3c/ServiceWorker#1213
[2] w3c/ServiceWorker#1394

Bug: 942414
Change-Id: I9a4a56d71d3919ed614ff78df2bdc6cc0251dadd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1524393
Commit-Queue: Matt Falkenhagen <falken@chromium.org>
Reviewed-by: Ben Kelly <wanderview@chromium.org>
Cr-Commit-Position: refs/heads/master@{#641514}
--

wpt-commits: cecb3eba4dae3d795876f7b4be71bd49afa03356
wpt-pr: 15852

mykmelez pushed a commit to mykmelez/gecko that referenced this issue Apr 25, 2019

Bug 1536619 [wpt PR 15852] - service worker: Improve WPT tests for as…
…ync respondWith/waitUntil., a=testonly

Automatic update from web-platform-tests
service worker: Improve WPT tests for async respondWith/waitUntil. (#15852)

See discussion at [1] and [2].

This makes the following changes.

1.
Adds a test for:

self.addEventListener('fetch', e => {
  Promise.resolve().then(() => {
    e.respondWith(new Response('hi'));
  });
});

This should not throw because respondWith() is called while the event
dispatch flag is still set.

The microtask checkpoint is in "Cleanup After Running Scripts" here:
https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script

This is called from step 16.2 here:
https://heycam.github.io/webidl/#call-a-user-objects-operation

Which in turn is called from the DOM spec's "Inner Invoke" to call event
targets:
https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke

2.
Changes the expectation for:

addEventListener('message', event => {
  Promise.resolve().then(event.waitUntil(p));
});

From throws to not throws, for the same reasoning as above.

3.
Changes the expectation for:

addEventListener('message', event => {
  waitPromise = Promise.resolve();
  event.waitUntil(waitPromise);
  waitPromise.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  });
});

From throws to not throws. This is subtle. Because all the promises
are just Promise.resolve(), the event dispatch flag is still set
by the time the second waitUntil() is called.

4.
To test what 3. originally intended, a new test is
added which makes waitPromise a promise that does not immediately
resolve.

5.
Changes the expectation for:

addEventListener(‘fetch’, event => {
  response = Promise.resolve(new Response('RESP'));
  event.respondWith(response);
  response.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  })
});

Again this is because the promises used resolve immediately,
so the event dispatch flag is still set.

Similarly, a new test is added to cover the original intent.

These WPT changes appear to match the behavior of Safari and Edge while
diverging from Chrome and (partially) Firefox.

[1] w3c/ServiceWorker#1213
[2] w3c/ServiceWorker#1394

Bug: 942414
Change-Id: I9a4a56d71d3919ed614ff78df2bdc6cc0251dadd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1524393
Commit-Queue: Matt Falkenhagen <falken@chromium.org>
Reviewed-by: Ben Kelly <wanderview@chromium.org>
Cr-Commit-Position: refs/heads/master@{#641514}
--

wpt-commits: cecb3eba4dae3d795876f7b4be71bd49afa03356
wpt-pr: 15852

mykmelez pushed a commit to mykmelez/gecko that referenced this issue Apr 25, 2019

Bug 1536619 [wpt PR 15852] - service worker: Improve WPT tests for as…
…ync respondWith/waitUntil., a=testonly

Automatic update from web-platform-tests
service worker: Improve WPT tests for async respondWith/waitUntil. (#15852)

See discussion at [1] and [2].

This makes the following changes.

1.
Adds a test for:

self.addEventListener('fetch', e => {
  Promise.resolve().then(() => {
    e.respondWith(new Response('hi'));
  });
});

This should not throw because respondWith() is called while the event
dispatch flag is still set.

The microtask checkpoint is in "Cleanup After Running Scripts" here:
https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script

This is called from step 16.2 here:
https://heycam.github.io/webidl/#call-a-user-objects-operation

Which in turn is called from the DOM spec's "Inner Invoke" to call event
targets:
https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke

2.
Changes the expectation for:

addEventListener('message', event => {
  Promise.resolve().then(event.waitUntil(p));
});

From throws to not throws, for the same reasoning as above.

3.
Changes the expectation for:

addEventListener('message', event => {
  waitPromise = Promise.resolve();
  event.waitUntil(waitPromise);
  waitPromise.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  });
});

From throws to not throws. This is subtle. Because all the promises
are just Promise.resolve(), the event dispatch flag is still set
by the time the second waitUntil() is called.

4.
To test what 3. originally intended, a new test is
added which makes waitPromise a promise that does not immediately
resolve.

5.
Changes the expectation for:

addEventListener(‘fetch’, event => {
  response = Promise.resolve(new Response('RESP'));
  event.respondWith(response);
  response.then(() => {
    Promise.resolve().then(() => {event.waitUntil();});
  })
});

Again this is because the promises used resolve immediately,
so the event dispatch flag is still set.

Similarly, a new test is added to cover the original intent.

These WPT changes appear to match the behavior of Safari and Edge while
diverging from Chrome and (partially) Firefox.

[1] w3c/ServiceWorker#1213
[2] w3c/ServiceWorker#1394

Bug: 942414
Change-Id: I9a4a56d71d3919ed614ff78df2bdc6cc0251dadd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1524393
Commit-Queue: Matt Falkenhagen <falken@chromium.org>
Reviewed-by: Ben Kelly <wanderview@chromium.org>
Cr-Commit-Position: refs/heads/master@{#641514}
--

wpt-commits: cecb3eba4dae3d795876f7b4be71bd49afa03356
wpt-pr: 15852
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.