<!doctype html> <meta charset="utf-8"> <title>Test mouseenter and mouseleave for iframe.</title> <script src="/tests/SimpleTest/SimpleTest.js"></script> <script src="/tests/SimpleTest/EventUtils.js"></script> <script src="/tests/SimpleTest/paint_listener.js"></script> <script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script> <link rel="stylesheet" href="/tests/SimpleTest/test.css"/> <style> #start { width: 300px; height: 30px; } #target, #target2 { width: 150px; height: 150px; background-color: #fcc; display: inline-block; } #frame, #frame2 { height: 100%; width: 100%; } #reflow, #div { width: 300px; height: 10px; background-color: lightgreen; } </style> <div id="start">Start from here!!</div> <div id="div"></div> <div id="target"> <iframe id="frame" frameborder="0" scrolling="no"></iframe> </div> <div id="target2"> <iframe id="frame2" frameborder="0" scrolling="no"></iframe> </div> <div id="reflow"></div> <script> function reflow() { let div = document.getElementById("reflow"); div.style.display = "none"; div.getBoundingClientRect(); div.style.display = "block"; div.getBoundingClientRect(); } function waitForMessage(aRemoteTarget, aEventType, aTargetName, aLastExpectedElement) { return new Promise(function (aResolve, aReject) { const data = `waiting for "${aEventType}" on <${aTargetName}> in <iframe id="${aRemoteTarget.id}">`; let expectedMessageReceived = false; window.addEventListener("message", function listener(aEvent) { if (aEvent.source != aRemoteTarget.contentWindow) { return; } if (aEvent.data.eventType == "reflowed") { if (expectedMessageReceived) { window.removeEventListener("message", listener); aResolve(); ok(true, `Message listener ${data} is correctly removed`); } return; } if (aEvent.data.eventType !== aEventType) { window.removeEventListener("message", listener); is( aEvent.data.eventType, aEventType, `receive unexpected message ${JSON.stringify(aEvent.data)} at ${data}` ); aReject(new Error(`receive unexpected message ${JSON.stringify(aEvent.data)} at ${data}`)); return; } if (aEvent.data.targetName !== aTargetName) { return; } if (expectedMessageReceived) { window.removeEventListener("message", listener); ok(false, `receive redundant message at ${data}`); aReject(new Error(`receive redundant message at ${data}`)); return; } expectedMessageReceived = true; ok(true, `receive message at ${data}`); if (aLastExpectedElement) { // Trigger a reflow which will generate synthesized mouse move event. aRemoteTarget.contentWindow.postMessage("reflow", "*"); } }); }); } /** * Wait for "mouseenter" events in a child document. * * @param aRemoteTarget An <iframe> element which has the child document. * @param aTargetNames An array of `mouseenter` targets which you want to * listen to. The order should be ancestor to descendant. */ function waitForMouseEnterMessages(aRemoteTarget, aTargetNames) { let promises = []; let targetName; while ((targetName = aTargetNames.shift())) { promises.push( waitForMessage(aRemoteTarget, "mouseenter", targetName, !aTargetNames.length) ); } return Promise.all(promises); } /** * Wait for "mouseleave" events in a child document. * * @param aRemoteTarget An <iframe> element which has the child document. * @param aTargetNames An array of `mouseleave` targets which you want to * listen to. The order should be ancestor to descendant. */ function waitForMouseLeaveMessages(aRemoteTarget, aTargetNames) { let promises = []; let targetName; while ((targetName = aTargetNames.pop())) { promises.push( waitForMessage(aRemoteTarget, "mouseleave", targetName, !aTargetNames.length) ); } return Promise.all(promises); } function waitForLeaveEvent(aTarget) { return new Promise(function(aResolve) { aTarget.addEventListener("mouseleave", function(aEvent) { ok(true, `receive ${aEvent.type}`); aResolve(); }, { once: true }); }); } function waitForEnterLeaveEvents(aEnterTarget, aLeaveTarget) { let expectedEvents = [{target: aEnterTarget, eventName: "mouseenter"}]; if (aLeaveTarget) { expectedEvents.push({target: aLeaveTarget, eventName: "mouseleave"}) } return new Promise(function(aResolve, aReject) { function cleanup() { aEnterTarget.removeEventListener("mouseenter", listener); aEnterTarget.removeEventListener("mouseleave", unexpectedEvent); if (aLeaveTarget) { aLeaveTarget.removeEventListener("mouseenter", unexpectedEvent); aLeaveTarget.removeEventListener("mouseleave", listener); } } function unexpectedEvent(aEvent) { cleanup(); ok(false, `receive unexpected ${aEvent.type}`); aReject(new Error(`receive unexpected ${aEvent.type}`)); } async function listener(aEvent) { if (expectedEvents.length <= 0) { unexpectedEvent(aEvent); return; } let expectedEvent = expectedEvents.pop(); if (expectedEvent.target == aEvent.target && expectedEvent.eventName == aEvent.type) { ok(true, `receive ${aEvent.type}`); } else { unexpectedEvent(aEvent); return; } if (!expectedEvents.length) { // Trigger a reflow which will generate synthesized mouse move event. reflow(); // Now wait a bit to see if there is any unexpected event fired. setTimeout(function() { cleanup(); aResolve(); }, 0); } } aEnterTarget.addEventListener("mouseenter", listener); aEnterTarget.addEventListener("mouseleave", unexpectedEvent); if (aLeaveTarget) { aLeaveTarget.addEventListener("mouseenter", unexpectedEvent); aLeaveTarget.addEventListener("mouseleave", listener); } }); } function moveMouseToInitialPosition() { info("Mouse moves to initial position"); return promiseNativeMouseEvent({ type: "mousemove", target: document.getElementById("start"), atCenter: true, }); } add_setup(async function() { // Wait for focus before starting tests. await SimpleTest.promiseFocus(); // Wait for apz getting stable. await waitUntilApzStable(); // Move mouse to initial position. await moveMouseToInitialPosition(); // After initializing the mouse cursor position, we should load <iframe>s. // This avoids the case that the cursor is over one of them. info("Load child documents into the iframes"); let promiseLoadingIFrames = []; for (const iframe of document.querySelectorAll("iframe")) { promiseLoadingIFrames.push( new Promise(resolve => { iframe.addEventListener("load", resolve, {once: true}); }) ); iframe.src = "http://example.com/tests/dom/events/test/file_mouse_enterleave.html"; } await Promise.all(promiseLoadingIFrames); }); add_task(async function testMouseEnterLeave() { let div = document.getElementById("div"); let target = document.getElementById("target"); let iframe = document.getElementById("frame"); info("Mouse moves to the div above iframe"); let promise = waitForEnterLeaveEvents(div); synthesizeNativeMouseEvent({ type: "mousemove", target: div, atCenter: true, }); await promise; info("Mouse moves into iframe"); promise = Promise.all([waitForEnterLeaveEvents(target, div), waitForMouseEnterMessages(iframe, ["html", "div"])]); synthesizeNativeMouseEvent({ type: "mousemove", target, atCenter: true, }); await promise; info("Mouse moves out from iframe to the div above iframe"); promise = Promise.all([waitForEnterLeaveEvents(div, target), waitForMouseLeaveMessages(iframe, ["html", "div"])]); synthesizeNativeMouseEvent({ type: "mousemove", target: div, atCenter: true, }); await promise; // Move mouse back to initial position. This is to prevent unexpected // mouseleave event in initial steps for test-verify which runs same test // multiple times. await moveMouseToInitialPosition(); }); add_task(async function testMouseEnterLeaveBetweenIframe() { let target = document.getElementById("target"); let iframe = document.getElementById("frame"); info("Mouse moves into the first iframe"); let promise = Promise.all([waitForEnterLeaveEvents(target), waitForMouseEnterMessages(iframe, ["html", "div"])]); synthesizeNativeMouseEvent({ type: "mousemove", target, atCenter: true, }); await promise; let target2 = document.getElementById("target2"); let iframe2 = document.getElementById("frame2"); info("Mouse moves out from the first iframe to the second iframe"); promise = Promise.all([waitForEnterLeaveEvents(target2, target), waitForMouseLeaveMessages(iframe, ["html", "div"]), waitForMouseEnterMessages(iframe2, ["html", "div"])]); synthesizeNativeMouseEvent({ type: "mousemove", target: target2, atCenter: true, }) await promise; info("Mouse moves out from the second iframe to the first iframe"); promise = Promise.all([waitForEnterLeaveEvents(target, target2), waitForMouseLeaveMessages(iframe2, ["html", "div"]), waitForMouseEnterMessages(iframe, ["html", "div"])]); synthesizeNativeMouseEvent({ type: "mousemove", target, atCenter: true, }); await promise; // Move mouse back to initial position. await Promise.all([waitForLeaveEvent(target), waitForMouseLeaveMessages(iframe, ["html", "div"]), moveMouseToInitialPosition()]); }); add_task(async function testMouseEnterLeaveSwitchWindow() { let target = document.getElementById("target"); let iframe = document.getElementById("frame"); info("Mouse moves into iframe"); let promise = Promise.all([waitForEnterLeaveEvents(target), waitForMouseEnterMessages(iframe, ["html", "div"])]); synthesizeNativeMouseEvent({ type: "mousemove", target, atCenter: true, }); await promise; info("Open and switch to new window"); promise = Promise.all([waitForLeaveEvent(target), waitForMouseLeaveMessages(iframe, ["html", "div"])]); let win = window.open("http://example.com/tests/dom/events/test/file_mouse_enterleave.html"); // Trigger a reflow which will generate synthesized mouse move event. win.postMessage("reflow", "*"); await promise; info("Switch back to test window"); promise = Promise.all([waitForEnterLeaveEvents(target), waitForMouseEnterMessages(iframe, ["html", "div"])]); win.close(); // Trigger a reflow which will generate synthesized mouse move event. reflow(); // Wait for apz getting stable. await waitUntilApzStable(); synthesizeNativeMouseEvent({ type: "mousemove", target, atCenter: true, }); await promise; // Move mouse back to initial position. await Promise.all([waitForLeaveEvent(target), moveMouseToInitialPosition()]); }); </script>