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

Timing of custom element microtasks isn't the way WPT expects it to be #25016

Closed
pshaughn opened this issue Dec 2, 2019 · 20 comments · Fixed by #25515
Closed

Timing of custom element microtasks isn't the way WPT expects it to be #25016

pshaughn opened this issue Dec 2, 2019 · 20 comments · Fixed by #25515
Assignees
Labels
A-content/dom Interacting with the DOM from web content B-high-value Represents work that would have a big impact

Comments

@pshaughn
Copy link
Member

pshaughn commented Dec 2, 2019

WPT custom-elements/microtasks-and-constructors.html and perform-microtask-checkpoint-before-construction.html test many specific expectations about when the microtasks of custom elements happen, and Servo violates many of those expectations.

The microtasks-and-constructors test is a little hard to read, since it's broken up into separate script elements to make interesting event orders happen while the document is still being parsed. My reading of it is that multiple script bodies seem to be firing in a row, where the callbacks of custom elements between those scripts are expected be firing in between them. I'm not sure I understand it, though.

@pshaughn pshaughn changed the title Timing of custom element microtasks Timing of custom element microtasks isn't the way WPT expects it to be Dec 2, 2019
@jdm jdm added the A-content/dom Interacting with the DOM from web content label Dec 2, 2019
@jdm jdm added this to To do in web-platform-test failures via automation Dec 2, 2019
@jdm jdm added the B-high-value Represents work that would have a big impact label Dec 2, 2019
@jdm
Copy link
Member

jdm commented Dec 2, 2019

Marking this as high-value since tests that expose issues in Servo's timing/ordering of microtasks could plausibly have broader web compatibility impact.

The real question is whether this is limited to the custom-elements tasks or whether this is exposing an issue in when Servo runs microtasks at all.

@pshaughn
Copy link
Member Author

pshaughn commented Jan 13, 2020

We're missing a microtask checkpoint that should be coming from inside the parser: when a custom element constructor enqueues a microtask, that microtask should fire BEFORE the start of the next element, but instead in Servo the microtask hangs around in the queue until the checkpoint at the end of the next <script> block. The simplest case for this is named "Microtasks evaluate immediately when the stack is empty inside the parser.", which gives some clue where to look.

@pshaughn pshaughn self-assigned this Jan 13, 2020
@jdm
Copy link
Member

jdm commented Jan 13, 2020

One question I have - given:

Why doesn't the microtask checkpoint occur?

@pshaughn
Copy link
Member Author

That one does! The microtask that was supposed to fire earlier and didn't does fire at that point.

customElements.define("x-0", class extends HTMLElement {
constructor() {
super();
logMicrotasks(window.x0Events);
}
});
</script>
<x-0></x-0>
<script>
"use strict";
test(() => {
assert_array_equals(window.x0Events, ["promise microtask", "MutationObserver microtask"]);
}, "Microtasks evaluate immediately when the stack is empty inside the parser");
customElements.define("x-bad", class extends HTMLElement {
constructor() {
super();
doMicrotasks(() => this.setAttribute("attribute", "value"));
}
});
</script>

We get checkpoints at 43 and 60; we're also supposed to be getting a checkpoint at 45.

@pshaughn
Copy link
Member Author

https://html.spec.whatwg.org/multipage/parsing.html#create-an-element-for-the-token says to perform a checkpoint, but it looks like it's saying to do it before the custom element constructor call rather than after, so it doesn't seem to be the one this test wants.

@jdm
Copy link
Member

jdm commented Jan 13, 2020

It probably comes from https://heycam.github.io/webidl/#invoke-a-callback-function which comes from https://html.spec.whatwg.org/multipage/custom-elements.html#invoke-custom-element-reactions which comes from step 9 in that algorithm.

@jdm
Copy link
Member

jdm commented Jan 13, 2020

Either that or https://heycam.github.io/webidl/#construct-a-callback-function from the Upgrade path.

@pshaughn
Copy link
Member Author

