RESOLVED FIXED 203288
MediaQueryList should extend EventTarget
https://bugs.webkit.org/show_bug.cgi?id=203288
Summary MediaQueryList should extend EventTarget
Alexey Shvayka
Reported 2019-10-23 00:21:35 PDT
Initially, CSSOM View Module specification (https://www.w3.org/TR/2011/WD-cssom-view-20110804/#mediaquerylist) had a custom callback mechanism with addListener() and removeListener(), and the callback was invoked with the associated media query list as argument. Now the normal event mechanism is used instead: https://www.w3.org/TR/cssom-view-1/#mediaquerylist. For backwards compatibility, the addListener() and removeListener() methods are basically aliases for addEventListener() and removeEventListener(), respectively, and the "change" event masquerades as a MediaQueryList. Other browsers have already shipped the change. To do list for Safari: 1. Implement MediaQueryListEvent interface. 2. Make MediaQueryList subclass of EventTarget and emit MediaQueryListEvent. 3. Implement MediaQueryList::onchange attribute.
Attachments
Patch (73.20 KB, patch)
2019-11-22 07:31 PST, Alexey Shvayka
sam: review-
Patch (85.73 KB, patch)
2020-01-09 05:15 PST, Alexey Shvayka
no flags
Patch (101.61 KB, patch)
2020-03-27 08:38 PDT, Alexey Shvayka
no flags
Patch (101.83 KB, patch)
2020-03-27 11:49 PDT, Alexey Shvayka
no flags
Patch (102.24 KB, patch)
2020-03-27 12:22 PDT, Alexey Shvayka
no flags
Patch (102.41 KB, patch)
2020-04-10 08:01 PDT, Alexey Shvayka
no flags
Radar WebKit Bug Importer
Comment 1 2019-11-21 19:25:32 PST
Alexey Shvayka
Comment 2 2019-11-22 07:31:00 PST
Alexey Shvayka
Comment 3 2019-11-22 07:43:29 PST
While this patch is ready for review, WPT tests are coming in https://github.com/web-platform-tests/wpt/pull/20401. I will re-sync wpt/css/cssom-view and rebase this patch as soon as it is merged.
Sam Weinig
Comment 4 2019-11-25 17:49:13 PST
Comment on attachment 384151 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=384151&action=review > Source/WebCore/css/MediaQueryList.cpp:60 > + addEventListener("change", listener.releaseNonNull()); The IDL declares the listener parameter as optional, so I don't think you can remove the null check if you are going to keep this listener.releaseNonNull(). Same with removeListener below.
Alexey Shvayka
Comment 5 2020-01-09 05:15:17 PST
Created attachment 387214 [details] Patch Bring back null checks and adjust expectations.
Antti Koivisto
Comment 6 2020-01-10 00:58:44 PST
Comment on attachment 387214 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=387214&action=review Looks good in general. > Source/WebCore/css/MediaQueryMatcher.cpp:116 > + for (auto& mql : m_mediaQueryLists) { WebKit coding style doesn't like abbreviations. This should be "mediaQueryList" or maybe just "list" if the context is clear. (the existing media query code is old and doesn't necessarily follow this) > LayoutTests/imported/w3c/web-platform-tests/css/cssom-view/MediaQueryList-addListener-handleEvent-expected.txt:10 > +CONSOLE MESSAGE: line 90: [object Object] > > -FAIL calls handleEvent method of event listener promise_test: Unhandled rejection with value: object "TypeError: Argument 1 ('listener') to MediaQueryList.addListener must be a function" > -FAIL looks up handleEvent method on every event dispatch promise_test: Unhandled rejection with value: object "TypeError: Argument 1 ('listener') to MediaQueryList.addListener must be a function" > -PASS doesn't look up handleEvent method on callable event listeners > -FAIL rethrows errors when getting handleEvent promise_rejects_exactly: function "function () { throw e }" threw object "TypeError: Argument 1 ('listener') to MediaQueryList.addListener must be a function" but we expected it to throw object "[object Object]" > -PASS throws if handleEvent is falsy and not callable > -PASS throws if handleEvent is thruthy and not callable > +Harness Error (TIMEOUT), message = null > > +PASS calls handleEvent method of event listener > +PASS looks up handleEvent method on every event dispatch > +PASS doesn't look up handleEvent method on callable event listeners > +TIMEOUT rethrows errors when getting handleEvent Test timed out > +NOTRUN throws if handleEvent is falsy and not callable > +NOTRUN throws if handleEvent is thruthy and not callable Looks like one test started timing out and the rest are not run. It is not ok to rebase that.
Antti Koivisto
Comment 7 2020-03-13 04:13:40 PDT
Comment on attachment 387214 [details] Patch r- due to test timeouts. This looks pretty good though, it would be nice to get it landed.
Alexey Shvayka
Comment 8 2020-03-22 10:14:54 PDT
(In reply to Antti Koivisto from comment #7) > Comment on attachment 387214 [details] > Patch > > r- due to test timeouts. Thank you, Antti. https://github.com/web-platform-tests/wpt/pull/21186 fixed TIMEOUT issue, yet the tests still remain false-positive. https://github.com/web-platform-tests/wpt/pull/22384 fixes the tests. I will sync WPT and update the patch as soon as it gets merged.
Alexey Shvayka
Comment 9 2020-03-27 08:38:16 PDT
Created attachment 394720 [details] Patch Rename `mql`, adjust tests, and tweak ChangeLog.
Darin Adler
Comment 10 2020-03-27 09:16:15 PDT
Comment on attachment 394720 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=394720&action=review Looks good. I am not sure we have enough test coverage for object lifetime issues. > Source/WebCore/css/MediaQueryList.cpp:63 > + addEventListener("change", listener.releaseNonNull()); eventNames().changeEvent rather than "change" > Source/WebCore/css/MediaQueryList.cpp:71 > + removeEventListener("change", *listener); Ditto. > Source/WebCore/css/MediaQueryList.h:26 > #include <wtf/Forward.h> > #include <wtf/RefCounted.h> > #include <wtf/RefPtr.h> None of these includes are needed now that we include EventTarget.h. > Source/WebCore/css/MediaQueryList.h:43 > + static Ref<MediaQueryList> create(ScriptExecutionContext&, MediaQueryMatcher&, Ref<MediaQuerySet>&&, bool matches); I suggest this take a Document& instead of a ScriptExecutionContext&. > Source/WebCore/css/MediaQueryList.h:58 > + MediaQueryList(ScriptExecutionContext&, MediaQueryMatcher&, Ref<MediaQuerySet>&&, bool matches); I suggest this take a Document& instead of a ScriptExecutionContext&. > Source/WebCore/css/MediaQueryList.idl:26 > + void addListener(EventListener? listener); > + void removeListener(EventListener? listener); Is it important for compatibility that the listener argument be nullable? If not we should remove that because it’s peculiar and unnecessary. > Source/WebCore/css/MediaQueryListEvent.h:29 > +#include <wtf/IsoMallocInlines.h> I think these inlines might not be needed in the header if we moved the create functions out of the header. Not sure. > Source/WebCore/css/MediaQueryListEvent.h:39 > + static Ref<MediaQueryListEvent> create(const AtomString& type, const String& media, bool matches) > + { > + return adoptRef(*new MediaQueryListEvent(type, media, matches)); > + } I don’t think it’s valuable to have this inlined in the header. It would be better for runtime performance if the constructor was inlined in a non-inline create function, and better for compile speed to have less in the header. > Source/WebCore/css/MediaQueryListEvent.h:42 > + String media { "" }; emptyString(), not "", is more efficient But also, is it important that media ia an empty string rather than a null string? Normally I’d expect a null string would suffice. > Source/WebCore/css/MediaQueryListEvent.h:46 > + static Ref<MediaQueryListEvent> create(const AtomString& type, const Init& init, IsTrusted isTrusted = IsTrusted::No) Same thought about not inlining this. > Source/WebCore/css/MediaQueryMatcher.cpp:118 > + list->evaluate(evaluator, notify); Since list is a weak pointer, it seems we would need to check for null here. > Source/WebCore/css/MediaQueryMatcher.cpp:121 > + auto event = MediaQueryListEvent::create(eventNames().changeEvent, list->media(), list->matches()); > + list->dispatchEvent(event); Do we get value from the local variable here? I think this would be better as a one-liner even if the line is a little longer.
Darin Adler
Comment 11 2020-03-27 09:50:09 PDT
Comment on attachment 394720 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=394720&action=review >> Source/WebCore/css/MediaQueryListEvent.h:42 >> + String media { "" }; > > emptyString(), not "", is more efficient > > But also, is it important that media ia an empty string rather than a null string? Normally I’d expect a null string would suffice. Could answer this question with tests, I think. If null string doesn’t work we should have a test that would fail if this is a null string. Or Chris Dumez might just know this answer. Surely this has come up in other structures that are part of DOM bindings.
Alexey Shvayka
Comment 12 2020-03-27 11:49:14 PDT
Created attachment 394740 [details] Patch Address Darin's comments.
Darin Adler
Comment 13 2020-03-27 12:13:11 PDT
Comment on attachment 394740 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=394740&action=review > Source/WebCore/css/MediaQueryListEvent.h:36 > + String media { nullString() }; No need to write { nullString() } … that’s the default initialization for WTF::String.
Alexey Shvayka
Comment 14 2020-03-27 12:22:53 PDT
Created attachment 394746 [details] Patch Drop nullString() and add optional EventListener tests.
Alexey Shvayka
Comment 15 2020-03-27 12:33:41 PDT
(In reply to Darin Adler from comment #13) > No need to write { nullString() } … that’s the default initialization for > WTF::String. Sorry! Now MediaQueryListEvent::EventInit is consistent with other DOM bindings with its default values tested in web-platform-tests/css/cssom-view/MediaQueryListEvent. (In reply to Darin Adler from comment #10) > Is it important for compatibility that the listener argument be nullable? If > not we should remove that because it’s peculiar and unnecessary. Sadly it is required, both Chrome and Firefox accept .addListener(null | undefined), which I added tests for in fast/media/media-query-list-07.html. Zero argument .addListener() throwing TypeError is covered by web-platform-tests/css/cssom-view/idlharness.html. Regarding test failures in web-platform-tests/css/cssom-view/MediaQueryList-addListener-handleEvent.html: those errors are visible in DevTools, but not caught correctly by "error" handler. It seemed like cross-realm issue at first, but it also reproduces in single-realm XPathNSResolver tests (web-platform-tests/domxpath/callback-interface.html). Chrome is also affected. I will open a bug as soon as I triage it a little more.
Alexey Shvayka
Comment 16 2020-04-03 09:41:56 PDT
*** Bug 156730 has been marked as a duplicate of this bug. ***
Alexey Shvayka
Comment 17 2020-04-10 08:01:43 PDT
Created attachment 396083 [details] Patch Rebase patch.
Alexey Shvayka
Comment 18 2020-04-13 10:21:51 PDT
(In reply to Darin Adler from comment #10) > Comment on attachment 394720 [details] > Patch > > View in context: > https://bugs.webkit.org/attachment.cgi?id=394720&action=review > > Looks good. I am not sure we have enough test coverage for object lifetime > issues. Darin, could you please give a hint on how such a test may look like? I am not sure MediaQueryList::~MediaQueryList() destructor is observable enough so it could be tested. There are a few tests for wrappers like LayoutTests/crypto/crypto-gc.html, yet I am not positive we need one since MediaQueryList's wrapper is not special. Is there something else we should do before landing?
Darin Adler
Comment 19 2020-04-13 19:52:22 PDT
(In reply to Alexey Shvayka from comment #18) > Darin, could you please give a hint on how such a test may look like? > I am not sure MediaQueryList::~MediaQueryList() destructor is observable > enough so it could be tested. > There are a few tests for wrappers like LayoutTests/crypto/crypto-gc.html, > yet I am not positive we need one since MediaQueryList's wrapper is not > special. > Is there something else we should do before landing? I don't think there is anything we have to do before landing. Here is what I can think of regarding lifetime: - If we add a listener to an object that also captures that same object, we need to make sure that the object and the listener can still both be garbage collected and there is no cycle. That's what fast/dom/reference-cycle-leaks.html tests for. But I don't think we need a special test for MediaQueryList since it's just using a normal event listener. - If we add a listener to an object and all other references to the object are deleted, we need to make sure that the listener still fires when the event should be dispatched and that when it does the object's wrapper still has custom properties (not a new wrapper). An attempt to test for something like this is http/tests/xmlhttprequest/event-listener-gc.html but on review that particular test doesn't seem to be correctly written. Garbage collection is not consistent enough; there is no guarantee a single object will be collected if we only run the test once. I don't know if have good tests for this. I don’t know what keeps a MediaQueryList alive when there are no references to it any more, but its existence can still be observed indirectly by whether events fire or not. This is probably the test that should be written, and there may be a real bug here. - Generally it's an anti-pattern to have a RefCounted object that is held in a collection in another object that removes itself when it's destroyed. That's because whether the object should be iterated or not shouldn't really be decided by whether it happens to have been destroyed or not. I can't see any practical problem with this, but if we could think of what might go wrong we'd write that kind of test.
Antti Koivisto
Comment 20 2020-04-17 03:02:36 PDT
Comment on attachment 396083 [details] Patch Sounds like everyone is ok to land this.
EWS
Comment 21 2020-04-17 03:16:40 PDT
Committed r260243: <https://trac.webkit.org/changeset/260243> All reviewed patches have been landed. Closing bug and clearing flags on attachment 396083 [details].
Antti Koivisto
Comment 22 2020-04-17 04:48:28 PDT
Thanks for the patch!
Alexey Shvayka
Comment 23 2020-04-21 16:55:45 PDT
(In reply to Darin Adler from comment #19) I appreciate the detailed write-up, thank you! I will make sure to update http/tests/xmlhttprequest/event-listener-gc.html as soon as I get better understanding of GC design. MQL seems to be kept alive by an event listener, same with other browsers: ``` ;(onchange => { mql = matchMedia("(min-width: 600px)") mql.onchange = onchange })(() => console.log("mql.onchange")) ``` > - Generally it's an anti-pattern to have a RefCounted object that is held in > a collection in another object that removes itself when it's destroyed. That is a great point, I will think through how we can decouple MediaQueryList from MediaQueryMatcher, or maybe set a flag in destructor and remove MQL from `matcher` during next evaluation round. (In reply to Antti Koivisto from comment #22) > Thanks for the patch! Thank you for reviewing & landing this!
Note You need to log in before you can comment on or make changes to this bug.