/* 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/. */ /** * ChromeUtils.js is a set of mochitest utilities that are used to * synthesize events in the browser. These are only used by * mochitest-chrome and browser-chrome tests. Originally these functions were in * EventUtils.js, but when porting to specialPowers, we didn't want * to move unnecessary functions. * * ChromeUtils.js depends on EventUtils.js being loaded. * */ /** * Synthesize a query text content event. * * @param aOffset The character offset. 0 means the first character in the * selection root. * @param aLength The length of getting text. If the length is too long, * the extra length is ignored. * @param aWindow Optional (If null, current |window| will be used) * @return An nsIQueryContentEventResult object. If this failed, * the result might be null. */ function synthesizeQueryTextContent(aOffset, aLength, aWindow) { var utils = _getDOMWindowUtils(aWindow); if (!utils) { return nullptr; } return utils.sendQueryContentEvent(utils.QUERY_TEXT_CONTENT, aOffset, aLength, 0, 0); } /** * Synthesize a query caret rect event. * * @param aOffset The caret offset. 0 means left side of the first character * in the selection root. * @param aWindow Optional (If null, current |window| will be used) * @return An nsIQueryContentEventResult object. If this failed, * the result might be null. */ function synthesizeQueryCaretRect(aOffset, aWindow) { var utils = _getDOMWindowUtils(aWindow); if (!utils) { return nullptr; } return utils.sendQueryContentEvent(utils.QUERY_CARET_RECT, aOffset, 0, 0, 0); } /** * Synthesize a query text rect event. * * @param aOffset The character offset. 0 means the first character in the * selection root. * @param aLength The length of the text. If the length is too long, * the extra length is ignored. * @param aWindow Optional (If null, current |window| will be used) * @return An nsIQueryContentEventResult object. If this failed, * the result might be null. */ function synthesizeQueryTextRect(aOffset, aLength, aWindow) { var utils = _getDOMWindowUtils(aWindow); if (!utils) { return nullptr; } return utils.sendQueryContentEvent(utils.QUERY_TEXT_RECT, aOffset, aLength, 0, 0); } /** * Synthesize a query editor rect event. * * @param aWindow Optional (If null, current |window| will be used) * @return An nsIQueryContentEventResult object. If this failed, * the result might be null. */ function synthesizeQueryEditorRect(aWindow) { var utils = _getDOMWindowUtils(aWindow); if (!utils) { return nullptr; } return utils.sendQueryContentEvent(utils.QUERY_EDITOR_RECT, 0, 0, 0, 0); } /** * Synthesize a character at point event. * * @param aX, aY The offset in the client area of the DOM window. * @param aWindow Optional (If null, current |window| will be used) * @return An nsIQueryContentEventResult object. If this failed, * the result might be null. */ function synthesizeCharAtPoint(aX, aY, aWindow) { var utils = _getDOMWindowUtils(aWindow); if (!utils) { return nullptr; } return utils.sendQueryContentEvent(utils.QUERY_CHARACTER_AT_POINT, 0, 0, aX, aY); } /** * 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: value, data: value, test: function}, ... ], ... ] * can be null * aWindow - optional; defaults to the current window object. * x - optional; initial x coordinate * y - optional; initial y coordinate * Returns null if data matches. * Returns the event.dataTransfer if data does not match * * eqTest is an optional function if comparison can't be done with x == y; * function (actualData, expectedData) {return boolean} * @param actualData from dataTransfer * @param expectedData from expectedDragData * see bug 462172 for example of use * */ function synthesizeDragStart(element, expectedDragData, aWindow, x, y) { if (!aWindow) aWindow = window; x = x || 2; y = y || 2; const step = 9; var result = "trapDrag was not called"; var trapDrag = function(event) { try { var dataTransfer = event.dataTransfer; result = null; if (!dataTransfer) throw "no dataTransfer"; if (expectedDragData == null || dataTransfer.mozItemCount != expectedDragData.length) throw dataTransfer; for (var i = 0; i < dataTransfer.mozItemCount; i++) { var dtTypes = dataTransfer.mozTypesAt(i); if (dtTypes.length != expectedDragData[i].length) throw dataTransfer; for (var j = 0; j < dtTypes.length; j++) { if (dtTypes[j] != expectedDragData[i][j].type) throw dataTransfer; var dtData = dataTransfer.mozGetDataAt(dtTypes[j],i); if (expectedDragData[i][j].eqTest) { if (!expectedDragData[i][j].eqTest(dtData, expectedDragData[i][j].data)) throw dataTransfer; } else if (expectedDragData[i][j].data != dtData) throw dataTransfer; } } } catch(ex) { result = ex; } event.preventDefault(); event.stopPropagation(); } aWindow.addEventListener("dragstart", trapDrag, false); synthesizeMouse(element, x, y, { type: "mousedown" }, aWindow); x += step; y += step; synthesizeMouse(element, x, y, { type: "mousemove" }, aWindow); x += step; y += step; synthesizeMouse(element, x, y, { type: "mousemove" }, aWindow); aWindow.removeEventListener("dragstart", trapDrag, false); synthesizeMouse(element, x, y, { type: "mouseup" }, aWindow); return result; } /** * Emulate a drop by emulating a dragstart and firing events dragenter, dragover, and drop. * srcElement - the element to use to start the drag, usually the same as destElement * but if destElement isn't suitable to start a drag on pass a suitable * element for srcElement * destElement - the element to fire the dragover, dragleave and drop events * dragData - the data to supply for the data transfer * This data is in the format: * [ [ {type: value, data: value}, ...], ... ] * dropEffect - the drop effect to set during the dragstart event, or 'move' if null * aWindow - optional; defaults to the current window object. * eventUtils - optional; allows you to pass in a reference to EventUtils.js. * If the eventUtils parameter is not passed in, we assume EventUtils.js is * in the scope. Used by browser-chrome tests. * * Returns the drop effect that was desired. */ function synthesizeDrop(srcElement, destElement, dragData, dropEffect, aWindow, eventUtils) { if (!aWindow) aWindow = window; var synthesizeMouseAtCenter = (eventUtils || window).synthesizeMouseAtCenter; var synthesizeMouse = (eventUtils || window).synthesizeMouse; var gWindowUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor). getInterface(Components.interfaces.nsIDOMWindowUtils); var ds = Components.classes["@mozilla.org/widget/dragservice;1"]. getService(Components.interfaces.nsIDragService); var dataTransfer; var trapDrag = function(event) { dataTransfer = event.dataTransfer; for (var i = 0; i < dragData.length; i++) { var item = dragData[i]; for (var j = 0; j < item.length; j++) { dataTransfer.mozSetDataAt(item[j].type, item[j].data, i); } } dataTransfer.dropEffect = dropEffect || "move"; event.preventDefault(); event.stopPropagation(); } ds.startDragSession(); try { // need to use real mouse action aWindow.addEventListener("dragstart", trapDrag, true); synthesizeMouseAtCenter(srcElement, { type: "mousedown" }, aWindow); var rect = srcElement.getBoundingClientRect(); var x = rect.width / 2; var y = rect.height / 2; synthesizeMouse(srcElement, x, y, { type: "mousemove" }, aWindow); synthesizeMouse(srcElement, x+10, y+10, { type: "mousemove" }, aWindow); aWindow.removeEventListener("dragstart", trapDrag, true); event = aWindow.document.createEvent("DragEvents"); event.initDragEvent("dragenter", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer); gWindowUtils.dispatchDOMEventViaPresShell(destElement, event, true); var event = aWindow.document.createEvent("DragEvents"); event.initDragEvent("dragover", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer); if (gWindowUtils.dispatchDOMEventViaPresShell(destElement, event, true)) { synthesizeMouseAtCenter(destElement, { type: "mouseup" }, aWindow); return "none"; } if (dataTransfer.dropEffect != "none") { event = aWindow.document.createEvent("DragEvents"); event.initDragEvent("drop", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer); gWindowUtils.dispatchDOMEventViaPresShell(destElement, event, true); } synthesizeMouseAtCenter(destElement, { type: "mouseup" }, aWindow); return dataTransfer.dropEffect; } finally { ds.endDragSession(true); } }; var PluginUtils = { withTestPlugin : function(callback) { if (typeof Components == "undefined") { todo(false, "Not a Mozilla-based browser"); return false; } var ph = Components.classes["@mozilla.org/plugin/host;1"] .getService(Components.interfaces.nsIPluginHost); var tags = ph.getPluginTags(); // Find the test plugin for (var i = 0; i < tags.length; i++) { if (tags[i].name == "Test Plug-in") { callback(tags[i]); return true; } } todo(false, "Need a test plugin on this platform"); return false; } };