[Web Animations] Move Document.getAnimations() to DocumentOrShadowRoot
authorgraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 6 Apr 2020 17:41:43 +0000 (17:41 +0000)
committergraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 6 Apr 2020 17:41:43 +0000 (17:41 +0000)
https://bugs.webkit.org/show_bug.cgi?id=202192
<rdar://problem/55697775>

Reviewed by Antti Koivisto.

LayoutTests/imported/w3c:

Update the test relevant to DocumentOrShadowRoot.getAnimations() from upstream and record three new PASS results in it. We also get two new PASS from a harness test.

* web-platform-tests/web-animations/idlharness.window-expected.txt:
* web-platform-tests/web-animations/interfaces/DocumentOrShadowRoot/getAnimations-expected.txt:
* web-platform-tests/web-animations/interfaces/DocumentOrShadowRoot/getAnimations.html:
* web-platform-tests/web-animations/testcommon.js:
(async insertFrameAndAwaitLoad):

Source/WebCore:

We remove the getAnimations() declaration from the Document interface and instead move it on the DocumentOrShadowRoot interface.

We add the new method Document::matchingAnimations() which takes a lambda that is provided an animation's effect's target to determine whether
that animation should be found in the list of animations.

In the case of Document::getAnimations(), we filter out animations targeting elements hosted in shadow roots, while in ShadowRoot:getAnimations(),
we filter out animations targeting elements that are not hosted in the shadow root the method was called on.

* dom/Document.cpp:
(WebCore::Document::getAnimations):
(WebCore::Document::matchingAnimations):
* dom/Document.h:
* dom/Document.idl:
* dom/DocumentOrShadowRoot.idl:
* dom/Element.cpp:
(WebCore::Element::getAnimations):
* dom/ShadowRoot.cpp:
(WebCore::ShadowRoot::getAnimations):
* dom/ShadowRoot.h:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@259577 268f45cc-cd09-0410-ab3c-d52691b4dbfc

13 files changed:
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/web-animations/idlharness.window-expected.txt
LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/DocumentOrShadowRoot/getAnimations-expected.txt
LayoutTests/imported/w3c/web-platform-tests/web-animations/interfaces/DocumentOrShadowRoot/getAnimations.html
LayoutTests/imported/w3c/web-platform-tests/web-animations/testcommon.js
Source/WebCore/ChangeLog
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Document.h
Source/WebCore/dom/Document.idl
Source/WebCore/dom/DocumentOrShadowRoot.idl
Source/WebCore/dom/Element.cpp
Source/WebCore/dom/ShadowRoot.cpp
Source/WebCore/dom/ShadowRoot.h

index dac269f..cd8c252 100644 (file)
@@ -1,3 +1,19 @@
+2020-04-06  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Move Document.getAnimations() to DocumentOrShadowRoot
+        https://bugs.webkit.org/show_bug.cgi?id=202192
+        <rdar://problem/55697775>
+
+        Reviewed by Antti Koivisto.
+
+        Update the test relevant to DocumentOrShadowRoot.getAnimations() from upstream and record three new PASS results in it. We also get two new PASS from a harness test.
+
+        * web-platform-tests/web-animations/idlharness.window-expected.txt:
+        * web-platform-tests/web-animations/interfaces/DocumentOrShadowRoot/getAnimations-expected.txt:
+        * web-platform-tests/web-animations/interfaces/DocumentOrShadowRoot/getAnimations.html:
+        * web-platform-tests/web-animations/testcommon.js:
+        (async insertFrameAndAwaitLoad):
+
 2020-04-05  Manuel Rego Casasnovas  <rego@igalia.com>
 
         Computed style for "outline-offset" is wrong when "outline-style" is "none"
index df4cda3..cef2255 100644 (file)
@@ -140,8 +140,8 @@ PASS Document interface: attribute timeline
 PASS Document interface: operation getAnimations() 
 PASS Document interface: document must inherit property "timeline" with the proper type 
 PASS Document interface: document must inherit property "getAnimations()" with the proper type 
-FAIL ShadowRoot interface: operation getAnimations() assert_own_property: interface prototype object missing non-static operation expected property "getAnimations" missing
-FAIL ShadowRoot interface: shadowRoot must inherit property "getAnimations()" with the proper type assert_inherits: property "getAnimations" not found in prototype chain
+PASS ShadowRoot interface: operation getAnimations() 
+PASS ShadowRoot interface: shadowRoot must inherit property "getAnimations()" with the proper type 
 PASS Element interface: operation animate(object, [object Object],[object Object]) 
 PASS Element interface: operation getAnimations(GetAnimationsOptions) 
 
