/** * EventUtils provides some utility methods for creating and sending DOM events. * Current methods: * sendMouseEvent * sendChar * sendString * sendKey */ /** * Send a mouse event to the node with id aTarget. The "event" passed in to * aEvent is just a JavaScript object with the properties set that the real * mouse event object should have. This includes the type of the mouse event. * E.g. to send an click event to the node with id 'node' you might do this: * * sendMouseEvent({type:'click'}, 'node'); */ function sendMouseEvent(aEvent, aTarget, aWindow) { if (['click', 'mousedown', 'mouseup', 'mouseover', 'mouseout'].indexOf(aEvent.type) == -1) { throw new Error("sendMouseEvent doesn't know about event type '"+aEvent.type+"'"); } if (!aWindow) { aWindow = window; } // For events to trigger the UA's default actions they need to be "trusted" netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserWrite'); var event = aWindow.document.createEvent('MouseEvent'); var typeArg = aEvent.type; var canBubbleArg = true; var cancelableArg = true; var viewArg = aWindow; var detailArg = aEvent.detail || (aEvent.type == 'click' || aEvent.type == 'mousedown' || aEvent.type == 'mouseup' ? 1 : 0); var screenXArg = aEvent.screenX || 0; var screenYArg = aEvent.screenY || 0; var clientXArg = aEvent.clientX || 0; var clientYArg = aEvent.clientY || 0; var ctrlKeyArg = aEvent.ctrlKey || false; var altKeyArg = aEvent.altKey || false; var shiftKeyArg = aEvent.shiftKey || false; var metaKeyArg = aEvent.metaKey || false; var buttonArg = aEvent.button || 0; var relatedTargetArg = aEvent.relatedTarget || null; event.initMouseEvent(typeArg, canBubbleArg, cancelableArg, viewArg, detailArg, screenXArg, screenYArg, clientXArg, clientYArg, ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg, buttonArg, relatedTargetArg); aWindow.document.getElementById(aTarget).dispatchEvent(event); } /** * Send the char aChar to the node with id aTarget. If aTarget is not * provided, use "target". This method handles casing of chars (sends the * right charcode, and sends a shift key for uppercase chars). No other * modifiers are handled at this point. * * For now this method only works for English letters (lower and upper case) * and the digits 0-9. * * Returns true if the keypress event was accepted (no calls to preventDefault * or anything like that), false otherwise. */ function sendChar(aChar, aTarget) { // DOM event charcodes match ASCII (JS charcodes) for a-zA-Z0-9. var hasShift = (aChar == aChar.toUpperCase()); var charCode = aChar.charCodeAt(0); var keyCode = charCode; if (!hasShift) { // For lowercase letters, the keyCode is actually 32 less than the charCode keyCode -= 0x20; } return __doEventDispatch(aTarget, charCode, keyCode, hasShift); } /** * Send the string aStr to the node with id aTarget. If aTarget is not * provided, use "target". * * For now this method only works for English letters (lower and upper case) * and the digits 0-9. */ function sendString(aStr, aTarget) { for (var i = 0; i < aStr.length; ++i) { sendChar(aStr.charAt(i), aTarget); } } /** * Send the non-character key aKey to the node with id aTarget. If aTarget is * not provided, use "target". The name of the key should be a lowercase * version of the part that comes after "DOM_VK_" in the KeyEvent constant * name for this key. No modifiers are handled at this point. * * Returns true if the keypress event was accepted (no calls to preventDefault * or anything like that), false otherwise. */ function sendKey(aKey, aTarget) { keyName = "DOM_VK_" + aKey.toUpperCase(); if (!KeyEvent[keyName]) { throw "Unknown key: " + keyName; } return __doEventDispatch(aTarget, 0, KeyEvent[keyName], false); } /** * Actually perform event dispatch given a charCode, keyCode, and boolean for * whether "shift" was pressed. Send the event to the node with id aTarget. If * aTarget is not provided, use "target". * * Returns true if the keypress event was accepted (no calls to preventDefault * or anything like that), false otherwise. */ function __doEventDispatch(aTarget, aCharCode, aKeyCode, aHasShift) { if (aTarget === undefined) { aTarget = "target"; } // Make our events trusted netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); var event = document.createEvent("KeyEvents"); event.initKeyEvent("keydown", true, true, document.defaultView, false, false, aHasShift, false, aKeyCode, 0); var accepted = $(aTarget).dispatchEvent(event); // Preventing the default keydown action also prevents the default // keypress action. event = document.createEvent("KeyEvents"); if (aCharCode) { event.initKeyEvent("keypress", true, true, document.defaultView, false, false, aHasShift, false, 0, aCharCode); } else { event.initKeyEvent("keypress", true, true, document.defaultView, false, false, aHasShift, false, aKeyCode, 0); } if (!accepted) { event.preventDefault(); } accepted = $(aTarget).dispatchEvent(event); // Always send keyup var event = document.createEvent("KeyEvents"); event.initKeyEvent("keyup", true, true, document.defaultView, false, false, aHasShift, false, aKeyCode, 0); $(aTarget).dispatchEvent(event); return accepted; } /** * Parse the key modifier flags from aEvent. Used to share code between * synthesizeMouse and synthesizeKey. */ function _parseModifiers(aEvent) { const masks = Components.interfaces.nsIDOMNSEvent; var mval = 0; if (aEvent.shiftKey) mval |= masks.SHIFT_MASK; if (aEvent.ctrlKey) mval |= masks.CONTROL_MASK; if (aEvent.altKey) mval |= masks.ALT_MASK; if (aEvent.metaKey) mval |= masks.META_MASK; if (aEvent.accelKey) mval |= (navigator.platform.indexOf("Mac") >= 0) ? masks.META_MASK : masks.CONTROL_MASK; return mval; } /** * Synthesize a mouse event on a target. The actual client point is determined * by taking the aTarget's client box and offseting it by aOffsetX and * aOffsetY. This allows mouse clicks to be simulated by calling this method. * * aEvent is an object which may contain the properties: * shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type * * If the type is specified, an mouse event of that type is fired. Otherwise, * a mousedown followed by a mouse up is performed. * * aWindow is optional, and defaults to the current window object. */ function synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow) { netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); if (!aWindow) aWindow = window; var utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor). getInterface(Components.interfaces.nsIDOMWindowUtils); if (utils) { var button = aEvent.button || 0; var clickCount = aEvent.clickCount || 1; var modifiers = _parseModifiers(aEvent); var rect = aTarget.getBoundingClientRect(); var left = rect.left + aOffsetX; var top = rect.top + aOffsetY; if (aEvent.type) { utils.sendMouseEvent(aEvent.type, left, top, button, clickCount, modifiers); } else { utils.sendMouseEvent("mousedown", left, top, button, clickCount, modifiers); utils.sendMouseEvent("mouseup", left, top, button, clickCount, modifiers); } } } /** * Synthesize a mouse scroll event on a target. The actual client point is determined * by taking the aTarget's client box and offseting it by aOffsetX and * aOffsetY. * * aEvent is an object which may contain the properties: * shiftKey, ctrlKey, altKey, metaKey, accessKey, button, type, axis, delta, hasPixels * * If the type is specified, a mouse scroll event of that type is fired. Otherwise, * "DOMMouseScroll" is used. * * If the axis is specified, it must be one of "horizontal" or "vertical". If not specified, * "vertical" is used. * * 'delta' is the amount to scroll by (can be positive or negative). It must * be specified. * * 'hasPixels' specifies whether kHasPixels should be set in the scrollFlags. * * aWindow is optional, and defaults to the current window object. */ function synthesizeMouseScroll(aTarget, aOffsetX, aOffsetY, aEvent, aWindow) { netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); if (!aWindow) aWindow = window; var utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor). getInterface(Components.interfaces.nsIDOMWindowUtils); if (utils) { // See nsMouseScrollFlags in nsGUIEvent.h const kIsVertical = 0x02; const kIsHorizontal = 0x04; const kHasPixels = 0x08; var button = aEvent.button || 0; var modifiers = _parseModifiers(aEvent); var rect = aTarget.getBoundingClientRect(); var left = rect.left; var top = rect.top; var type = aEvent.type || "DOMMouseScroll"; var axis = aEvent.axis || "vertical"; var scrollFlags = (axis == "horizontal") ? kIsHorizontal : kIsVertical; if (aEvent.hasPixels) { scrollFlags |= kHasPixels; } utils.sendMouseScrollEvent(type, left + aOffsetX, top + aOffsetY, button, scrollFlags, aEvent.delta, modifiers); } } /** * Synthesize a key event. It is targeted at whatever would be targeted by an * actual keypress by the user, typically the focused element. * * aKey should be either a character or a keycode starting with VK_ such as * VK_ENTER. * * aEvent is an object which may contain the properties: * shiftKey, ctrlKey, altKey, metaKey, accessKey, type * * If the type is specified, a key event of that type is fired. Otherwise, * a keydown, a keypress and then a keyup event are fired in sequence. * * aWindow is optional, and defaults to the current window object. */ function synthesizeKey(aKey, aEvent, aWindow) { netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); if (!aWindow) aWindow = window; var utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor). getInterface(Components.interfaces.nsIDOMWindowUtils); if (utils) { var keyCode = 0, charCode = 0; if (aKey.indexOf("VK_") == 0) keyCode = KeyEvent["DOM_" + aKey]; else charCode = aKey.charCodeAt(0); var modifiers = _parseModifiers(aEvent); if (aEvent.type) { utils.sendKeyEvent(aEvent.type, keyCode, charCode, modifiers); } else { var keyDownDefaultHappened = utils.sendKeyEvent("keydown", keyCode, charCode, modifiers); utils.sendKeyEvent("keypress", keyCode, charCode, modifiers, !keyDownDefaultHappened); utils.sendKeyEvent("keyup", keyCode, charCode, modifiers); } } } var _gSeenEvent = false; /** * Indicate that an event with an original target of aExpectedTarget and * a type of aExpectedEvent is expected to be fired, or not expected to * be fired. */ function _expectEvent(aExpectedTarget, aExpectedEvent, aTestName) { if (!aExpectedTarget || !aExpectedEvent) return null; _gSeenEvent = false; var type = (aExpectedEvent.charAt(0) == "!") ? aExpectedEvent.substring(1) : aExpectedEvent; var eventHandler = function(event) { var epassed = (!_gSeenEvent && event.originalTarget == aExpectedTarget && event.type == type); is(epassed, true, aTestName + " " + type + " event target " + (_gSeenEvent ? "twice" : "")); _gSeenEvent = true; }; aExpectedTarget.addEventListener(type, eventHandler, false); return eventHandler; } /** * Check if the event was fired or not. The event handler aEventHandler * will be removed. */ function _checkExpectedEvent(aExpectedTarget, aExpectedEvent, aEventHandler, aTestName) { if (aEventHandler) { var expectEvent = (aExpectedEvent.charAt(0) != "!"); var type = expectEvent ? aExpectedEvent : aExpectedEvent.substring(1); aExpectedTarget.removeEventListener(type, aEventHandler, false); var desc = type + " event"; if (!expectEvent) desc += " not"; is(_gSeenEvent, expectEvent, aTestName + " " + desc + " fired"); } _gSeenEvent = false; } /** * Similar to synthesizeMouse except that a test is performed to see if an * event is fired at the right target as a result. * * aExpectedTarget - the expected originalTarget of the event. * aExpectedEvent - the expected type of the event, such as 'select'. * aTestName - the test name when outputing results * * To test that an event is not fired, use an expected type preceded by an * exclamation mark, such as '!select'. This might be used to test that a * click on a disabled element doesn't fire certain events for instance. * * aWindow is optional, and defaults to the current window object. */ function synthesizeMouseExpectEvent(aTarget, aOffsetX, aOffsetY, aEvent, aExpectedTarget, aExpectedEvent, aTestName, aWindow) { var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName); synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow); _checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName); } /** * Similar to synthesizeKey except that a test is performed to see if an * event is fired at the right target as a result. * * aExpectedTarget - the expected originalTarget of the event. * aExpectedEvent - the expected type of the event, such as 'select'. * aTestName - the test name when outputing results * * To test that an event is not fired, use an expected type preceded by an * exclamation mark, such as '!select'. * * aWindow is optional, and defaults to the current window object. */ function synthesizeKeyExpectEvent(key, aEvent, aExpectedTarget, aExpectedEvent, aTestName, aWindow) { var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName); synthesizeKey(key, aEvent, aWindow); _checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName); } /** * Emulate a dragstart event. * element - element to fire the dragstart event on * expectedDragData - the data you expect the data transfer to contain afterwards * This data is in the format: * [ [ "type: data", "type: data" ], ... ] * Returns the expected data in the same format if it is not correct. Returns null * if successful. */ function synthesizeDragStart(element, expectedDragData) { var failed = null; var trapDrag = function(event) { try { var dataTransfer = event.dataTransfer; if (dataTransfer.mozItemCount != expectedDragData.length) throw "Failed"; for (var t = 0; t < dataTransfer.mozItemCount; t++) { var types = dataTransfer.mozTypesAt(t); var expecteditem = expectedDragData[t]; if (types.length != expecteditem.length) throw "Failed"; for (var f = 0; f < types.length; f++) { if (types[f] != expecteditem[f].substring(0, types[f].length) || dataTransfer.mozGetDataAt(types[f], t) != expecteditem[f].substring(types[f].length + 2)) throw "Failed"; } } } catch(ex) { failed = dataTransfer; } event.preventDefault(); event.stopPropagation(); } window.addEventListener("dragstart", trapDrag, false); synthesizeMouse(element, 2, 2, { type: "mousedown" }); synthesizeMouse(element, 9, 9, { type: "mousemove" }); synthesizeMouse(element, 10, 10, { type: "mousemove" }); window.removeEventListener("dragstart", trapDrag, false); synthesizeMouse(element, 10, 10, { type: "mouseup" }); return failed; } /** * Emulate a drop by firing a dragover, dragexit and a drop event. * element - the element to fire the dragover, dragexit and drop events on * dragData - the data to supply for the data transfer * This data is in the format: * [ [ "type: data", "type: data" ], ... ] * effectAllowed - the allowed effects that the dragstart event would have set * * Returns the drop effect that was desired. */ function synthesizeDrop(element, dragData, effectAllowed) { var dataTransfer; var trapDrag = function(event) { dataTransfer = event.dataTransfer; for (var t = 0; t < dragData.length; t++) { var item = dragData[t]; for (var v = 0; v < item.length; v++) { var idx = item[v].indexOf(":"); dataTransfer.mozSetDataAt(item[v].substring(0, idx), item[v].substring(idx + 2), t); } } dataTransfer.dropEffect = "move"; event.preventDefault(); event.stopPropagation(); } // need to use a real window.addEventListener("dragstart", trapDrag, true); synthesizeMouse(element, 2, 2, { type: "mousedown" }); synthesizeMouse(element, 9, 9, { type: "mousemove" }); synthesizeMouse(element, 10, 10, { type: "mousemove" }); window.removeEventListener("dragstart", trapDrag, true); synthesizeMouse(element, 10, 10, { type: "mouseup" }); var event = document.createEvent("DragEvents"); event.initDragEvent("dragover", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer); if (element.dispatchEvent(event)) return "none"; event = document.createEvent("DragEvents"); event.initDragEvent("dragexit", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer); element.dispatchEvent(event); if (dataTransfer.dropEffect != "none") { event = document.createEvent("DragEvents"); event.initDragEvent("drop", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer); element.dispatchEvent(event); } return dataTransfer.dropEffect; } function disableNonTestMouseEvents(aDisable) { netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor). getInterface(Components.interfaces.nsIDOMWindowUtils); if (utils) utils.disableNonTestMouseEvents(aDisable); }