<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<?xml-stylesheet href="chrome://global/skin" type="text/css"?>

<window id="NativeMenuWindow"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        xmlns:html="http://www.w3.org/1999/xhtml"
        width="600"
        height="600"
        title="Native Mouse Event Test"
        orient="vertical">
  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />

  <box height="200" id="box"/>
  <menupopup id="popup" width="250" height="50"/>
  <panel id="panel" width="250" height="50" noautohide="true"/>

  <script type="application/javascript"><![CDATA[

    function ok(condition, message) {
      window.opener.wrappedJSObject.SimpleTest.ok(condition, message);
    }

    function is(a, b, message) {
      window.opener.wrappedJSObject.SimpleTest.is(a, b, message);
    }

    function todo(condition, message) {
      window.opener.wrappedJSObject.SimpleTest.todo(condition, message);
    }

    function todo_is(a, b, message) {
      window.opener.wrappedJSObject.SimpleTest.todo_is(a, b, message);
    }

    function onTestsFinished() {
      clearTimeout(gAfterLoopExecution);
      observe(window, eventMonitor, false);
      observe(gRightWindow, eventMonitor, false);
      observe(gPopup, eventMonitor, false);
      gRightWindow.close();
      var openerSimpleTest = window.opener.wrappedJSObject.SimpleTest;
      window.close();
      openerSimpleTest.finish();
    }

    const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
    const xulWin = 'data:application/vnd.mozilla.xul+xml,<?xml version="1.0"?><?xml-stylesheet href="chrome://global/skin" type="text/css"?><window xmlns="' + XUL_NS + '"/>';

    const NSLeftMouseDown      = 1,
          NSLeftMouseUp        = 2,
          NSRightMouseDown     = 3,
          NSRightMouseUp       = 4,
          NSMouseMoved         = 5,
          NSLeftMouseDragged   = 6,
          NSRightMouseDragged  = 7,
          NSMouseEntered       = 8,
          NSMouseExited        = 9,
          NSKeyDown            = 10,
          NSKeyUp              = 11,
          NSFlagsChanged       = 12,
          NSAppKitDefined      = 13,
          NSSystemDefined      = 14,
          NSApplicationDefined = 15,
          NSPeriodic           = 16,
          NSCursorUpdate       = 17,
          NSScrollWheel        = 22,
          NSTabletPoint        = 23,
          NSTabletProximity    = 24,
          NSOtherMouseDown     = 25,
          NSOtherMouseUp       = 26,
          NSOtherMouseDragged  = 27,
          NSEventTypeGesture   = 29,
          NSEventTypeMagnify   = 30,
          NSEventTypeSwipe     = 31,
          NSEventTypeRotate    = 18,
          NSEventTypeBeginGesture = 19,
          NSEventTypeEndGesture   = 20;

    const NSAlphaShiftKeyMask = 1 << 16,
          NSShiftKeyMask      = 1 << 17,
          NSControlKeyMask    = 1 << 18,
          NSAlternateKeyMask  = 1 << 19,
          NSCommandKeyMask    = 1 << 20,
          NSNumericPadKeyMask = 1 << 21,
          NSHelpKeyMask       = 1 << 22,
          NSFunctionKeyMask   = 1 << 23;

    const gDebug = false;

    function printDebug(msg) { if (gDebug) dump(msg); }

    var gExpectedEvents = [];
    var gRightWindow = null, gPopup = null;
    var gCurrentMouseX = 0, gCurrentMouseY = 0;
    var gAfterLoopExecution = 0;

    function testMouse(x, y, msg, elem, win, exp, flags, callback) {
      clearExpectedEvents();
      var syntheticEvent = null;
      exp.forEach(function (expEv) {
        expEv.screenX = x;
        expEv.screenY = y;
        if (expEv.synthetic) {
          is(syntheticEvent, null,
             "Can't handle two synthetic events in a single testMouse call");
          syntheticEvent = expEv;
        }
        gExpectedEvents.push(expEv);
      });
      printDebug("sending event: " + x + ", " + y + " (" + msg + ")\n");
      gCurrentMouseX = x;
      gCurrentMouseY = y;
      var utils = win.windowUtils;
      var callbackFunc = function() {
        clearExpectedEvents();
        callback();
      }
      if (syntheticEvent) {
        // Set up this listener before we sendNativeMouseEvent, just
        // in case that synchronously calls us.
        eventListenOnce(syntheticEvent.target, syntheticEvent.type,
                        // Trigger callbackFunc async, so we're not assuming
                        // anything about how our listener gets ordered with
                        // others.
                        function () { SimpleTest.executeSoon(callbackFunc) });
      }
      utils.sendNativeMouseEvent(x, y, msg, flags || 0, elem);
      if (!syntheticEvent) {
        gAfterLoopExecution = setTimeout(callbackFunc, 0);
      }
    }

    function eventListenOnce(elem, name, callback) {
      elem.addEventListener(name, function(e) {
        elem.removeEventListener(name, arguments.callee, false);
        callback(e);
      }, false);
    }

    function focusAndThen(win, callback) {
      eventListenOnce(win, "focus", callback);
      printDebug("focusing a window\n");
      win.focus();
    }

    function eventToString(e) {
      return JSON.stringify({
        type: e.type, target: e.target.nodeName, screenX: e.screenX, screenY: e.screenY
      });
    }

    function clearExpectedEvents() {
      while (gExpectedEvents.length > 0) {
        var expectedEvent = gExpectedEvents.shift();
        var errFun = expectedEvent.shouldFireButDoesnt ? todo : ok;
        errFun(false, "Didn't receive expected event: " + eventToString(expectedEvent));
      }
    }

    var gEventNum = 0;

    function eventMonitor(e) {
      printDebug("got event: " + eventToString(e) + "\n");
      processEvent(e);
    }

    function processEvent(e) {
      if (e.screenX != gCurrentMouseX || e.screenY != gCurrentMouseY) {
        todo(false, "Oh no! Received a stray event from a confused tracking area. Aborting test.");
        onTestsFinished();
        return;
      }
      var expectedEvent = gExpectedEvents.shift();
      if (!expectedEvent) {
        ok(false, "received event I didn't expect: " + eventToString(e));
        return;
      }
      if (e.type != expectedEvent.type) {
        // Didn't get expectedEvent.
        var errFun = expectedEvent.shouldFireButDoesnt ? todo : ok;
        errFun(false, "Didn't receive expected event: " + eventToString(expectedEvent));
        return processEvent(e);
      }
      gEventNum++;
      is(e.screenX, expectedEvent.screenX, gEventNum + " | wrong X coord for event " + eventToString(e));
      is(e.screenY, expectedEvent.screenY, gEventNum + " | wrong Y coord for event " + eventToString(e));
      is(e.target, expectedEvent.target, gEventNum + " | wrong target for event " + eventToString(e));
      if (expectedEvent.firesButShouldnt) {
        todo(false, gEventNum + " | Got an event that should not have fired: " + eventToString(e));
      }
    }

    function observe(elem, fun, add) {
      var addOrRemove = add ? "addEventListener" : "removeEventListener";
      elem[addOrRemove]("mousemove", fun, false);
      elem[addOrRemove]("mouseover", fun, false);
      elem[addOrRemove]("mouseout", fun, false);
      elem[addOrRemove]("mousedown", fun, false);
      elem[addOrRemove]("mouseup", fun, false);
      elem[addOrRemove]("click", fun, false);
    }

    function start() {
      window.resizeTo(200, 200);
      window.moveTo(50, 50);
      gRightWindow = open(xulWin, '', 'chrome,screenX=300,screenY=50,width=200,height=200');
      eventListenOnce(gRightWindow, "focus", function () {
        focusAndThen(window, runTests);
      });
      gPopup = document.getElementById("popup");
    }

    function runTests() {
      observe(window, eventMonitor, true);
      observe(gRightWindow, eventMonitor, true);
      var left = window, right = gRightWindow;
      var leftElem = document.getElementById("box");
      var rightElem = gRightWindow.document.documentElement;
      var panel = document.getElementById("panel");
      var tooltip = (function createTooltipInRightWindow() {
        var _tooltip = right.document.createElementNS(XUL_NS, "tooltip");
        _tooltip.setAttribute("id", "tip");
        _tooltip.setAttribute("width", "80");
        _tooltip.setAttribute("height", "20");
        right.document.documentElement.appendChild(_tooltip);
        return _tooltip;
      })();
      var tests = [

        // Part 1: Disallow click-through

        function blockClickThrough(callback) {
          document.documentElement.setAttribute("clickthrough", "never");
          gRightWindow.document.documentElement.setAttribute("clickthrough", "never");
          callback();
        },
        // Enter the left window, which is focused.
        [150, 150, NSMouseMoved, null, left, [
          { type: "mouseover", target: leftElem },
          { type: "mousemove", target: leftElem }
        ]],
        // Test that moving inside the window fires mousemove events.
        [170, 150, NSMouseMoved, null, left, [
          { type: "mousemove", target: leftElem },
        ]],
        // Leaving the window should fire a mouseout event...
        [170, 20, NSMouseMoved, null, left, [
          { type: "mouseout", target: leftElem },
        ]],
        // ... and entering a mouseover event.
        [170, 120, NSMouseMoved, null, left, [
          { type: "mouseover", target: leftElem },
          { type: "mousemove", target: leftElem },
        ]],
        // Move over the right window, which is inactive.
        // Inactive windows shouldn't respond to mousemove events when clickthrough="never",
        // so we should only get a mouseout event, no mouseover event.
        [400, 150, NSMouseMoved, null, right, [
          { type: "mouseout", target: leftElem },
        ]],
        // Left-clicking while holding Cmd and middle clicking should work even
        // on inactive windows, but without making them active.
        [400, 150, NSLeftMouseDown, null, right, [
          { type: "mousedown", target: rightElem },
        ], NSCommandKeyMask],
        [400, 150, NSLeftMouseUp, null, right, [
          { type: "mouseup", target: rightElem },
          { type: "click", target: rightElem },
        ], NSCommandKeyMask],
        [400, 150, NSOtherMouseDown, null, right, [
          { type: "mousedown", target: rightElem },
        ]],
        [400, 150, NSOtherMouseUp, null, right, [
          { type: "mouseup", target: rightElem },
          { type: "click", target: rightElem },
        ]],
        // Clicking an inactive window should make it active and fire a mouseover
        // event.
        [400, 150, NSLeftMouseDown, null, right, [
          { type: "mouseover", target: rightElem, synthetic: true },
        ]],
        [400, 150, NSLeftMouseUp, null, right, [
        ]],
        // Now it's focused, so we should get a mousedown event when clicking.
        [400, 150, NSLeftMouseDown, null, right, [
          { type: "mousedown", target: rightElem },
        ]],
        // Let's drag to the right without letting the button go.
        [410, 150, NSLeftMouseDragged, null, right, [
          { type: "mousemove", target: rightElem },
        ]],
        // Let go of the mouse.
        [410, 150, NSLeftMouseUp, null, right, [
          { type: "mouseup", target: rightElem },
          { type: "click", target: rightElem },
        ]],
        // Move the mouse back over the left window, which is inactive.
        [150, 170, NSMouseMoved, null, left, [
          { type: "mouseout", target: rightElem },
        ]],
        // Now we're being sneaky. The left window is inactive, but *right*-clicks to it
        // should still get through. Test that.
        // Ideally we'd be bracketing that event with over and out events, too, but it
        // probably doesn't matter too much.
        [150, 170, NSRightMouseDown, null, left, [
          { type: "mouseover", target: leftElem, shouldFireButDoesnt: true },
          { type: "mousedown", target: leftElem },
          { type: "mouseout", target: leftElem, shouldFireButDoesnt: true },
        ]],
        // Let go of the mouse.
        [150, 170, NSRightMouseUp, null, left, [
          { type: "mouseover", target: leftElem, shouldFireButDoesnt: true },
          { type: "mouseup", target: leftElem },
          { type: "click", target: leftElem },
          { type: "mouseout", target: leftElem, shouldFireButDoesnt: true },
        ]],
        // Right clicking hasn't focused it, so the window is still inactive.
        // Let's focus it; this time without the mouse, for variaton's sake.
        // Still, mouseout and mouseover events should fire.
        function raiseLeftWindow(callback) {
          clearExpectedEvents();
          gExpectedEvents.push({ screenX: 150, screenY: 170, type: "mouseover", target: leftElem });
          // We have to be a bit careful here.  The synthetic mouse event may
          // not fire for a bit after we focus the left window.
          eventListenOnce(leftElem, "mouseover", function() {
            // Trigger callback async, so we're not assuming
            // anything about how our listener gets ordered with others.
            SimpleTest.executeSoon(callback);
          });
          printDebug("focusing left window");
          left.focus();
        },
        // It's active, so it should respond to mousemove events now.
        [150, 170, NSMouseMoved, null, left, [
          { type: "mousemove", target: leftElem },
        ]],
        // This was boring... let's introduce a popup. It will overlap both the left
        // and the right window.
        function openPopupInLeftWindow(callback) {
          eventListenOnce(gPopup, "popupshown", callback);
          gPopup.openPopupAtScreen(150, 50, true);
        },
        // Move the mouse over the popup.
        [200, 80, NSMouseMoved, gPopup, left, [
          { type: "mouseout", target: leftElem },
          { type: "mouseover", target: gPopup },
          { type: "mousemove", target: gPopup },
        ]],
        // Move the mouse back over the left window outside the popup.
        [160, 170, NSMouseMoved, null, left, [
          { type: "mouseout", target: gPopup },
          { type: "mouseover", target: leftElem },
          { type: "mousemove", target: leftElem },
        ]],
        // Back over the popup...
        [190, 80, NSMouseMoved, gPopup, left, [
          { type: "mouseout", target: leftElem },
          { type: "mouseover", target: gPopup },
          { type: "mousemove", target: gPopup },
        ]],
        // ...and over into the right window.
        // It's inactive, so it shouldn't get mouseover events yet.
        [400, 170, NSMouseMoved, null, right, [
          { type: "mouseout", target: gPopup },
        ]],
        // Again, no mouse events please, even though a popup is open. (bug 425556)
        [400, 180, NSMouseMoved, null, right, [
        ]],
        // Activate the right window with a click.
        // This will close the popup and make the mouse enter the right window.
        [400, 180, NSLeftMouseDown, null, right, [
          { type: "mouseover", target: rightElem, synthetic: true },
        ]],
        [400, 180, NSLeftMouseUp, null, right, [
        ]],
        function verifyPopupClosed2(callback) {
          is(gPopup.state, "closed", "popup should have closed when clicking");
          callback();
        },
        // Now the right window is active; click it again, just for fun.
        [400, 180, NSLeftMouseDown, null, right, [
          { type: "mousedown", target: rightElem },
        ]],
        [400, 180, NSLeftMouseUp, null, right, [
          { type: "mouseup", target: rightElem },
          { type: "click", target: rightElem },
        ]],

        // Time for our next trick: a tooltip!
        // Install the tooltip, but don't show it yet.
        function setTooltip(callback) {
          rightElem.setAttribute("tooltip", "tip");
          gExpectedEvents.push({ screenX: 410, screenY: 180, type: "mousemove", target: rightElem });
          eventListenOnce(rightElem, "popupshown", callback);
          gCurrentMouseX = 410;
          gCurrentMouseY = 180;
          var utils = right.windowUtils;
          utils.sendNativeMouseEvent(410, 180, NSMouseMoved, 0, null);
        },
        // Now the tooltip is visible.
        // Move the mouse a little to the right.
        [411, 180, NSMouseMoved, null, right, [
          { type: "mousemove", target: rightElem },
        ]],
        // Move another pixel.
        [412, 180, NSMouseMoved, null, right, [
          { type: "mousemove", target: rightElem },
        ]],
        // Move up and click to make the tooltip go away.
        [412, 80, NSMouseMoved, null, right, [
          { type: "mousemove", target: rightElem },
        ]],
        [412, 80, NSLeftMouseDown, null, right, [
          { type: "mousedown", target: rightElem },
        ]],
        [412, 80, NSLeftMouseUp, null, right, [
          { type: "mouseup", target: rightElem },
          { type: "click", target: rightElem },
        ]],
        // OK, next round. Open a panel in the left window, which is inactive.
        function openPanel(callback) {
          eventListenOnce(panel, "popupshown", callback);
          panel.openPopupAtScreen(150, 150, false);
        },
        // The panel is parented, so it will be z-ordered over its parent but
        // under the active window.
        // Now we move the mouse over the part where the panel rect intersects the
        // right window's rect. Since the panel is under the window, all the events
        // should target the right window.
        [390, 170, NSMouseMoved, null, right, [
          { type: "mousemove", target: rightElem },
        ]],
        [390, 171, NSMouseMoved, null, right, [
          { type: "mousemove", target: rightElem },
        ]],
        [391, 171, NSMouseMoved, null, right, [
          { type: "mousemove", target: rightElem },
        ]],
        // Now move off the right window, so that the mouse is directly over the
        // panel.
        [260, 170, NSMouseMoved, panel, left, [
          { type: "mouseout", target: rightElem },
        ]],
        [260, 171, NSMouseMoved, panel, left, [
        ]],
        [261, 171, NSMouseMoved, panel, left, [
        ]],
        // Let's be evil and click it.
        [261, 171, NSLeftMouseDown, panel, left, [
        ]],
        [261, 171, NSLeftMouseUp, panel, left, [
        ]],
        // This didn't focus the window, unfortunately, so let's do it ourselves.
        function raiseLeftWindowTakeTwo(callback) {
          focusAndThen(left, callback);
        },
        // Now mouse events should get through to the panel (which is now over the
        // right window).
        [387, 170, NSMouseMoved, panel, left, [
          { type: "mouseover", target: panel },
          { type: "mousemove", target: panel },
        ]],
        [387, 171, NSMouseMoved, panel, left, [
          { type: "mousemove", target: panel },
        ]],
        [388, 171, NSMouseMoved, panel, left, [
          { type: "mousemove", target: panel },
        ]],
        // Click the panel.
        [388, 171, NSLeftMouseDown, panel, left, [
          { type: "mousedown", target: panel }
        ]],
        [388, 171, NSLeftMouseUp, panel, left, [
          { type: "mouseup", target: panel },
          { type: "click", target: panel },
        ]],

        // Last test for this part: Hit testing in the Canyon of Nowhere -
        // the pixel row directly south of the panel, over the left window.
        // Before bug 515003 we wrongly thought the mouse wasn't over any window.
        [173, 200, NSMouseMoved, null, left, [
          { type: "mouseout", target: panel },
          { type: "mouseover", target: leftElem },
          { type: "mousemove", target: leftElem },
        ]],
        [173, 201, NSMouseMoved, null, left, [
          { type: "mousemove", target: leftElem },
        ]],

        // Part 2: Allow click-through

        function hideThatPanel(callback) {
          eventListenOnce(panel, "popuphidden", callback);
          panel.hidePopup();
        },
        function unblockClickThrough(callback) {
          document.documentElement.removeAttribute("clickthrough");
          gRightWindow.document.documentElement.removeAttribute("clickthrough");
          callback();
        },
        // Enter the left window, which is focused.
        [150, 150, NSMouseMoved, null, left, [
          { type: "mousemove", target: leftElem }
        ]],
        // Test that moving inside the window fires mousemove events.
        [170, 150, NSMouseMoved, null, left, [
          { type: "mousemove", target: leftElem },
        ]],
        // Leaving the window should fire a mouseout event...
        [170, 20, NSMouseMoved, null, left, [
          { type: "mouseout", target: leftElem },
        ]],
        // ... and entering a mouseover event.
        [170, 120, NSMouseMoved, null, left, [
          { type: "mouseover", target: leftElem },
          { type: "mousemove", target: leftElem },
        ]],
        // Move over the right window, which is inactive but still accepts
        // mouse events.
        [400, 150, NSMouseMoved, null, right, [
          { type: "mouseout", target: leftElem },
          { type: "mouseover", target: rightElem },
          { type: "mousemove", target: rightElem },
        ]],
        // Left-clicking while holding Cmd and middle clicking should work
        // on inactive windows, but without making them active.
        [400, 150, NSLeftMouseDown, null, right, [
          { type: "mousedown", target: rightElem },
        ], NSCommandKeyMask],
        [400, 150, NSLeftMouseUp, null, right, [
          { type: "mouseup", target: rightElem },
          { type: "click", target: rightElem },
        ], NSCommandKeyMask],
        [400, 150, NSOtherMouseDown, null, right, [
          { type: "mousedown", target: rightElem },
        ]],
        [400, 150, NSOtherMouseUp, null, right, [
          { type: "mouseup", target: rightElem },
          { type: "click", target: rightElem },
        ]],
        // Clicking an inactive window should make it active
        [400, 150, NSLeftMouseDown, null, right, [
          { type: "mousedown", target: rightElem },
        ]],
        [400, 150, NSLeftMouseUp, null, right, [
          { type: "mouseup", target: rightElem },
          { type: "click", target: rightElem },
        ]],
        // Now it's focused.
        [401, 150, NSLeftMouseDown, null, right, [
          { type: "mousedown", target: rightElem },
        ]],
        // Let's drag to the right without letting the button go.
        [410, 150, NSLeftMouseDragged, null, right, [
          { type: "mousemove", target: rightElem },
        ]],
        // Let go of the mouse.
        [410, 150, NSLeftMouseUp, null, right, [
          { type: "mouseup", target: rightElem },
          { type: "click", target: rightElem },
        ]],
        // Move the mouse back over the left window, which is inactive.
        [150, 170, NSMouseMoved, null, left, [
          { type: "mouseout", target: rightElem },
          { type: "mouseover", target: leftElem },
          { type: "mousemove", target: leftElem },
        ]],
        // Right-click it.
        [150, 170, NSRightMouseDown, null, left, [
          { type: "mousedown", target: leftElem },
        ]],
        // Let go of the mouse.
        [150, 170, NSRightMouseUp, null, left, [
          { type: "mouseup", target: leftElem },
          { type: "click", target: leftElem },
        ]],
        // Right clicking hasn't focused it, so the window is still inactive.
        // Let's focus it; this time without the mouse, for variaton's sake.
        function raiseLeftWindow(callback) {
          clearExpectedEvents();
          focusAndThen(left, function () { SimpleTest.executeSoon(callback); });
        },
        // It's active and should still respond to mousemove events.
        [150, 170, NSMouseMoved, null, left, [
          { type: "mousemove", target: leftElem },
        ]],

        // This was boring... let's introduce a popup. It will overlap both the left
        // and the right window.
        function openPopupInLeftWindow(callback) {
          eventListenOnce(gPopup, "popupshown", callback);
          gPopup.openPopupAtScreen(150, 50, true);
        },
        // Move the mouse over the popup.
        [200, 80, NSMouseMoved, gPopup, left, [
          { type: "mouseout", target: leftElem },
          { type: "mouseover", target: gPopup },
          { type: "mousemove", target: gPopup },
        ]],
        // Move the mouse back over the left window outside the popup.
        [160, 170, NSMouseMoved, null, left, [
          { type: "mouseout", target: gPopup },
          { type: "mouseover", target: leftElem },
          { type: "mousemove", target: leftElem },
        ]],
        // Back over the popup...
        [190, 80, NSMouseMoved, gPopup, left, [
          { type: "mouseout", target: leftElem },
          { type: "mouseover", target: gPopup },
          { type: "mousemove", target: gPopup },
        ]],
        // ...and over into the right window.
        [400, 170, NSMouseMoved, null, right, [
          { type: "mouseout", target: gPopup },
          { type: "mouseover", target: rightElem },
          { type: "mousemove", target: rightElem },
        ]],
        [400, 180, NSMouseMoved, null, right, [
          { type: "mousemove", target: rightElem },
        ]],
        // Activate the right window with a click.
        [400, 180, NSLeftMouseDown, null, right, [
          { type: "mousedown", target: rightElem },
        ]],
        [400, 180, NSLeftMouseUp, null, right, [
          { type: "mouseup", target: rightElem },
          { type: "click", target: rightElem },
        ]],
        function verifyPopupClosed2(callback) {
          is(gPopup.state, "closed", "popup should have closed when clicking");
          callback();
        },
        // Now the right window is active; click it again, just for fun.
        [400, 180, NSLeftMouseDown, null, right, [
          { type: "mousedown", target: rightElem },
        ]],
        [400, 180, NSLeftMouseUp, null, right, [
          { type: "mouseup", target: rightElem },
          { type: "click", target: rightElem },
        ]],

        // Time for our next trick: a tooltip!
        // Install the tooltip, but don't show it yet.
        function setTooltip2(callback) {
          rightElem.setAttribute("tooltip", "tip");
          gExpectedEvents.push({ screenX: 410, screenY: 180, type: "mousemove", target: rightElem });
          eventListenOnce(rightElem, "popupshown", callback);
          gCurrentMouseX = 410;
          gCurrentMouseY = 180;
          var utils = right.windowUtils;
          utils.sendNativeMouseEvent(410, 180, NSMouseMoved, 0, null);
        },
        // Now the tooltip is visible.
        // Move the mouse a little to the right.
        [411, 180, NSMouseMoved, null, right, [
          { type: "mousemove", target: rightElem },
        ]],
        // Move another pixel.
        [412, 180, NSMouseMoved, null, right, [
          { type: "mousemove", target: rightElem },
        ]],
        // Move up and click to make the tooltip go away.
        [412, 80, NSMouseMoved, null, right, [
          { type: "mousemove", target: rightElem },
        ]],
        [412, 80, NSLeftMouseDown, null, right, [
          { type: "mousedown", target: rightElem },
        ]],
        [412, 80, NSLeftMouseUp, null, right, [
          { type: "mouseup", target: rightElem },
          { type: "click", target: rightElem },
        ]],
        // OK, next round. Open a panel in the left window, which is inactive.
        function openPanel2(callback) {
          eventListenOnce(panel, "popupshown", callback);
          panel.openPopupAtScreen(150, 150, false);
        },
        // The panel is parented, so it will be z-ordered over its parent but
        // under the active window.
        // Now we move the mouse over the part where the panel rect intersects the
        // right window's rect. Since the panel is under the window, all the events
        // should target the right window.
        [390, 170, NSMouseMoved, null, right, [
          { type: "mousemove", target: rightElem },
        ]],
        [390, 171, NSMouseMoved, null, right, [
          { type: "mousemove", target: rightElem },
        ]],
        [391, 171, NSMouseMoved, null, right, [
          { type: "mousemove", target: rightElem },
        ]],
        // Now move off the right window, so that the mouse is directly over the
        // panel.
        [260, 170, NSMouseMoved, panel, left, [
          { type: "mouseout", target: rightElem },
          { type: "mouseover", target: panel },
          { type: "mousemove", target: panel },
        ]],
        [260, 171, NSMouseMoved, panel, left, [
          { type: "mousemove", target: panel },
        ]],
        [261, 171, NSMouseMoved, panel, left, [
          { type: "mousemove", target: panel },
        ]],
        // Let's be evil and click it.
        [261, 171, NSLeftMouseDown, panel, left, [
          { type: "mousedown", target: panel },
        ]],
        [261, 171, NSLeftMouseUp, panel, left, [
          { type: "mouseup", target: panel },
          { type: "click", target: panel },
        ]],
        // This didn't focus the window, unfortunately, so let's do it ourselves.
        function raiseLeftWindowTakeTwo(callback) {
          focusAndThen(left, callback);
        },
        [387, 170, NSMouseMoved, panel, left, [
          { type: "mousemove", target: panel },
        ]],
        [387, 171, NSMouseMoved, panel, left, [
          { type: "mousemove", target: panel },
        ]],
        [388, 171, NSMouseMoved, panel, left, [
          { type: "mousemove", target: panel },
        ]],
        // Click the panel.
        [388, 171, NSLeftMouseDown, panel, left, [
          { type: "mousedown", target: panel }
        ]],
        [388, 171, NSLeftMouseUp, panel, left, [
          { type: "mouseup", target: panel },
          { type: "click", target: panel },
        ]],

        // Last test for today: Hit testing in the Canyon of Nowhere -
        // the pixel row directly south of the panel, over the left window.
        // Before bug 515003 we wrongly thought the mouse wasn't over any window.
        [173, 200, NSMouseMoved, null, left, [
          { type: "mouseout", target: panel },
          { type: "mouseover", target: leftElem },
          { type: "mousemove", target: leftElem },
        ]],
        [173, 201, NSMouseMoved, null, left, [
          { type: "mousemove", target: leftElem },
        ]],
      ];
      function runNextTest() {
        if (!tests.length)
          return onTestsFinished();

        var test = tests.shift();
        if (typeof test == "function")
          return test(runNextTest);

        var [x, y, msg, elem, win, exp, flags] = test;
        testMouse(x, y, msg, elem, win, exp, flags, runNextTest);
      }
      runNextTest();
    }

    SimpleTest.waitForFocus(start);

  ]]></script>
</window>