index 8b23a8d..26a6584 100644 (file)
@@ -5,7 +5,9 @@ PASS Document.getAnimations() returns script-generated animations in the order t
 PASS Document.getAnimations() does not return a disconnected node 
 PASS Document.getAnimations() does not return an animation with a null target 
 FAIL Document.getAnimations() returns animations on elements inside same-origin iframes assert_equals: expected 1 but got 0
-FAIL ShadowRoot.getAnimations() return all animations in the shadow tree div.shadowRoot.getAnimations is not a function. (In 'div.shadowRoot.getAnimations()', 'div.shadowRoot.getAnimations' is undefined)
-FAIL Document.getAnimations() does NOT return animations in shadow trees assert_array_equals: getAnimations() called on Document does not return animations from shadow trees lengths differ, expected 0 got 1
+FAIL iframe.contentDocument.getAnimations() returns animations on elements inside same-origin Document assert_equals: expected 1 but got 0
+PASS ShadowRoot.getAnimations() return all animations in the shadow tree 
+PASS Document.getAnimations() does NOT return animations in shadow trees 
+PASS ShadowRoot.getAnimations() does NOT return animations in parent document 
 PASS Document.getAnimations() triggers a style change event 
 
index 4730f18..9bcc042 100644 (file)
@@ -68,14 +68,7 @@ test(t => {
 
 promise_test(async t => {
   const iframe = document.createElement('iframe');
-
-  const eventWatcher = new EventWatcher(t, iframe, ['load']);
-  const event_promise = eventWatcher.wait_for('load');
-
-  document.body.appendChild(iframe);
-  t.add_cleanup(() => { document.body.removeChild(iframe); });
-
-  await event_promise;
+  await insertFrameAndAwaitLoad(t, iframe, document)
 
   const div = createDiv(t, iframe.contentDocument)
   const effect = new KeyframeEffect(div, null, 100 * MS_PER_SEC);
@@ -91,6 +84,52 @@ promise_test(async t => {
 }, 'Document.getAnimations() returns animations on elements inside same-origin'
    + ' iframes');
 
+promise_test(async t => {
+  const iframe1 = document.createElement('iframe');
+  const iframe2 = document.createElement('iframe');
+
+  await insertFrameAndAwaitLoad(t, iframe1, document);
+  await insertFrameAndAwaitLoad(t, iframe2, document);
+
+  const div_frame1 = createDiv(t, iframe1.contentDocument)
+  const div_main_frame = createDiv(t)
+  const effect1 = new KeyframeEffect(div_frame1, null, 100 * MS_PER_SEC);
+  const anim1 = new Animation(effect1, document.timeline);
+  anim1.play();
+  // Animation of div_frame1 is in iframe with main timeline.
+  // The animation's timeline is from the iframe, but the effect's target
+  // element is part of the iframe's document.
+  assert_equals(document.getAnimations().length, 0);
+  assert_equals(iframe1.contentDocument.getAnimations().length, 1);
+  anim1.finish();
+
+   // animation of div_frame1 in iframe1 with iframe timeline
+  const effect2 = new KeyframeEffect(div_frame1, null, 100 * MS_PER_SEC);
+  const anim2 = new Animation(effect2, iframe1.contentDocument.timeline);
+  anim2.play();
+  assert_equals(document.getAnimations().length, 0);
+  assert_equals(iframe1.contentDocument.getAnimations().length, 1);
+  anim2.finish();
+
+  //animation of div_main_frame in main frame with iframe timeline
+  const effect3 = new KeyframeEffect(div_main_frame, null, 100 * MS_PER_SEC);
+  const anim3 = new Animation(effect3, iframe1.contentDocument.timeline);
+  anim3.play();
+  assert_equals(document.getAnimations().length, 1);
+  assert_equals(iframe1.contentDocument.getAnimations().length, 0);
+  anim3.finish();
+
+  //animation of div_frame1 in iframe1 with another iframe's timeline
+  const effect4 = new KeyframeEffect(div_frame1, null, 100 * MS_PER_SEC);
+  const anim4 = new Animation(effect4, iframe2.contentDocument.timeline);
+  anim4.play();
+  assert_equals(document.getAnimations().length, 0);
+  assert_equals(iframe1.contentDocument.getAnimations().length, 1);
+  assert_equals(iframe2.contentDocument.getAnimations().length, 0);
+  anim4.finish();
+}, 'iframe.contentDocument.getAnimations() returns animations on elements '
+   + 'inside same-origin Document');
+
 test(t => {
   const div = createDiv(t);
   const shadow = div.attachShadow({ mode: 'open' });
@@ -149,6 +188,20 @@ test(t => {
   );
 }, 'Document.getAnimations() does NOT return animations in shadow trees');
 
+test(t => {
+  const div = createDiv(t);
+  const shadow = div.attachShadow({ mode: 'open' });
+
+  div.animate(gKeyFrames, 100 * MS_PER_SEC)
+
+  assert_array_equals(
+    div.shadowRoot.getAnimations(),
+    [],
+    'getAnimations() called on ShadowRoot does not return animations from'
+    + ' Document'
+  );
+}, 'ShadowRoot.getAnimations() does NOT return animations in parent document');
+
 promise_test(async t => {
   const div = createDiv(t);
   const watcher = EventWatcher(t, div, 'transitionrun');
index f89cbba..1eb24f6 100644 (file)
@@ -180,6 +180,16 @@ function waitForNextFrame() {
   });
 }
 
+async function insertFrameAndAwaitLoad(test, iframe, doc) {
+  const eventWatcher = new EventWatcher(test, iframe, ['load']);
+  const event_promise = eventWatcher.wait_for('load');
+
+  doc.body.appendChild(iframe);
+  test.add_cleanup(() => { doc.body.removeChild(iframe); });
+
+  await event_promise;
+}
+
 // Returns 'matrix()' or 'matrix3d()' function string generated from an array.
 function createMatrixFromArray(array) {
   return (array.length == 16 ? 'matrix3d' : 'matrix') + `(${array.join()})`;
index 0ac6ad9..76c81dd 100644 (file)
@@ -1,3 +1,31 @@
+2020-04-06  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Move Document.getAnimations() to DocumentOrShadowRoot
+        https://bugs.webkit.org/show_bug.cgi?id=202192
+        <rdar://problem/55697775>
+
+        Reviewed by Antti Koivisto.
+
+        We remove the getAnimations() declaration from the Document interface and instead move it on the DocumentOrShadowRoot interface.
+
+        We add the new method Document::matchingAnimations() which takes a lambda that is provided an animation's effect's target to determine whether
+        that animation should be found in the list of animations.
+
+        In the case of Document::getAnimations(), we filter out animations targeting elements hosted in shadow roots, while in ShadowRoot:getAnimations(),
+        we filter out animations targeting elements that are not hosted in the shadow root the method was called on.
+
+        * dom/Document.cpp:
+        (WebCore::Document::getAnimations):
+        (WebCore::Document::matchingAnimations):
+        * dom/Document.h:
+        * dom/Document.idl:
+        * dom/DocumentOrShadowRoot.idl:
+        * dom/Element.cpp:
+        (WebCore::Element::getAnimations):
+        * dom/ShadowRoot.cpp:
+        (WebCore::ShadowRoot::getAnimations):
+        * dom/ShadowRoot.h:
+
 2020-04-04  Darin Adler  <darin@apple.com>
 
         Stop using live ranges in DocumentMarkerController
index 2cacc5d..8521692 100644 (file)
@@ -8103,13 +8103,30 @@ DocumentTimeline& Document::timeline()
 
 Vector<RefPtr<WebAnimation>> Document::getAnimations()
 {
+    return matchingAnimations([] (Element& target) -> bool {
+        return !target.containingShadowRoot();
+    });
+}
+
+Vector<RefPtr<WebAnimation>> Document::matchingAnimations(const WTF::Function<bool(Element&)>& function)
+{
     // For the list of animations to be current, we need to account for any pending CSS changes,
     // such as updates to CSS Animations and CSS Transitions.
     updateStyleIfNeeded();
 
-    if (m_timeline)
-        return m_timeline->getAnimations();
-    return { };
+    if (!m_timeline)
+        return { };
+
+    Vector<RefPtr<WebAnimation>> animations;
+    for (auto& animation : m_timeline->getAnimations()) {
+        auto* effect = animation->effect();
+        ASSERT(is<KeyframeEffect>(animation->effect()));
+        auto* target = downcast<KeyframeEffect>(*effect).target();
+        ASSERT(target);
+        if (function(*target))
+            animations.append(animation);
+    }
+    return animations;
 }
 
 #if ENABLE(ATTACHMENT_ELEMENT)
index 839afcd..ebbfc52 100644 (file)
@@ -1485,7 +1485,8 @@ public:
     WEBCORE_EXPORT DocumentTimeline& timeline();
     DocumentTimeline* existingTimeline() const { return m_timeline.get(); }
     Vector<RefPtr<WebAnimation>> getAnimations();
-        
+    Vector<RefPtr<WebAnimation>> matchingAnimations(const WTF::Function<bool(Element&)>&);
+
 #if ENABLE(ATTACHMENT_ELEMENT)
     void registerAttachmentIdentifier(const String&);
     void didInsertAttachmentElement(HTMLAttachmentElement&);
index ce3dee0..7bd055c 100644 (file)
@@ -190,7 +190,6 @@ typedef (
     [Replaceable] readonly attribute HTMLAllCollection all; /* [SameObject] */
 
     [EnabledAtRuntime=WebAnimations] readonly attribute DocumentTimeline timeline;
-    [EnabledAtRuntime=WebAnimations] sequence<WebAnimation> getAnimations();
 };
 
 enum DocumentReadyState { "loading", "interactive", "complete" };
index b9b23f1..386362a 100644 (file)
@@ -43,4 +43,7 @@
 
     // Extensions from Picture-in-Picture API (https://wicg.github.io/picture-in-picture/#documentorshadowroot-extension)
     [Conditional=PICTURE_IN_PICTURE_API, EnabledBySetting=PictureInPictureAPI] readonly attribute Element? pictureInPictureElement;
+
+    // Extension from Web Animations API (https://drafts.csswg.org/web-animations-1/#extensions-to-the-documentorshadowroot-interface-mixin)
+    [EnabledAtRuntime=WebAnimations] sequence<WebAnimation> getAnimations();
 };
index 5c291d0..bd9b0b7 100644 (file)
@@ -4534,19 +4534,11 @@ Vector<RefPtr<WebAnimation>> Element::getAnimations(Optional<GetAnimationsOption
     // animations targeting that are not registered on this element, one of its pseudo elements or a child's
     // pseudo element.
     if (options && options->subtree) {
-        Vector<RefPtr<WebAnimation>> animations;
-        for (auto& animation : document().getAnimations()) {
-            auto* effect = animation->effect();
-            ASSERT(is<KeyframeEffect>(animation->effect()));
-            auto* target = downcast<KeyframeEffect>(*effect).target();
-            ASSERT(target);
-            if (is<PseudoElement>(target)) {
-                if (contains(downcast<PseudoElement>(*target).hostElement()))
-                    animations.append(animation);
-            } else if (contains(target))
-                animations.append(animation);
-        }
-        return animations;
+        return document().matchingAnimations([&] (Element& target) -> bool {
+            if (is<PseudoElement>(target))
+                return contains(downcast<PseudoElement>(target).hostElement());
+            return contains(&target);
+        });
     }
 
     // For the list of animations to be current, we need to account for any pending CSS changes,
index 6f4f4c4..3fc2e34 100644 (file)
@@ -367,4 +367,11 @@ HTMLVideoElement* ShadowRoot::pictureInPictureElement() const
 }
 #endif
 
+Vector<RefPtr<WebAnimation>> ShadowRoot::getAnimations()
+{
+    return document().matchingAnimations([&] (Element& target) -> bool {
+        return target.containingShadowRoot() == this;
+    });
+}
+
 }
index b0c9dc1..dabe27f 100644 (file)
@@ -40,6 +40,7 @@ namespace WebCore {
 class HTMLSlotElement;
 class SlotAssignment;
 class StyleSheetList;
+class WebAnimation;
 
 class ShadowRoot final : public DocumentFragment, public TreeScope {
     WTF_MAKE_ISO_ALLOCATED(ShadowRoot);
@@ -111,6 +112,8 @@ public:
     HTMLVideoElement* pictureInPictureElement() const;
 #endif
 
+    Vector<RefPtr<WebAnimation>> getAnimations();
+
 private:
     ShadowRoot(Document&, ShadowRootMode, DelegatesFocus);
     ShadowRoot(Document&, std::unique_ptr<SlotAssignment>&&);