I am think it's case "An end tag whose name is <script>" in https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-incdata actually. It looks like the INSTANT the HTML parser sees a </script>, before actually javascript-parsing the script itself, we are supposed to get a checkpoint.

@jdm
Copy link
Member

jdm commented Jan 13, 2020

Hmm, good point!

@pshaughn
Copy link
Member Author

Adding a checkpoint at line 560 here:

let script = match feed(&mut *self.tokenizer.borrow_mut()) {
Ok(()) => return,
Err(script) => script,
};
let script_nesting_level = self.script_nesting_level.get();

passes some of the tests, but not all.

@pshaughn
Copy link
Member Author

pshaughn commented Jan 13, 2020

customElements.define("x-bad", class extends HTMLElement {
constructor() {
super();
doMicrotasks(() => this.setAttribute("attribute", "value"));
}
});
</script>
<x-bad></x-bad>
<script>
"use strict";
test(() => {
const xBad = document.querySelector("x-bad");
assert_false(xBad.hasAttribute("attribute"), "The attribute must not be present");
assert_true(xBad instanceof HTMLUnknownElement, "The element must be a HTMLUnknownElement");
I don't yet understand why this constructor is supposed to fail and leave us with an HTMLUnknownElement, but Firefox and Chrome agree that it does; it fails in Servo by making an HTMLElement with an attribute.

@pshaughn
Copy link
Member Author

I think I've found it; it zig-zags across three specs. https://dom.spec.whatwg.org/#concept-create-element says "the result of constructing C", with a link to the WebIDL spec. https://heycam.github.io/webidl/#construct-a-callback-function includes "Clean up after running script" before returning, with a link to the HTML spec. https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script says we run a checkpoint here.

That checkpoint would thus call setAttribute while we are still on step 6.1.2 of "create an element", meaning that when we get to step 6.1.5 we have an attribute and need to throw.

@jdm
Copy link
Member

jdm commented Jan 13, 2020

Implementation detail that may help - the codegen for webidl callbacks is based on

/// A class that performs whatever setup we need to safely make a call while
/// this class is on the stack. After `new` returns, the call is safe to make.
pub struct CallSetup {
/// The global for reporting exceptions. This is the global object of the
/// (possibly wrapped) callback object.
exception_global: DomRoot<GlobalScope>,
/// The `JSContext` used for the call.
cx: JSContext,
/// The compartment we were in before the call.
old_realm: *mut Realm,
/// The exception handling used for the call.
handling: ExceptionHandling,
/// <https://heycam.github.io/webidl/#es-invoking-callback-functions>
/// steps 8 and 18.2.
entry_script: Option<AutoEntryScript>,
/// <https://heycam.github.io/webidl/#es-invoking-callback-functions>
/// steps 9 and 18.1.
incumbent_script: Option<AutoIncumbentScript>,
}
impl CallSetup {
/// Performs the setup needed to make a call.
#[allow(unrooted_must_root)]
pub fn new<T: CallbackContainer>(callback: &T, handling: ExceptionHandling) -> CallSetup {
let global = unsafe { GlobalScope::from_object(callback.callback()) };
if let Some(window) = global.downcast::<Window>() {
window.Document().ensure_safe_to_run_script_or_layout();
}
let cx = global.get_cx();
let aes = AutoEntryScript::new(&global);
let ais = callback.incumbent().map(AutoIncumbentScript::new);
CallSetup {
exception_global: global,
cx: cx,
old_realm: unsafe { EnterRealm(*cx, callback.callback()) },
handling: handling,
entry_script: Some(aes),
incumbent_script: ais,
}
}
/// Returns the `JSContext` used for the call.
pub fn get_context(&self) -> JSContext {
self.cx
}
}
impl Drop for CallSetup {
fn drop(&mut self) {
unsafe {
LeaveRealm(*self.cx, self.old_realm);
if self.handling == ExceptionHandling::Report {
let _ac = JSAutoRealm::new(
*self.cx,
self.exception_global.reflector().get_jsobject().get(),
);
report_pending_exception(*self.cx, true);
}
drop(self.incumbent_script.take());
drop(self.entry_script.take().unwrap());
}
}
}
, which is based around the same AutoEntryScript that performs a microtask checkpoint in its drop implementation.

@pshaughn
Copy link
Member Author

The "result of constructing C" phrase only appears twice and both times are about custom element definitions, so I don't think this will require a codegen change.

@pshaughn
Copy link
Member Author

These actually aren't going through an AutoEntryScript; they're creating a js::jsapi::JSAutoRealm then calling directly into js::rust::wrappers::Construct1. The only other piece of code doing this is PaintWorkletGlobalScope::invoke_a_paint_callback.

In the paint worklet's case, the spec says "the result of Construct(paintCtor)" and is linking to ECMAScript, not WebIDL, for the definition of "Construct", so the paint worklet doesn't need to perform a microtask checkpoint on the way out of constructing and is of no concern here.

@jdm
Copy link
Member

jdm commented Jan 13, 2020

Ooh, that's good to know. We should probably add an AutoEntryScript, in that case.

@pshaughn
Copy link
Member Author

I don't know whether AutoEntryScript applies here. Adding a checkpoint after exiting the Construct1 call's realm, combined with the checkpoint in the tokenizer, passes every test in microtasks_and_constructors.html.

None of this has resolved the failures in perform-a-microtask-before-construction.html. The most obvious difference I can see between the tests is that where microtasks-and-checkpoints.html does everything in the content document of a normally fetched top-level window, perform-a-microtask-before-construction.html sets the srcdoc of an iframe. My first thought is that this one might involve Servo checking the wrong global's custom element registry.

@jdm
Copy link
Member

jdm commented Jan 13, 2020

Totally believable!

@pshaughn
Copy link
Member Author

pshaughn commented Jan 13, 2020

The element is upgraded to custom, so it's the right registry, but the microtask checkpoint on upgrading doesn't fire, and this is almost definitely another timing problem in HTMLIFrameElement::navigate_or_reload_child_browsing_context (#24901, #25098), since that's what assigning to srcdoc actually calls and we already know it's getting things wrong. I will dig a little more to confirm that's what's happening, then if it is I'll do a PR for just the microtasks-and-constructors.html part.

@pshaughn
Copy link
Member Author

Opened #25514 for that so I can close this.

bors-servo pushed a commit that referenced this issue Jan 14, 2020
Add microtask checkpoints to script elements and custom elements

Servo had a microtask checkpoint at the end of running a script, but there was also supposed to be one at the end of HTML-parsing a script element before Javascript-parsing the script itself, and there were supposed to be checkpoints immediately after the call to a custom element constructor. This adds those, passing all cases of one WPT test file.

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix #25016 except for the remaining not-really-about-microtasks case #25514

<!-- Either: -->
- [X] There are tests for these changes

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
bors-servo pushed a commit that referenced this issue Jan 14, 2020
Add microtask checkpoints to script elements and custom elements

Servo had a microtask checkpoint at the end of running a script, but there was also supposed to be one at the end of HTML-parsing a script element before Javascript-parsing the script itself, and there were supposed to be checkpoints immediately after the call to a custom element constructor. This adds those, passing all cases of one WPT test file.

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix #25016 except for the remaining not-really-about-microtasks case #25514

<!-- Either: -->
- [X] There are tests for these changes

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
bors-servo pushed a commit that referenced this issue Jan 25, 2020
Add microtask checkpoints to script elements and custom elements

Servo had a microtask checkpoint at the end of running a script, but there was also supposed to be one at the end of HTML-parsing a script element before Javascript-parsing the script itself, and there were supposed to be checkpoints immediately after the call to a custom element constructor. This adds those, passing all cases of one WPT test file.

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix #25016 except for the remaining not-really-about-microtasks case #25514

<!-- Either: -->
- [X] There are tests for these changes

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
web-platform-test failures automation moved this from To do to Done Jan 25, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-content/dom Interacting with the DOM from web content B-high-value Represents work that would have a big impact
Projects
Development

Successfully merging a pull request may close this issue.

2 participants