<!DOCTYPE HTML> <html> <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1236979 --> <head> <meta charset="utf-8"> <title>Test for Bug 1236979 (events that have legacy alternative versions)</title> <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> <style> @keyframes anim1 { 0% { margin-left: 0px } 100% { margin-left: 100px } } </style> </head> <body> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1236979">Mozilla Bug 1236979</a> <p id="display"></p> <div id="content" style="display: none"> </div> <pre id="test"> <script type="application/javascript"> /** Test for Bug 1236979 **/ 'use strict'; SimpleTest.waitForExplicitFinish(); // Array of info-bundles about each legacy event to be tested: var gLegacyEventInfo = [ { legacy_name: "webkitTransitionEnd", modern_name: "transitionend", trigger_event: triggerShortTransition, }, { legacy_name: "webkitAnimationStart", modern_name: "animationstart", trigger_event: triggerShortAnimation, }, { legacy_name: "webkitAnimationEnd", modern_name: "animationend", trigger_event: triggerShortAnimation, }, { legacy_name: "webkitAnimationIteration", modern_name: "animationiteration", trigger_event: triggerAnimationIteration, } ]; // EVENT-TRIGGERING FUNCTIONS // -------------------------- // This function triggers a very short (1ms long) transition, which will cause // events to fire for the transition ending. function triggerShortTransition(node) { node.style.transition = "1ms color linear" ; node.style.color = "purple"; // Flush style, so that the above assignment value actually takes effect // in the computed style, so that a transition will get triggered when it // changes. window.getComputedStyle(node, "").color; node.style.color = "teal"; } // This function triggers a very short (1ms long) animation, which will cause // events to fire for the animation beginning & ending. function triggerShortAnimation(node) { node.style.animation = "anim1 1ms linear"; } // This function triggers a long animation with two iterations, which is // *nearly* at the end of its first iteration. It will hit the end of that // iteration (firing an event) almost immediately, 1ms in the future. // // NOTE: It's important that this animation have a *long* duration. If it were // short (e.g. 1ms duration), then we might jump past all its iterations in // a single refresh-driver tick. And if that were to happens, we'd *never* fire // any animationiteration events -- the CSS Animations spec says this event // must not be fired "...when an animationend event would fire at the same time" // (which would be the case in this example with a 1ms duration). So, to make // sure our event does fire, we use a long duration and a nearly-as-long // negative delay. This ensures we hit the end of the first iteration right // away, and that we don't risk hitting the end of the second iteration at the // same time. function triggerAnimationIteration(node) { node.style.animation = "anim1 300s -299.999s linear 2"; } // GENERAL UTILITY FUNCTIONS // ------------------------- // Creates a new div and appends it as a child of the specified parentNode, or // (if no parent is specified) as a child of the element with ID 'display'. function createChildDiv(parentNode) { if (!parentNode) { parentNode = document.getElementById("display"); if (!parentNode) { ok(false, "no 'display' element to append to"); } } var div = document.createElement("div"); parentNode.appendChild(div); return div; } // Returns an event-handler function, which (when invoked) simply checks that // the event's type matches what's expected. If a callback is passed in, then // the event-handler will invoke that callback as well. function createHandlerWithTypeCheck(expectedEventType, extraHandlerLogic) { var handler = function(e) { is(e.type, expectedEventType, "When an event handler for '" + expectedEventType + "' is invoked, " + "the event's type field should be '" + expectedEventType + "'."); if (extraHandlerLogic) { extraHandlerLogic(e); } } return handler; } // TEST FUNCTIONS // -------------- // These functions expect to be passed an entry from gEventInfo, and they // return a Promise which performs the test & resolves when it's complete. // The function names all begin with "mp", which stands for "make promise". // So e.g. "mpTestLegacyEventSent" means "make a promise to test that the // legacy event is sent". // Tests that the legacy event type is sent, when only a legacy handler is // registered. function mpTestLegacyEventSent(eventInfo) { return new Promise( function(resolve, reject) { // Create a node & register an event-handler for the legacy event: var div = createChildDiv(); var handler = createHandlerWithTypeCheck(eventInfo.legacy_name, function() { // When event-handler is done, clean up & resolve: div.parentNode.removeChild(div); resolve(); }); div.addEventListener(eventInfo.legacy_name, handler); // Trigger the event: eventInfo.trigger_event(div); } ); } // Test that the modern event type (and only the modern event type) is fired, // when listeners of both modern & legacy types are registered. The legacy // listener should not be invoked. function mpTestModernBeatsLegacy(eventInfo) { return new Promise( function(resolve, reject) { var div = createChildDiv(); var legacyHandler = function(e) { reject("Handler for legacy event '" + eventInfo.legacy_name + "' should not be invoked when there's a handler registered " + "for both modern & legacy event type on the same node"); }; var modernHandler = createHandlerWithTypeCheck(eventInfo.modern_name, function() { // Indicate that the test has passed (we invoked the modern handler): ok(true, "Handler for modern event '" + eventInfo.modern_name + "' should be invoked when there's a handler registered for " + "both modern & legacy event type on the same node"); // When event-handler is done, clean up & resolve: div.parentNode.removeChild(div); resolve(); }); div.addEventListener(eventInfo.legacy_name, legacyHandler); div.addEventListener(eventInfo.modern_name, modernHandler); eventInfo.trigger_event(div); } ); } // Test that an event which bubbles may fire listeners of different flavors // (modern vs. legacy) at each bubbling level, depending on what's registered // at that level. function mpTestDiffListenersEventBubbling(eventInfo) { return new Promise( function(resolve, reject) { var grandparent = createChildDiv(); var parent = createChildDiv(grandparent); var target = createChildDiv(parent); var didEventFireOnTarget = false; var didEventFireOnParent = false; var eventSentToTarget; target.addEventListener(eventInfo.modern_name, createHandlerWithTypeCheck(eventInfo.modern_name, function(e) { ok(e.bubbles, "Expecting event to bubble"); eventSentToTarget = e; didEventFireOnTarget = true; })); parent.addEventListener(eventInfo.legacy_name, createHandlerWithTypeCheck(eventInfo.legacy_name, function(e) { is(e, eventSentToTarget, "Same event object should bubble, despite difference in type"); didEventFireOnParent = true; })); grandparent.addEventListener(eventInfo.modern_name, createHandlerWithTypeCheck(eventInfo.modern_name, function(e) { ok(didEventFireOnTarget, "Event should have fired on child"); ok(didEventFireOnParent, "Event should have fired on parent"); is(e, eventSentToTarget, "Same event object should bubble, despite difference in type"); // Clean up. grandparent.parentNode.removeChild(grandparent); resolve(); })); eventInfo.trigger_event(target); } ); } // Test that an event in the capture phase may fire listeners of different // flavors (modern vs. legacy) at each level, depending on what's registered // at that level. function mpTestDiffListenersEventCapturing(eventInfo) { return new Promise( function(resolve, reject) { var grandparent = createChildDiv(); var parent = createChildDiv(grandparent); var target = createChildDiv(parent); var didEventFireOnTarget = false; var didEventFireOnParent = false; var didEventFireOnGrandparent = false; var eventSentToGrandparent; grandparent.addEventListener(eventInfo.modern_name, createHandlerWithTypeCheck(eventInfo.modern_name, function(e) { eventSentToGrandparent = e; didEventFireOnGrandparent = true; }), true); parent.addEventListener(eventInfo.legacy_name, createHandlerWithTypeCheck(eventInfo.legacy_name, function(e) { is(e.eventPhase, Event.CAPTURING_PHASE, "event should be in capturing phase"); is(e, eventSentToGrandparent, "Same event object should capture, despite difference in type"); ok(didEventFireOnGrandparent, "Event should have fired on grandparent"); didEventFireOnParent = true; }), true); target.addEventListener(eventInfo.modern_name, createHandlerWithTypeCheck(eventInfo.modern_name, function(e) { is(e.eventPhase, Event.AT_TARGET, "event should be at target phase"); is(e, eventSentToGrandparent, "Same event object should capture, despite difference in type"); ok(didEventFireOnParent, "Event should have fired on parent"); // Clean up. grandparent.parentNode.removeChild(grandparent); resolve(); }), true); eventInfo.trigger_event(target); } ); } // MAIN FUNCTION: Kick off the tests. function main() { Promise.resolve().then(function() { return Promise.all(gLegacyEventInfo.map(mpTestLegacyEventSent)) }).then(function() { return Promise.all(gLegacyEventInfo.map(mpTestModernBeatsLegacy)); }).then(function() { return Promise.all(gLegacyEventInfo.map(mpTestDiffListenersEventCapturing)); }).then(function() { return Promise.all(gLegacyEventInfo.map(mpTestDiffListenersEventBubbling)); }).then(function() { SimpleTest.finish(); }).catch(function(reason) { ok(false, "Test failed: " + reason); SimpleTest.finish(); }); } main(); </script> </pre> </body> </html>