Bug 1973726 - Set DOM file path for webkitRelativePath. r=sefeng,geckoview-reviewers,webidl,smaug,ohall
Actually, there is no way to set webkitRelativePath from JavaScript.
Since GeckoView's folder picker handles virtual file data, we need to
set relative path from JavaScript directly.
A content URI of System storage document provider only allow file data
access from file/folder picker, so there is no way to test it on
geckoivew-junit.
Also, after landing bug 1591640, GVE always crash when using folder
picker. So this includes a fix for it.
Differential Revision: https://phabricator.services.mozilla.com/D255615
<!doctype html><head><metaname="viewport"content="width=device-width,minimum-scale=1,initial-scale=1"><metacharset=utf-8><title>Tests restyles caused by animations</title><script>constok=opener.ok.bind(opener);constis=opener.is.bind(opener);consttodo=opener.todo.bind(opener);consttodo_is=opener.todo_is.bind(opener);constinfo=opener.info.bind(opener);constoriginal_finish=opener.SimpleTest.finish;constSimpleTest=opener.SimpleTest;constadd_task=opener.add_task;SimpleTest.finish=functionfinish(){self.close();original_finish();}</script><scriptsrc="/tests/SimpleTest/EventUtils.js"></script><scriptsrc="/tests/SimpleTest/paint_listener.js"></script><scriptsrc="../testcommon.js"></script><linkrel="stylesheet"type="text/css"href="/tests/SimpleTest/test.css"><style>@keyframesbackground-position{0%{background-position:-25pxcenter;}40%,100%{background-position:36pxcenter;}}@keyframesopacity{from{opacity:1;}to{opacity:0;}}@keyframesopacity-from-zero{from{opacity:0;}to{opacity:1;}}@keyframesopacity-without-end-value{from{opacity:0;}}@keyframeson-main-thread{from{z-index:0;}to{z-index:999;}}@keyframesrotate{from{transform:rotate(0deg);}to{transform:rotate(360deg);}}@keyframesmove-in{from{transform:translate(120%,120%);}to{transform:translate(0%,0%);}}@keyframesbackground-color{from{background-color:rgb(255,0,0,);}to{background-color:rgb(0,255,0,);}}div{/* Element needs geometry to be eligible for layerization */width:100px;height:100px;background-color:white;}progress:not(.stop)::-moz-progress-bar{animation:on-main-thread100s;}body{/* * set overflow:hidden to avoid accidentally unthrottling animations to update * the overflow region. */overflow:hidden;}</style></head><body><script>'use strict';// Returns observed animation restyle markers when |funcToMakeRestyleHappen|// is called.// NOTE: This function is synchronous version of the above observeStyling().// Unlike the above observeStyling, this function takes a callback function,// |funcToMakeRestyleHappen|, which may be expected to trigger a synchronous// restyles, and returns any restyle markers produced by calling that function.functionobserveAnimSyncStyling(funcToMakeRestyleHappen){letpriorAnimationTriggeredRestyles=SpecialPowers.DOMWindowUtils.animationTriggeredRestyles;funcToMakeRestyleHappen();constrestyleCount=SpecialPowers.DOMWindowUtils.animationTriggeredRestyles-priorAnimationTriggeredRestyles;returnrestyleCount;}functionensureElementRemoval(aElement){returnnewPromise(resolve=>{aElement.remove();waitForAllPaintsFlushed(resolve);});}functionwaitForWheelEvent(aTarget){returnnewPromise(resolve=>{// Get the scrollable target element position in this window coordinate// system to send a wheel event to the element.consttargetRect=aTarget.getBoundingClientRect();constcenterX=targetRect.left+targetRect.width/2;constcenterY=targetRect.top+targetRect.height/2;sendWheelAndPaintNoFlush(aTarget,centerX,centerY,{deltaMode:WheelEvent.DOM_DELTA_PIXEL,deltaY:targetRect.height},resolve);});}constomtaEnabled=isOMTAEnabled();functionadd_task_if_omta_enabled(test){if(!omtaEnabled){info(test.name+" is skipped because OMTA is disabled");return;}add_task(test);}asyncfunctionestimateVsyncRate(){awaitwaitForNextFrame();consttimeAtStart=document.timeline.currentTime;awaitwaitForAnimationFrames(5);return(document.timeline.currentTime-timeAtStart)/5;}// We need to wait for all paints before running tests to avoid contaminations// from styling of this document itself.waitForAllPaints(async()=>{constvsyncRate=awaitestimateVsyncRate();// In this test we basically observe restyling counts in 5 frames, if it// takes over 200ms during the 5 frames, this test will fail. So// "200ms / 5 = 40ms" is a threshold whether the test works as expected or// not. We'd take 5ms additional tolerance here.// Note that the 200ms is a period we unthrottle throttled animations that// at least one of the animating styles produces change hints causing// overflow, the value is defined in// KeyframeEffect::OverflowRegionRefreshInterval.if(vsyncRate>40-5){ok(true,`the vsync rate ${vsyncRate} on this machine is too slow to run this test`);SimpleTest.finish();return;}add_task(asyncfunctionrestyling_for_main_thread_animations(){constdiv=addDiv(null,{style:'animation: on-main-thread 100s'});constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);constrestyleCount=awaitobserveStyling(5);is(restyleCount,5,'CSS animations running on the main-thread should update style '+'on the main thread');awaitensureElementRemoval(div);});add_task(asyncfunctionrestyling_for_main_thread_animations_progress_bar_pseudo(){constprogress=document.createElement("progress");document.body.appendChild(progress);awaitwaitForNextFrame();awaitwaitForNextFrame();letrestyleCount;restyleCount=awaitobserveStyling(5);// TODO(bug 1784931): Figure out why we only see four markers sometimes.// That's not the point of this test tho.letmaybe_todo_is=restyleCount==4?todo_is:is;maybe_todo_is(restyleCount,5,'CSS animations running on the main-thread should update style '+'on the main thread on ::-moz-progress-bar');progress.classList.add("stop");awaitwaitForNextFrame();awaitwaitForNextFrame();restyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animation is correctly removed');awaitensureElementRemoval(progress);});add_task_if_omta_enabled(asyncfunctionno_restyling_for_compositor_animations(){constdiv=addDiv(null,{style:'animation: opacity 100s'});constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);ok(SpecialPowers.wrap(animation).isRunningOnCompositor);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'CSS animations running on the compositor should not update style '+'on the main thread');awaitensureElementRemoval(div);});add_task_if_omta_enabled(asyncfunctionno_restyling_for_compositor_transitions(){constdiv=addDiv(null,{style:'transition: opacity 100s; opacity: 0'});getComputedStyle(div).opacity;div.style.opacity=1;constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);ok(SpecialPowers.wrap(animation).isRunningOnCompositor);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'CSS transitions running on the compositor should not update style '+'on the main thread');awaitensureElementRemoval(div);});add_task_if_omta_enabled(asyncfunctionno_restyling_when_animation_duration_is_changed(){constdiv=addDiv(null,{style:'animation: opacity 100s'});constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);ok(SpecialPowers.wrap(animation).isRunningOnCompositor);div.animationDuration='200s';constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animations running on the compositor should not update style '+'on the main thread');awaitensureElementRemoval(div);});add_task_if_omta_enabled(asyncfunctiononly_one_restyling_after_finish_is_called(){constdiv=addDiv(null,{style:'animation: opacity 100s'});constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);ok(SpecialPowers.wrap(animation).isRunningOnCompositor);animation.finish();letrestyleCount;restyleCount=awaitobserveStyling(1);is(restyleCount,1,'Animations running on the compositor should only update style once '+'after finish() is called');restyleCount=awaitobserveStyling(1);todo_is(restyleCount,0,'Bug 1415457: Animations running on the compositor should only '+'update style once after finish() is called');restyleCount=awaitobserveStyling(5);is(restyleCount,0,'Finished animations should never update style after one '+'restyle happened for finish()');awaitensureElementRemoval(div);});add_task(asyncfunctionno_restyling_mouse_movement_on_finished_transition(){constdiv=addDiv(null,{style:'transition: opacity 1ms; opacity: 0'});getComputedStyle(div).opacity;div.style.opacity=1;constanimation=div.getAnimations()[0];constinitialRect=div.getBoundingClientRect();awaitanimation.finished;letrestyleCount;restyleCount=awaitobserveStyling(1);is(restyleCount,1,'Finished transitions should restyle once after Animation.finished '+'was fulfilled');letmouseX=initialRect.left+initialRect.width/2;letmouseY=initialRect.top+initialRect.height/2;restyleCount=awaitobserveStyling(5,()=>{// We can't use synthesizeMouse here since synthesizeMouse causes// layout flush.synthesizeMouseAtPoint(mouseX++,mouseY++,{type:'mousemove'},window);});is(restyleCount,0,'Finished transitions should never cause restyles when mouse is moved '+'on the transitions');awaitensureElementRemoval(div);});add_task(asyncfunctionno_restyling_mouse_movement_on_finished_animation(){constdiv=addDiv(null,{style:'animation: opacity 1ms'});constanimation=div.getAnimations()[0];constinitialRect=div.getBoundingClientRect();awaitanimation.finished;letrestyleCount;restyleCount=awaitobserveStyling(1);is(restyleCount,1,'Finished animations should restyle once after Animation.finished '+'was fulfilled');letmouseX=initialRect.left+initialRect.width/2;letmouseY=initialRect.top+initialRect.height/2;restyleCount=awaitobserveStyling(5,()=>{// We can't use synthesizeMouse here since synthesizeMouse causes// layout flush.synthesizeMouseAtPoint(mouseX++,mouseY++,{type:'mousemove'},window);});is(restyleCount,0,'Finished animations should never cause restyles when mouse is moved '+'on the animations');awaitensureElementRemoval(div);});add_task_if_omta_enabled(asyncfunctionno_restyling_compositor_animations_out_of_view_element(){constdiv=addDiv(null,{style:'animation: opacity 100s; transform: translateY(-400px);'});constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animations running on the compositor in an out-of-view element '+'should never cause restyles');awaitensureElementRemoval(div);});add_task(asyncfunctionno_restyling_main_thread_animations_out_of_view_element(){constdiv=addDiv(null,{style:'animation: on-main-thread 100s; transform: translateY(-400px);'});constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animations running on the main-thread in an out-of-view element '+'should never cause restyles');awaitensureElementRemoval(div);});add_task_if_omta_enabled(asyncfunctionno_restyling_compositor_animations_in_scrolled_out_element(){constparentElement=addDiv(null,{style:'overflow-y: scroll; height: 20px;'});constdiv=addDiv(null,{style:'animation: opacity 100s; position: relative; top: 100px;'});parentElement.appendChild(div);constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animations running on the compositor for elements '+'which are scrolled out should never cause restyles');awaitensureElementRemoval(parentElement);});add_task(asyncfunctionno_restyling_missing_keyframe_opacity_animations_on_scrolled_out_element(){constparentElement=addDiv(null,{style:'overflow-y: scroll; height: 20px;'});constdiv=addDiv(null,{style:'animation: opacity-without-end-value 100s; '+'position: relative; top: 100px;'});parentElement.appendChild(div);constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Opacity animations on scrolled out elements should never cause '+'restyles even if the animation has missing keyframes');awaitensureElementRemoval(parentElement);});add_task(asyncfunctionrestyling_transform_animations_in_scrolled_out_element(){// Make sure we start from the state right after requestAnimationFrame.awaitwaitForFrame();constparentElement=addDiv(null,{style:'overflow-y: scroll; height: 20px;'});constdiv=addDiv(null,{style:'animation: rotate 100s infinite; position: relative; top: 100px;'});parentElement.appendChild(div);constanimation=div.getAnimations()[0];lettimeAtStart=document.timeline.currentTime;ok(!animation.isRunningOnCompositor,'The transform animation is not running on the compositor');letrestyleCountletnow;letelapsed;while(true){now=document.timeline.currentTime;elapsed=(now-timeAtStart);restyleCount=awaitobserveStyling(1);if(restyleCount){break;}}// If the current time has elapsed over 200ms since the animation was// created, it means that the animation should have already// unthrottled in this tick, let's see what we observe in this tick's// restyling process.// We use toPrecision here and below so 199.99999999999977 will turn into 200.ok(elapsed.toPrecision(10)>=200,'Transform animation running on the element which is scrolled out '+'should be throttled until 200ms is elapsed. now: '+now+' start time: '+timeAtStart+' elapsed:'+elapsed);timeAtStart=document.timeline.currentTime;restyleCount=awaitobserveStyling(1);now=document.timeline.currentTime;elapsed=(now-timeAtStart);letexpectedMarkersLengthValid;// On the fence of 200 ms, we probably have 1 marker; but if we hit a bad rounding// we might still have 0. But if it's > 200, we should have 1; and less we should have 0.if(elapsed.toPrecision(10)==200)expectedMarkersLengthValid=restyleCount<2;elseif(elapsed.toPrecision(10)>200)expectedMarkersLengthValid=restyleCount==1;elseexpectedMarkersLengthValid=!restyleCount;ok(expectedMarkersLengthValid,'Transform animation running on the element which is scrolled out '+'should be unthrottled after around 200ms have elapsed. now: '+now+' start time: '+timeAtStart+' elapsed: '+elapsed);awaitensureElementRemoval(parentElement);});add_task(asyncfunctionrestyling_out_of_view_transform_animations_in_another_element(){// Make sure we start from the state right after requestAnimationFrame.awaitwaitForFrame();constparentElement=addDiv(null,{style:'overflow: hidden;'});constdiv=addDiv(null,{style:'animation: move-in 100s infinite;'});parentElement.appendChild(div);constanimation=div.getAnimations()[0];lettimeAtStart=document.timeline.currentTime;ok(!animation.isRunningOnCompositor,'The transform animation on out of view element '+'is not running on the compositor');// Structure copied from restyling_transform_animations_in_scrolled_out_elementletrestyleCountletnow;letelapsed;while(true){now=document.timeline.currentTime;elapsed=(now-timeAtStart);restyleCount=awaitobserveStyling(1);if(restyleCount){break;}}ok(elapsed.toPrecision(10)>=200,'Transform animation running on out of view element '+'should be throttled until 200ms is elapsed. now: '+now+' start time: '+timeAtStart+' elapsed:'+elapsed);timeAtStart=document.timeline.currentTime;restyleCount=awaitobserveStyling(1);now=document.timeline.currentTime;elapsed=(now-timeAtStart);letexpectedMarkersLengthValid;// On the fence of 200 ms, we probably have 1 marker; but if we hit a bad rounding// we might still have 0. But if it's > 200, we should have 1; and less we should have 0.if(elapsed.toPrecision(10)==200)expectedMarkersLengthValid=restyleCount<2;elseif(elapsed.toPrecision(10)>200)expectedMarkersLengthValid=restyleCount==1;elseexpectedMarkersLengthValid=!restyleCount;ok(expectedMarkersLengthValid,'Transform animation running on out of view element '+'should be unthrottled after around 200ms have elapsed. now: '+now+' start time: '+timeAtStart+' elapsed: '+elapsed);awaitensureElementRemoval(parentElement);});add_task(asyncfunctionfinite_transform_animations_in_out_of_view_element(){constparentElement=addDiv(null,{style:'overflow: hidden;'});constdiv=addDiv(null);constanimation=div.animate({transform:['translateX(120%)','translateX(100%)']},// This animation will move a bit but// will remain out-of-view.100*MS_PER_SEC);parentElement.appendChild(div);awaitwaitForAnimationReadyToRestyle(animation);ok(!SpecialPowers.wrap(animation).isRunningOnCompositor,"Should not be running in compositor");constrestyleCount=awaitobserveStyling(20);is(restyleCount,20,'Finite transform animation in out-of-view element should never be '+'throttled');awaitensureElementRemoval(parentElement);});add_task(asyncfunctionrestyling_main_thread_animations_in_scrolled_out_element(){constparentElement=addDiv(null,{style:'overflow-y: scroll; height: 20px;'});constdiv=addDiv(null,{style:'animation: on-main-thread 100s; position: relative; top: 20px;'});parentElement.appendChild(div);constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);letrestyleCount;restyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animations running on the main-thread for elements '+'which are scrolled out should never cause restyles');awaitwaitForWheelEvent(parentElement);// Make sure we are ready to restyle before counting restyles.awaitwaitForFrame();restyleCount=awaitobserveStyling(5);is(restyleCount,5,'Animations running on the main-thread which were in scrolled out '+'elements should update restyling soon after the element moved in '+'view by scrolling');awaitensureElementRemoval(parentElement);});add_task(asyncfunctionrestyling_main_thread_animations_in_nested_scrolled_out_element(){constgrandParent=addDiv(null,{style:'overflow-y: scroll; height: 20px;'});constparentElement=addDiv(null,{style:'overflow-y: scroll; height: 100px;'});constdiv=addDiv(null,{style:'animation: on-main-thread 100s; '+'position: relative; '+'top: 20px;'});// This element is in-view in the parent, but// out of view in the grandparent.grandParent.appendChild(parentElement);parentElement.appendChild(div);constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);letrestyleCount;restyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animations running on the main-thread which are in nested elements '+'which are scrolled out should never cause restyles');awaitwaitForWheelEvent(grandParent);awaitwaitForFrame();restyleCount=awaitobserveStyling(5);is(restyleCount,5,'Animations running on the main-thread which were in nested scrolled '+'out elements should update restyle soon after the element moved '+'in view by scrolling');awaitensureElementRemoval(grandParent);});add_task_if_omta_enabled(asyncfunctionno_restyling_compositor_animations_in_visibility_hidden_element(){constdiv=addDiv(null,{style:'animation: opacity 100s; visibility: hidden'});constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animations running on the compositor in visibility hidden element '+'should never cause restyles');awaitensureElementRemoval(div);});add_task(asyncfunctionrestyling_main_thread_animations_move_out_of_view_by_scrolling(){constparentElement=addDiv(null,{style:'overflow-y: scroll; height: 200px;'});constdiv=addDiv(null,{style:'animation: on-main-thread 100s;'});constpad=addDiv(null,{style:'height: 400px;'});parentElement.appendChild(div);parentElement.appendChild(pad);constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);awaitwaitForWheelEvent(parentElement);awaitwaitForFrame();constrestyleCount=awaitobserveStyling(5);// FIXME: We should reduce a redundant restyle here.ok(restyleCount>=0,'Animations running on the main-thread which are in scrolled out '+'elements should throttle restyling');awaitensureElementRemoval(parentElement);});add_task(asyncfunctionrestyling_main_thread_animations_moved_in_view_by_resizing(){constparentElement=addDiv(null,{style:'overflow-y: scroll; height: 20px;'});constdiv=addDiv(null,{style:'animation: on-main-thread 100s; position: relative; top: 100px;'});parentElement.appendChild(div);constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);letrestyleCount;restyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animations running on the main-thread which is in scrolled out '+'elements should not update restyling');parentElement.style.height='100px';restyleCount=awaitobserveStyling(1);is(restyleCount,1,'Animations running on the main-thread which was in scrolled out '+'elements should update restyling soon after the element moved in '+'view by resizing');awaitensureElementRemoval(parentElement);});add_task(asyncfunctionrestyling_animations_on_visibility_changed_element_having_child(){constdiv=addDiv(null,{style:'animation: on-main-thread 100s;'});constchildElement=addDiv(null);div.appendChild(childElement);constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);// We don't check the animation causes restyles here since we already// check it in the first test case.div.style.visibility='hidden';awaitwaitForNextFrame();constrestyleCount=awaitobserveStyling(5);todo_is(restyleCount,0,'Animations running on visibility hidden element which '+'has a child whose visiblity is inherited from the element and '+'the element was initially visible');awaitensureElementRemoval(div);});add_task(asyncfunctionrestyling_animations_on_visibility_hidden_element_which_gets_visible(){constdiv=addDiv(null,{style:'animation: on-main-thread 100s; visibility: hidden'});constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);letrestyleCount;restyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animations running on visibility hidden element should never '+'cause restyles');div.style.visibility='visible';awaitwaitForNextFrame();restyleCount=awaitobserveStyling(5);is(restyleCount,5,'Animations running that was on visibility hidden element which '+'gets visible should not throttle restyling any more');awaitensureElementRemoval(div);});add_task(asyncfunctionrestyling_animations_in_visibility_changed_parent(){constparentDiv=addDiv(null,{style:'visibility: hidden'});constdiv=addDiv(null,{style:'animation: on-main-thread 100s;'});parentDiv.appendChild(div);constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);letrestyleCount;restyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animations running in visibility hidden parent should never cause '+'restyles');parentDiv.style.visibility='visible';awaitwaitForNextFrame();restyleCount=awaitobserveStyling(5);is(restyleCount,5,'Animations that was in visibility hidden parent should not '+'throttle restyling any more');parentDiv.style.visibility='hidden';awaitwaitForNextFrame();restyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animations that the parent element became visible should throttle '+'restyling again');awaitensureElementRemoval(parentDiv);});add_task(asyncfunctionrestyling_animations_on_visibility_hidden_element_with_visibility_changed_children(){constdiv=addDiv(null,{style:'animation: on-main-thread 100s; visibility: hidden'});constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);letrestyleCount;restyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animations on visibility hidden element having no visible children '+'should never cause restyles');constchildElement=addDiv(null,{style:'visibility: visible'});div.appendChild(childElement);awaitwaitForNextFrame();restyleCount=awaitobserveStyling(5);is(restyleCount,5,'Animations running on visibility hidden element but the element has '+'a visible child should not throttle restyling');childElement.style.visibility='hidden';awaitwaitForNextFrame();restyleCount=awaitobserveStyling(5);todo_is(restyleCount,0,'Animations running on visibility hidden element that a child '+'has become invisible should throttle restyling');childElement.style.visibility='visible';awaitwaitForNextFrame();restyleCount=awaitobserveStyling(5);is(restyleCount,5,'Animations running on visibility hidden element should not throttle '+'restyling after the invisible element changed to visible');childElement.remove();awaitwaitForNextFrame();restyleCount=awaitobserveStyling(5);todo_is(restyleCount,0,'Animations running on visibility hidden element should throttle '+'restyling again after all visible descendants were removed');awaitensureElementRemoval(div);});add_task(asyncfunctionrestyling_animations_on_visiblity_hidden_element_having_oof_child(){constdiv=addDiv(null,{style:'animation: on-main-thread 100s; position: absolute'});constchildElement=addDiv(null,{style:'float: left; visibility: hidden'});div.appendChild(childElement);constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);// We don't check the animation causes restyles here since we already// check it in the first test case.div.style.visibility='hidden';awaitwaitForNextFrame();constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animations running on visibility hidden element which has an '+'out-of-flow child should throttle restyling');awaitensureElementRemoval(div);});add_task(asyncfunctionrestyling_animations_on_visibility_hidden_element_having_grandchild(){// element tree://// root(visibility:hidden)// / \// childA childB// / \ / \// AA AB BA BBconstdiv=addDiv(null,{style:'animation: on-main-thread 100s; visibility: hidden'});constchildA=addDiv(null);div.appendChild(childA);constchildB=addDiv(null);div.appendChild(childB);constgrandchildAA=addDiv(null);childA.appendChild(grandchildAA);constgrandchildAB=addDiv(null);childA.appendChild(grandchildAB);constgrandchildBA=addDiv(null);childB.appendChild(grandchildBA);constgrandchildBB=addDiv(null);childB.appendChild(grandchildBB);constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);letrestyleCount;restyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animations on visibility hidden element having no visible '+'descendants should never cause restyles');childA.style.visibility='visible';grandchildAA.style.visibility='visible';grandchildAB.style.visibility='visible';awaitwaitForNextFrame();restyleCount=awaitobserveStyling(5);is(restyleCount,5,'Animations running on visibility hidden element but the element has '+'visible children should not throttle restyling');// Make childA hidden again but both of grandchildAA and grandchildAB are// still visible.childA.style.visibility='hidden';awaitwaitForNextFrame();restyleCount=awaitobserveStyling(5);is(restyleCount,5,'Animations running on visibility hidden element that a child has '+'become invisible again but there are still visible children should '+'not throttle restyling');// Make grandchildAA hidden but grandchildAB is still visible.grandchildAA.style.visibility='hidden';awaitwaitForNextFrame();restyleCount=awaitobserveStyling(5);is(restyleCount,5,'Animations running on visibility hidden element that a grandchild '+'become invisible again but another grandchild is still visible '+'should not throttle restyling');// Make childB and grandchildBA visible.childB.style.visibility='visible';grandchildBA.style.visibility='visible';awaitwaitForNextFrame();restyleCount=awaitobserveStyling(5);is(restyleCount,5,'Animations running on visibility hidden element but the element has '+'visible descendants should not throttle restyling');// Make childB hidden but grandchildAB and grandchildBA are still visible.childB.style.visibility='hidden';awaitwaitForNextFrame();restyleCount=awaitobserveStyling(5);is(restyleCount,5,'Animations running on visibility hidden element but the element has '+'visible grandchildren should not throttle restyling');// Make grandchildAB hidden but grandchildBA is still visible.grandchildAB.style.visibility='hidden';awaitwaitForNextFrame();restyleCount=awaitobserveStyling(5);is(restyleCount,5,'Animations running on visibility hidden element but the element has '+'a visible grandchild should not throttle restyling');// Make grandchildBA hidden. Now all descedants are invisible.grandchildBA.style.visibility='hidden';awaitwaitForNextFrame();restyleCount=awaitobserveStyling(5);todo_is(restyleCount,0,'Animations on visibility hidden element that all descendants have '+'become invisible again should never cause restyles');// Make childB visible.childB.style.visibility='visible';awaitwaitForNextFrame();restyleCount=awaitobserveStyling(5);is(restyleCount,5,'Animations on visibility hidden element that has a visible child '+'should never cause restyles');// Make childB invisible againchildB.style.visibility='hidden';awaitwaitForNextFrame();restyleCount=awaitobserveStyling(5);todo_is(restyleCount,0,'Animations on visibility hidden element that the visible child '+'has become invisible again should never cause restyles');awaitensureElementRemoval(div);});add_task_if_omta_enabled(asyncfunctionno_restyling_compositor_animations_after_pause_is_called(){constdiv=addDiv(null,{style:'animation: opacity 100s'});constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);ok(SpecialPowers.wrap(animation).isRunningOnCompositor);animation.pause();awaitanimation.ready;letrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Paused animations running on the compositor should never cause '+'restyles');awaitensureElementRemoval(div);});add_task(asyncfunctionno_restyling_main_thread_animations_after_pause_is_called(){constdiv=addDiv(null,{style:'animation: on-main-thread 100s'});constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);animation.pause();awaitanimation.ready;letrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Paused animations running on the main-thread should never cause '+'restyles');awaitensureElementRemoval(div);});add_task_if_omta_enabled(asyncfunctiononly_one_restyling_when_current_time_is_set_to_middle_of_duration(){constdiv=addDiv(null,{style:'animation: opacity 100s'});constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);animation.currentTime=50*MS_PER_SEC;constrestyleCount=awaitobserveStyling(5);is(restyleCount,1,'Bug 1235478: Animations running on the compositor should only once '+'update style when currentTime is set to middle of duration time');awaitensureElementRemoval(div);});add_task_if_omta_enabled(asyncfunctionchange_duration_and_currenttime(){constdiv=addDiv(null);constanimation=div.animate({opacity:[0,1]},100*MS_PER_SEC);awaitwaitForAnimationReadyToRestyle(animation);ok(SpecialPowers.wrap(animation).isRunningOnCompositor);// Set currentTime to a time longer than duration.animation.currentTime=500*MS_PER_SEC;// Now the animation immediately get back from compositor.ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);// Extend the duration.animation.effect.updateTiming({duration:800*MS_PER_SEC});constrestyleCount=awaitobserveStyling(5);is(restyleCount,1,'Animations running on the compositor should update style '+'when duration is made longer than the current time');awaitensureElementRemoval(div);});add_task(asyncfunctionscript_animation_on_display_none_element(){constdiv=addDiv(null);constanimation=div.animate({zIndex:['0','999']},100*MS_PER_SEC);awaitwaitForAnimationReadyToRestyle(animation);div.style.display='none';// We need to wait a frame to apply display:none style.awaitwaitForNextFrame();is(animation.playState,'running','Script animations keep running even when the target element has '+'"display: none" style');ok(!SpecialPowers.wrap(animation).isRunningOnCompositor,'Script animations on "display:none" element should not run on the '+'compositor');letrestyleCount;restyleCount=awaitobserveStyling(5);is(restyleCount,0,'Script animations on "display: none" element should not update styles');div.style.display='';restyleCount=awaitobserveStyling(5);is(restyleCount,5,'Script animations restored from "display: none" state should update '+'styles');awaitensureElementRemoval(div);});add_task_if_omta_enabled(asyncfunctioncompositable_script_animation_on_display_none_element(){constdiv=addDiv(null);constanimation=div.animate({opacity:[0,1]},100*MS_PER_SEC);awaitwaitForAnimationReadyToRestyle(animation);div.style.display='none';// We need to wait a frame to apply display:none style.awaitwaitForNextFrame();is(animation.playState,'running','Opacity script animations keep running even when the target element '+'has "display: none" style');ok(!SpecialPowers.wrap(animation).isRunningOnCompositor,'Opacity script animations on "display:none" element should not '+'run on the compositor');letrestyleCount;restyleCount=awaitobserveStyling(5);is(restyleCount,0,'Opacity script animations on "display: none" element should not '+'update styles');div.style.display='';restyleCount=awaitobserveStyling(1);is(restyleCount,1,'Script animations restored from "display: none" state should update '+'styles soon');ok(SpecialPowers.wrap(animation).isRunningOnCompositor,'Opacity script animations restored from "display: none" should be '+'run on the compositor in the next frame');awaitensureElementRemoval(div);});add_task(asyncfunctionrestyling_for_empty_keyframes(){constdiv=addDiv(null);constanimation=div.animate({},100*MS_PER_SEC);awaitwaitForAnimationReadyToRestyle(animation);letrestyleCount;restyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animations with no keyframes should not cause restyles');animation.effect.setKeyframes({zIndex:['0','999']});restyleCount=awaitobserveStyling(5);is(restyleCount,5,'Setting valid keyframes should cause regular animation restyles to '+'occur');animation.effect.setKeyframes({});restyleCount=awaitobserveStyling(5);is(restyleCount,1,'Setting an empty set of keyframes should trigger a single restyle '+'to remove the previous animated style');awaitensureElementRemoval(div);});add_task_if_omta_enabled(asyncfunctionno_restyling_when_animation_style_when_re_setting_same_animation_property(){constdiv=addDiv(null,{style:'animation: opacity 100s'});constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);ok(SpecialPowers.wrap(animation).isRunningOnCompositor);// Apply the same animation stylediv.style.animation='opacity 100s';constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Applying same animation style '+'should never cause restyles');awaitensureElementRemoval(div);});add_task(asyncfunctionnecessary_update_should_be_invoked(){constdiv=addDiv(null,{style:'animation: on-main-thread 100s'});constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);awaitwaitForAnimationFrames(5);// Apply another animation stylediv.style.animation='on-main-thread 110s';constrestyleCount=awaitobserveStyling(1);// There should be two restyles.// 1) Animation-only restyle for before applying the new animation style// 2) Animation-only restyle for after applying the new animation styleis(restyleCount,2,'Applying animation style with different duration '+'should restyle twice');awaitensureElementRemoval(div);});add_task_if_omta_enabled(asyncfunctionchanging_cascading_result_for_main_thread_animation(){constdiv=addDiv(null,{style:'on-main-thread: blue'});constanimation=div.animate({opacity:[0,1],zIndex:['0','999']},100*MS_PER_SEC);awaitwaitForAnimationReadyToRestyle(animation);ok(SpecialPowers.wrap(animation).isRunningOnCompositor,'The opacity animation is running on the compositor');// Make the z-index style as !important to cause an update// to the cascade.// Bug 1300982: The z-index animation should be no longer// running on the main thread.div.style.setProperty('z-index','0','important');constrestyleCount=awaitobserveStyling(5);todo_is(restyleCount,0,'Changing cascading result for the property running on the main '+'thread does not cause synchronization layer of opacity animation '+'running on the compositor');awaitensureElementRemoval(div);});add_task_if_omta_enabled(asyncfunctionanimation_visibility_and_opacity(){constdiv=addDiv(null);constanimation1=div.animate({opacity:[0,1]},100*MS_PER_SEC);constanimation2=div.animate({visibility:['hidden','visible']},100*MS_PER_SEC);awaitwaitForAnimationReadyToRestyle(animation1);awaitwaitForAnimationReadyToRestyle(animation2);constrestyleCount=awaitobserveStyling(5);is(restyleCount,5,'The animation should not be throttled');awaitensureElementRemoval(div);});add_task(asyncfunctionrestyling_for_animation_on_orphaned_element(){constdiv=addDiv(null);constanimation=div.animate({marginLeft:['0px','100px']},100*MS_PER_SEC);awaitwaitForAnimationReadyToRestyle(animation);div.remove();letrestyleCount;restyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animation on orphaned element should not cause restyles');document.body.appendChild(div);awaitwaitForNextFrame();restyleCount=awaitobserveStyling(5);is(restyleCount,5,'Animation on re-attached to the document begins to update style, got '+restyleCount);awaitensureElementRemoval(div);});add_task_if_omta_enabled(// Tests that if we remove an element from the document whose animation// cascade needs recalculating, that it is correctly updated when it is// re-attached to the document.asyncfunctionrestyling_for_opacity_animation_on_re_attached_element(){constdiv=addDiv(null,{style:'opacity: 1 ! important'});constanimation=div.animate({opacity:[0,1]},100*MS_PER_SEC);awaitwaitForAnimationReadyToRestyle(animation);ok(!SpecialPowers.wrap(animation).isRunningOnCompositor,'The opacity animation overridden by an !important rule is NOT '+'running on the compositor');// Drop the !important rule to update the cascade.div.style.setProperty('opacity','1','');div.remove();constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Opacity animation on orphaned element should not cause restyles');document.body.appendChild(div);// Need a frame to give the animation a chance to be sent to the// compositor.awaitwaitForNextFrame();ok(SpecialPowers.wrap(animation).isRunningOnCompositor,'The opacity animation which is no longer overridden by the '+'!important rule begins running on the compositor even if the '+'!important rule had been dropped before the target element was '+'removed');awaitensureElementRemoval(div);});add_task(asyncfunctionno_throttling_additive_animations_out_of_view_element(){constdiv=addDiv(null,{style:'transform: translateY(-400px);'});constanimation=div.animate([{visibility:'visible'}],{duration:100*MS_PER_SEC,composite:'add'});awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Additive animation has no keyframe whose offset is 0 or 1 in an '+'out-of-view element should be throttled');awaitensureElementRemoval(div);});// Tests that missing keyframes animations don't throttle at all.add_task(asyncfunctionno_throttling_animations_out_of_view_element(){constdiv=addDiv(null,{style:'transform: translateY(-400px);'});constanimation=div.animate([{visibility:'visible'}],100*MS_PER_SEC);awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Discrete animation has has no keyframe whose offset is 0 or 1 in an '+'out-of-view element should be throttled');awaitensureElementRemoval(div);});// Tests that missing keyframes animation on scrolled out element that the// animation is not able to be throttled.add_task(asyncfunctionno_throttling_missing_keyframe_animations_out_of_view_element(){constdiv=addDiv(null,{style:'transform: translateY(-400px);'+'visibility: collapse;'});constanimation=div.animate([{visibility:'visible'}],100*MS_PER_SEC);awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'visibility animation has no keyframe whose offset is 0 or 1 in an '+'out-of-view element should be throttled');awaitensureElementRemoval(div);});// Counter part of the above test.add_task(asyncfunctionno_restyling_discrete_animations_out_of_view_element(){constdiv=addDiv(null,{style:'transform: translateY(-400px);'});constanimation=div.animate({visibility:['visible','hidden']},100*MS_PER_SEC);awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Discrete animation running on the main-thread in an out-of-view '+'element should never cause restyles');awaitensureElementRemoval(div);});add_task(asyncfunctionno_restyling_while_computed_timing_is_not_changed(){constdiv=addDiv(null);constanimation=div.animate({zIndex:['0','999']},{duration:100*MS_PER_SEC,easing:'step-end'});awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);// We possibly expect one restyle from the initial animation compose, in// order to update animations, but nothing else.ok(restyleCount<=1,'Animation running on the main-thread while computed timing is not '+'changed should not cause extra restyles, got '+restyleCount);awaitensureElementRemoval(div);});add_task(asyncfunctionno_throttling_animations_in_view_svg(){constdiv=addDiv(null,{style:'overflow: scroll;'+'height: 100px; width: 100px;'});constsvg=addSVGElement(div,'svg',{viewBox:'-10 -10 0.1 0.1',width:'50px',height:'50px'});constrect=addSVGElement(svg,'rect',{x:'-10',y:'-10',width:'10',height:'10',fill:'red'});constanimation=rect.animate({fill:['blue','lime']},100*MS_PER_SEC);awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,5,'CSS animations on an in-view svg element with post-transform should '+'not be throttled.');awaitensureElementRemoval(div);});add_task_if_omta_enabled(asyncfunctionsvg_non_scaling_stroke_animation(){constdiv=addDiv(null,{style:'overflow: scroll;'+'height: 100px; width: 100px;'});constsvg=addSVGElement(div,'svg',{viewBox:'0 0 250 250',width:'40px',height:'40px'});constrect=addSVGElement(svg,'rect',{x:'0',y:'0',width:'250',height:'250',fill:'red',style:'vector-effect: non-scaling-stroke; animation: rotate 100s infinite;'});constanimation=rect.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);ok(!SpecialPowers.wrap(animation).isRunningOnCompositor,'The animation of a non-scaling-stroke element is not running on the compositor');awaitensureElementRemoval(div);});add_task(asyncfunctionno_throttling_animations_in_transformed_parent(){constdiv=addDiv(null,{style:'overflow: scroll;'+'transform: translateX(50px);'});constsvg=addSVGElement(div,'svg',{viewBox:'0 0 1250 1250',width:'40px',height:'40px'});constrect=addSVGElement(svg,'rect',{x:'0',y:'0',width:'1250',height:'1250',fill:'red'});constanimation=rect.animate({fill:['blue','lime']},100*MS_PER_SEC);awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,5,'CSS animations on an in-view svg element which is inside transformed '+'parent should not be throttled.');awaitensureElementRemoval(div);});add_task(asyncfunctionthrottling_animations_out_of_view_svg(){constdiv=addDiv(null,{style:'overflow: scroll;'+'height: 100px; width: 100px;'});constsvg=addSVGElement(div,'svg',{viewBox:'-10 -10 0.1 0.1',width:'50px',height:'50px'});constrect=addSVGElement(svg,'rect',{width:'10',height:'10',fill:'red'});constanimation=rect.animate({fill:['blue','lime']},100*MS_PER_SEC);awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'CSS animations on an out-of-view svg element with post-transform '+'should be throttled.');awaitensureElementRemoval(div);});add_task(asyncfunctionno_throttling_animations_in_view_css_transform(){constscrollDiv=addDiv(null,{style:'overflow: scroll; '+'height: 100px; width: 100px;'});consttargetDiv=addDiv(null,{style:'animation: on-main-thread 100s;'+'transform: translate(-50px, -50px);'});scrollDiv.appendChild(targetDiv);constanimation=targetDiv.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,5,'CSS animation on an in-view element with pre-transform should not '+'be throttled.');awaitensureElementRemoval(scrollDiv);});add_task(asyncfunctionthrottling_animations_out_of_view_css_transform(){constscrollDiv=addDiv(null,{style:'overflow: scroll;'+'height: 100px; width: 100px;'});consttargetDiv=addDiv(null,{style:'animation: on-main-thread 100s;'+'transform: translate(100px, 100px);'});scrollDiv.appendChild(targetDiv);constanimation=targetDiv.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'CSS animation on an out-of-view element with pre-transform should be '+'throttled.');awaitensureElementRemoval(scrollDiv);});add_task(asyncfunctionthrottling_animations_in_out_of_view_position_absolute_element(){constparentDiv=addDiv(null,{style:'position: absolute; top: -1000px;'});consttargetDiv=addDiv(null,{style:'animation: on-main-thread 100s;'});parentDiv.appendChild(targetDiv);constanimation=targetDiv.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'CSS animation in an out-of-view position absolute element should '+'be throttled');awaitensureElementRemoval(parentDiv);});add_task(asyncfunctionthrottling_animations_on_out_of_view_position_absolute_element(){constdiv=addDiv(null,{style:'animation: on-main-thread 100s; '+'position: absolute; top: -1000px;'});constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'CSS animation on an out-of-view position absolute element should '+'be throttled');awaitensureElementRemoval(div);});add_task(asyncfunctionthrottling_animations_in_out_of_view_position_fixed_element(){constparentDiv=addDiv(null,{style:'position: fixed; top: -1000px;'});consttargetDiv=addDiv(null,{style:'animation: on-main-thread 100s;'});parentDiv.appendChild(targetDiv);constanimation=targetDiv.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'CSS animation on an out-of-view position:fixed element should be '+'throttled');awaitensureElementRemoval(parentDiv);});add_task(asyncfunctionthrottling_animations_on_out_of_view_position_fixed_element(){constdiv=addDiv(null,{style:'animation: on-main-thread 100s; '+'position: fixed; top: -1000px;'});constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'CSS animation on an out-of-view position:fixed element should be '+'throttled');awaitensureElementRemoval(div);});add_task(asyncfunctionno_throttling_animations_in_view_position_fixed_element(){constiframe=document.createElement('iframe');iframe.setAttribute('srcdoc','<div id="target"></div>');document.documentElement.appendChild(iframe);awaitnewPromise(resolve=>{iframe.addEventListener('load',()=>{resolve();});});consttarget=iframe.contentDocument.getElementById('target');target.style='position: fixed; top: 20px; width: 100px; height: 100px;';constanimation=target.animate({zIndex:['0','999']},{duration:100*MS_PER_SEC,iterations:Infinity});awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStylingInTargetWindow(iframe.contentWindow,5);is(restyleCount,5,'CSS animation on an in-view position:fixed element should NOT be '+'throttled');awaitensureElementRemoval(iframe);});add_task(asyncfunctionthrottling_position_absolute_animations_in_collapsed_iframe(){constiframe=document.createElement('iframe');iframe.setAttribute('srcdoc','<div id="target"></div>');iframe.style.height='0px';document.documentElement.appendChild(iframe);awaitnewPromise(resolve=>{iframe.addEventListener('load',()=>{resolve();});});consttarget=iframe.contentDocument.getElementById("target");target.style='position: absolute; top: 50%; width: 100px; height: 100px';constanimation=target.animate({opacity:[0,1]},{duration:100*MS_PER_SEC,iterations:Infinity});awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStylingInTargetWindow(iframe.contentWindow,5);is(restyleCount,0,'Animation on position:absolute element in collapsed iframe should '+'be throttled');awaitensureElementRemoval(iframe);});add_task(asyncfunctionposition_absolute_animations_in_collapsed_element(){constparent=addDiv(null,{style:'overflow: scroll; height: 0px;'});consttarget=addDiv(null,{style:'animation: on-main-thread 100s infinite;'+'position: absolute; top: 50%;'+'width: 100px; height: 100px;'});parent.appendChild(target);constanimation=target.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,5,'Animation on position:absolute element in collapsed element '+'should not be throttled');awaitensureElementRemoval(parent);});add_task(asyncfunctionthrottling_position_absolute_animations_in_collapsed_element(){constparent=addDiv(null,{style:'overflow: scroll; height: 0px;'});consttarget=addDiv(null,{style:'animation: on-main-thread 100s infinite;'+'position: absolute; top: 50%;'});parent.appendChild(target);constanimation=target.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);todo_is(restyleCount,0,'Animation on collapsed position:absolute element in collapsed '+'element should be throttled');awaitensureElementRemoval(parent);});add_task_if_omta_enabled(asyncfunctionno_restyling_for_compositor_animation_on_unrelated_style_change(){constdiv=addDiv(null);constanimation=div.animate({opacity:[0,1]},100*MS_PER_SEC);awaitwaitForAnimationReadyToRestyle(animation);ok(SpecialPowers.wrap(animation).isRunningOnCompositor,'The opacity animation is running on the compositor');div.style.setProperty('color','blue','');constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'The opacity animation keeps running on the compositor when '+'color style is changed');awaitensureElementRemoval(div);});add_task(asyncfunctionno_overflow_transform_animations_in_scrollable_element(){constparentElement=addDiv(null,{style:'overflow-y: scroll; height: 100px;'});constdiv=addDiv(null);constanimation=div.animate({transform:['translateY(10px)','translateY(10px)']},100*MS_PER_SEC);parentElement.appendChild(div);awaitwaitForAnimationReadyToRestyle(animation);ok(SpecialPowers.wrap(animation).isRunningOnCompositor);constrestyleCount=awaitobserveStyling(20);is(restyleCount,0,'No-overflow transform animations running on the compositor should '+'never update style on the main thread');awaitensureElementRemoval(parentElement);});add_task(asyncfunctionno_flush_on_getAnimations(){constdiv=addDiv(null);constanimation=div.animate({opacity:['0','1']},100*MS_PER_SEC);awaitwaitForAnimationReadyToRestyle(animation);ok(SpecialPowers.wrap(animation).isRunningOnCompositor);constrestyleCount=observeAnimSyncStyling(()=>{is(div.getAnimations().length,1,'There should be one animation');});is(restyleCount,0,'Element.getAnimations() should not flush throttled animation style');awaitensureElementRemoval(div);});add_task(asyncfunctionrestyling_for_throttled_animation_on_getAnimations(){constdiv=addDiv(null,{style:'animation: opacity 100s'});constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);ok(SpecialPowers.wrap(animation).isRunningOnCompositor);constrestyleCount=observeAnimSyncStyling(()=>{div.style.animationDuration='0s';is(div.getAnimations().length,0,'There should be no animation');});is(restyleCount,1,// For discarding the throttled animation.'Element.getAnimations() should flush throttled animation style so '+'that the throttled animation is discarded');awaitensureElementRemoval(div);});add_task(asyncfunctionno_restyling_for_throttled_animation_on_querying_play_state(){constdiv=addDiv(null,{style:'animation: opacity 100s'});constanimation=div.getAnimations()[0];constsibling=addDiv(null);awaitwaitForAnimationReadyToRestyle(animation);ok(SpecialPowers.wrap(animation).isRunningOnCompositor);constrestyleCount=observeAnimSyncStyling(()=>{sibling.style.opacity='0.5';is(animation.playState,'running','Animation.playState should be running');});is(restyleCount,0,'Animation.playState should not flush throttled animation in the '+'case where there are only style changes that don\'t affect the '+'throttled animation');awaitensureElementRemoval(div);awaitensureElementRemoval(sibling);});add_task(asyncfunctionrestyling_for_throttled_animation_on_querying_play_state(){constdiv=addDiv(null,{style:'animation: opacity 100s'});constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);ok(SpecialPowers.wrap(animation).isRunningOnCompositor);constrestyleCount=observeAnimSyncStyling(()=>{div.style.animationPlayState='paused';is(animation.playState,'paused','Animation.playState should be reflected by pending style');});is(restyleCount,1,'Animation.playState should flush throttled animation style that '+'affects the throttled animation');awaitensureElementRemoval(div);});add_task(asyncfunctionno_restyling_for_throttled_transition_on_querying_play_state(){constdiv=addDiv(null,{style:'transition: opacity 100s; opacity: 0'});constsibling=addDiv(null);getComputedStyle(div).opacity;div.style.opacity=1;consttransition=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(transition);ok(SpecialPowers.wrap(transition).isRunningOnCompositor);constrestyleCount=observeAnimSyncStyling(()=>{sibling.style.opacity='0.5';is(transition.playState,'running','Animation.playState should be running');});is(restyleCount,0,'Animation.playState should not flush throttled transition in the '+'case where there are only style changes that don\'t affect the '+'throttled animation');awaitensureElementRemoval(div);awaitensureElementRemoval(sibling);});add_task(asyncfunctionrestyling_for_throttled_transition_on_querying_play_state(){constdiv=addDiv(null,{style:'transition: opacity 100s; opacity: 0'});getComputedStyle(div).opacity;div.style.opacity='1';consttransition=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(transition);ok(SpecialPowers.wrap(transition).isRunningOnCompositor);constrestyleCount=observeAnimSyncStyling(()=>{div.style.transitionProperty='none';is(transition.playState,'idle','Animation.playState should be reflected by pending style change '+'which cancel the transition');});is(restyleCount,1,'Animation.playState should flush throttled transition style that '+'affects the throttled animation');awaitensureElementRemoval(div);});add_task(asyncfunctionrestyling_visibility_animations_on_in_view_element(){constdiv=addDiv(null);constanimation=div.animate({visibility:['hidden','visible']},100*MS_PER_SEC);awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,5,'Visibility animation running on the main-thread on in-view element '+'should not be throttled');awaitensureElementRemoval(div);});add_task(asyncfunctionrestyling_outline_offset_animations_on_invisible_element(){constdiv=addDiv(null,{style:'visibility: hidden; '+'outline-style: solid; '+'outline-width: 1px;'});constanimation=div.animate({outlineOffset:['0px','10px']},{duration:100*MS_PER_SEC,iterations:Infinity});awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Outline offset animation running on the main-thread on invisible '+'element should be throttled');awaitensureElementRemoval(div);});add_task(asyncfunctionrestyling_transform_animations_on_invisible_element(){constdiv=addDiv(null,{style:'visibility: hidden;'});constanimation=div.animate({transform:['none','rotate(360deg)']},{duration:100*MS_PER_SEC,iterations:Infinity});awaitwaitForAnimationReadyToRestyle(animation);ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Transform animations on visibility hidden element should be throttled');awaitensureElementRemoval(div);});add_task(asyncfunctionrestyling_transform_animations_on_invisible_element(){constdiv=addDiv(null,{style:'visibility: hidden;'});constanimation=div.animate([{transform:'rotate(360deg)'}],{duration:100*MS_PER_SEC,iterations:Infinity});awaitwaitForAnimationReadyToRestyle(animation);ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Transform animations without 100% keyframe on visibility hidden '+'element should be throttled');awaitensureElementRemoval(div);});add_task(asyncfunctionrestyling_translate_animations_on_invisible_element(){constdiv=addDiv(null,{style:'visibility: hidden;'});constanimation=div.animate([{translate:'100px'}],{duration:100*MS_PER_SEC,iterations:Infinity});awaitwaitForAnimationReadyToRestyle(animation);ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Translate animations without 100% keyframe on visibility hidden '+'element should be throttled');awaitensureElementRemoval(div);});add_task(asyncfunctionrestyling_rotate_animations_on_invisible_element(){constdiv=addDiv(null,{style:'visibility: hidden;'});constanimation=div.animate([{rotate:'45deg'}],{duration:100*MS_PER_SEC,iterations:Infinity});awaitwaitForAnimationReadyToRestyle(animation);ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Rotate animations without 100% keyframe on visibility hidden '+'element should be throttled');awaitensureElementRemoval(div);});add_task(asyncfunctionrestyling_scale_animations_on_invisible_element(){constdiv=addDiv(null,{style:'visibility: hidden;'});constanimation=div.animate([{scale:'2 2'}],{duration:100*MS_PER_SEC,iterations:Infinity});awaitwaitForAnimationReadyToRestyle(animation);ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Scale animations without 100% keyframe on visibility hidden '+'element should be throttled');awaitensureElementRemoval(div);});add_task(asyncfunctionrestyling_transform_animations_having_abs_pos_child_on_invisible_element(){constdiv=addDiv(null,{style:'visibility: hidden;'});constchild=addDiv(null,{style:'position: absolute; top: 100px;'});div.appendChild(child);constanimation=div.animate({transform:['none','rotate(360deg)']},{duration:100*MS_PER_SEC,iterations:Infinity});awaitwaitForAnimationReadyToRestyle(animation);ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Transform animation having an absolutely positioned child on '+'visibility hidden element should be throttled');awaitensureElementRemoval(div);});add_task(asyncfunctionno_restyling_animations_in_out_of_view_iframe(){constdiv=addDiv(null,{style:'overflow-y: scroll; height: 100px;'});constiframe=document.createElement('iframe');iframe.setAttribute('srcdoc','<div style="height: 100px;"></div><div id="target"></div>');div.appendChild(iframe);awaitnewPromise(resolve=>{iframe.addEventListener('load',()=>{resolve();});});consttarget=iframe.contentDocument.getElementById("target");target.style='width: 100px; height: 100px;';constanimation=target.animate({zIndex:['0','999']},100*MS_PER_SEC);awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStylingInTargetWindow(iframe.contentWindow,5);is(restyleCount,0,'Animation in out-of-view iframe should be throttled');awaitensureElementRemoval(div);});// Tests that transform animations are not able to run on the compositor due// to layout restrictions (e.g. animations on a large size frame) doesn't// flush layout at all.add_task(asyncfunctionflush_layout_for_transform_animations(){// Set layout.animation.prerender.partial to disallow transform animations// on large frames to be sent to the compositor.awaitSpecialPowers.pushPrefEnv({set:[['layout.animation.prerender.partial',false]]});constdiv=addDiv(null,{style:'width: 10000px; height: 10000px;'});constanimation=div.animate([{transform:'rotate(360deg)',}],{duration:100*MS_PER_SEC,// Set step-end to skip further restyles.easing:'step-end'});constFLUSH_LAYOUT=SpecialPowers.DOMWindowUtils.FLUSH_LAYOUT;ok(SpecialPowers.DOMWindowUtils.needsFlush(FLUSH_LAYOUT),'Flush is needed for the appended div');awaitwaitForAnimationReadyToRestyle(animation);ok(!SpecialPowers.wrap(animation).isRunningOnCompositor,"Shouldn't be running in the compositor");// We expect one restyle from the initial animation compose.awaitwaitForNextFrame();ok(!SpecialPowers.wrap(animation).isRunningOnCompositor,"Still shouldn't be running in the compositor");ok(!SpecialPowers.DOMWindowUtils.needsFlush(FLUSH_LAYOUT),'No further layout flush needed');awaitensureElementRemoval(div);});add_task(asyncfunctionpartial_prerendered_transform_animations(){awaitSpecialPowers.pushPrefEnv({set:[['layout.animation.prerender.partial',true]]});constdiv=addDiv(null,{style:'width: 10000px; height: 10000px;'});constanimation=div.animate(// Use the same value both for `from` and `to` to avoid jank on the// compositor.{transform:['rotate(0deg)','rotate(0deg)']},100*MS_PER_SEC);awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5)is(restyleCount,0,'Transform animation with partial pre-rendered should never cause '+'restyles');awaitensureElementRemoval(div);});add_task(asyncfunctionrestyling_on_create_animation(){constdiv=addDiv();letpriorAnimationTriggeredRestyles=SpecialPowers.DOMWindowUtils.animationTriggeredRestyles;constanimationA=div.animate({transform:['none','rotate(360deg)']},100*MS_PER_SEC);constanimationB=div.animate({opacity:[0,1]},100*MS_PER_SEC);constanimationC=div.animate({color:['blue','green']},100*MS_PER_SEC);constanimationD=div.animate({width:['100px','200px']},100*MS_PER_SEC);constanimationE=div.animate({height:['100px','200px']},100*MS_PER_SEC);constrestyleCount=SpecialPowers.DOMWindowUtils.animationTriggeredRestyles-priorAnimationTriggeredRestyles;is(restyleCount,0,'Creating animations should not flush styles');awaitensureElementRemoval(div);});add_task(asyncfunctionout_of_view_background_position(){constdiv=addDiv(null,{style:` background-image: linear-gradient(90deg, rgb(224, 224, 224), rgb(241, 241, 241) 30%, rgb(224, 224, 224) 60%); background-size: 80px; animation: background-position 100s infinite; transform: translateY(-400px); `,})constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'background-position animations can be throttled');awaitensureElementRemoval(div);});add_task_if_omta_enabled(asyncfunctionno_restyling_animations_in_opacity_zero_element(){constdiv=addDiv(null,{style:'animation: on-main-thread 100s infinite; opacity: 0'});constanimation=div.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animations running on the main thread in opacity: 0 element '+'should never cause restyles');awaitensureElementRemoval(div);});add_task_if_omta_enabled(asyncfunctionno_restyling_compositor_animations_in_opacity_zero_descendant(){constcontainer=addDiv(null,{style:'opacity: 0'});constchild=addDiv(null,{style:'animation: background-color 100s infinite;'});container.appendChild(child);constanimation=child.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animations running on the compositor in opacity zero descendant element '+'should never cause restyles');awaitensureElementRemoval(container);});add_task_if_omta_enabled(asyncfunctionno_restyling_compositor_animations_in_opacity_zero_descendant_abspos(){constcontainer=addDiv(null,{style:'opacity: 0'});constchild=addDiv(null,{style:'position: absolute; animation: background-color 100s infinite;'});container.appendChild(child);constanimation=child.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animations running on the compositor in opacity zero abspos descendant element '+'should never cause restyles');awaitensureElementRemoval(container);});add_task_if_omta_enabled(asyncfunctionno_restyling_compositor_animations_in_opacity_zero_element(){constchild=addDiv(null,{style:'animation: background-color 100s infinite; opacity: 0'});constanimation=child.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animations running on the compositor in opacity zero element '+'should never cause restyles');awaitensureElementRemoval(child);});add_task_if_omta_enabled(asyncfunctionrestyling_main_thread_animations_in_opacity_zero_descendant_after_root_opacity_animation(){constcontainer=addDiv(null,{style:'opacity: 0'});constchild=addDiv(null,{style:'animation: on-main-thread 100s infinite;'});container.appendChild(child);// Animate the container from 1 to zero opacity and ensure the child animation is throttled then.constcontainerAnimation=container.animate({opacity:['1','0']},100);awaitcontainerAnimation.finished;constanimation=child.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'Animations running on the compositor in opacity zero descendant element '+'should never cause restyles after root animation has finished');awaitensureElementRemoval(container);});add_task_if_omta_enabled(asyncfunctionrestyling_main_thread_animations_in_opacity_zero_descendant_during_root_opacity_animation(){constcontainer=addDiv(null,{style:'opacity: 0; animation: opacity-from-zero 100s infinite'});constchild=addDiv(null,{style:'animation: on-main-thread 100s infinite;'});container.appendChild(child);constanimation=child.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);constrestyleCount=awaitobserveStyling(5);is(restyleCount,5,'Animations in opacity zero descendant element '+'should not be throttled if root is animating opacity');awaitensureElementRemoval(container);});add_task_if_omta_enabled(asyncfunctionrestyling_omt_animations_in_opacity_zero_descendant_during_root_opacity_animation(){constcontainer=addDiv(null,{style:'opacity: 0; animation: opacity-from-zero 100s infinite'});constchild=addDiv(null,{style:'animation: rotate 100s infinite'});container.appendChild(child);constanimation=child.getAnimations()[0];awaitwaitForAnimationReadyToRestyle(animation);ok(SpecialPowers.wrap(animation).isRunningOnCompositor);awaitensureElementRemoval(container);});add_task_if_omta_enabled(asyncfunctiontransparent_background_color_animations(){constdiv=addDiv(null);constanimation=div.animate({backgroundColor:['rgb(0, 200, 0, 0)','rgb(200, 0, 0, 0.1)']},{duration:100*MS_PER_SEC,// An easing function producing zero in the first half of// the duration.easing:'cubic-bezier(1, 0, 1, 0)'});awaitwaitForAnimationReadyToRestyle(animation);ok(SpecialPowers.wrap(animation).isRunningOnCompositor);constrestyleCount=awaitobserveStyling(5);is(restyleCount,0,'transparent background-color animation should not update styles on '+'the main thread');awaitensureElementRemoval(div);});add_task_if_omta_enabled(asyncfunctiontransform_animation_on_collapsed_element(){constiframe=document.createElement('iframe');document.body.appendChild(iframe);// Load a cross origin iframe.consttargetURL=SimpleTest.getTestFileURL("empty.html").replace(window.location.origin,"http://example.com/");iframe.src=targetURL;awaitnewPromise(resolve=>{iframe.onload=resolve;});awaitSpecialPowers.spawn(iframe,[MS_PER_SEC],async(MS_PER_SEC)=>{// Create a flex item with "preserve-3d" having an abs-pos child inside// a grid container.// These styles make the flex item size (0x0).constgridContainer=content.document.createElement("div");gridContainer.style.display="grid";gridContainer.style.placeItems="center";consttarget=content.document.createElement("div");target.style.display="flex";target.style.transformStyle="preserve-3d";gridContainer.appendChild(target);constchild=content.document.createElement("div");child.style.position="absolute";child.style.transform="rotateY(0deg)";child.style.width="100px";child.style.height="100px";child.style.backgroundColor="green";target.appendChild(child);content.document.body.appendChild(gridContainer);constanimation=target.animate({transform:["rotateY(0deg)","rotateY(360deg)"]},{duration:100*MS_PER_SEC,id:"test",easing:'step-end'});awaitcontent.wrappedJSObject.waitForAnimationReadyToRestyle(animation);ok(SpecialPowers.wrap(animation).isRunningOnCompositor,'transform animation on a collapsed element should run on the '+'compositor');constrestyleCount=awaitcontent.wrappedJSObject.observeStyling(5);is(restyleCount,0,'transform animation on a collapsed element animation should not '+'update styles on the main thread');});awaitensureElementRemoval(iframe);});});</script></body>