Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | /** |
michael@0 | 6 | * ChromeUtils.js is a set of mochitest utilities that are used to |
michael@0 | 7 | * synthesize events in the browser. These are only used by |
michael@0 | 8 | * mochitest-chrome and browser-chrome tests. Originally these functions were in |
michael@0 | 9 | * EventUtils.js, but when porting to specialPowers, we didn't want |
michael@0 | 10 | * to move unnecessary functions. |
michael@0 | 11 | * |
michael@0 | 12 | * ChromeUtils.js depends on EventUtils.js being loaded. |
michael@0 | 13 | * |
michael@0 | 14 | */ |
michael@0 | 15 | |
michael@0 | 16 | /** |
michael@0 | 17 | * Synthesize a query text content event. |
michael@0 | 18 | * |
michael@0 | 19 | * @param aOffset The character offset. 0 means the first character in the |
michael@0 | 20 | * selection root. |
michael@0 | 21 | * @param aLength The length of getting text. If the length is too long, |
michael@0 | 22 | * the extra length is ignored. |
michael@0 | 23 | * @param aWindow Optional (If null, current |window| will be used) |
michael@0 | 24 | * @return An nsIQueryContentEventResult object. If this failed, |
michael@0 | 25 | * the result might be null. |
michael@0 | 26 | */ |
michael@0 | 27 | function synthesizeQueryTextContent(aOffset, aLength, aWindow) |
michael@0 | 28 | { |
michael@0 | 29 | var utils = _getDOMWindowUtils(aWindow); |
michael@0 | 30 | if (!utils) { |
michael@0 | 31 | return nullptr; |
michael@0 | 32 | } |
michael@0 | 33 | return utils.sendQueryContentEvent(utils.QUERY_TEXT_CONTENT, |
michael@0 | 34 | aOffset, aLength, 0, 0); |
michael@0 | 35 | } |
michael@0 | 36 | |
michael@0 | 37 | /** |
michael@0 | 38 | * Synthesize a query caret rect event. |
michael@0 | 39 | * |
michael@0 | 40 | * @param aOffset The caret offset. 0 means left side of the first character |
michael@0 | 41 | * in the selection root. |
michael@0 | 42 | * @param aWindow Optional (If null, current |window| will be used) |
michael@0 | 43 | * @return An nsIQueryContentEventResult object. If this failed, |
michael@0 | 44 | * the result might be null. |
michael@0 | 45 | */ |
michael@0 | 46 | function synthesizeQueryCaretRect(aOffset, aWindow) |
michael@0 | 47 | { |
michael@0 | 48 | var utils = _getDOMWindowUtils(aWindow); |
michael@0 | 49 | if (!utils) { |
michael@0 | 50 | return nullptr; |
michael@0 | 51 | } |
michael@0 | 52 | return utils.sendQueryContentEvent(utils.QUERY_CARET_RECT, |
michael@0 | 53 | aOffset, 0, 0, 0); |
michael@0 | 54 | } |
michael@0 | 55 | |
michael@0 | 56 | /** |
michael@0 | 57 | * Synthesize a query text rect event. |
michael@0 | 58 | * |
michael@0 | 59 | * @param aOffset The character offset. 0 means the first character in the |
michael@0 | 60 | * selection root. |
michael@0 | 61 | * @param aLength The length of the text. If the length is too long, |
michael@0 | 62 | * the extra length is ignored. |
michael@0 | 63 | * @param aWindow Optional (If null, current |window| will be used) |
michael@0 | 64 | * @return An nsIQueryContentEventResult object. If this failed, |
michael@0 | 65 | * the result might be null. |
michael@0 | 66 | */ |
michael@0 | 67 | function synthesizeQueryTextRect(aOffset, aLength, aWindow) |
michael@0 | 68 | { |
michael@0 | 69 | var utils = _getDOMWindowUtils(aWindow); |
michael@0 | 70 | if (!utils) { |
michael@0 | 71 | return nullptr; |
michael@0 | 72 | } |
michael@0 | 73 | return utils.sendQueryContentEvent(utils.QUERY_TEXT_RECT, |
michael@0 | 74 | aOffset, aLength, 0, 0); |
michael@0 | 75 | } |
michael@0 | 76 | |
michael@0 | 77 | /** |
michael@0 | 78 | * Synthesize a query editor rect event. |
michael@0 | 79 | * |
michael@0 | 80 | * @param aWindow Optional (If null, current |window| will be used) |
michael@0 | 81 | * @return An nsIQueryContentEventResult object. If this failed, |
michael@0 | 82 | * the result might be null. |
michael@0 | 83 | */ |
michael@0 | 84 | function synthesizeQueryEditorRect(aWindow) |
michael@0 | 85 | { |
michael@0 | 86 | var utils = _getDOMWindowUtils(aWindow); |
michael@0 | 87 | if (!utils) { |
michael@0 | 88 | return nullptr; |
michael@0 | 89 | } |
michael@0 | 90 | return utils.sendQueryContentEvent(utils.QUERY_EDITOR_RECT, 0, 0, 0, 0); |
michael@0 | 91 | } |
michael@0 | 92 | |
michael@0 | 93 | /** |
michael@0 | 94 | * Synthesize a character at point event. |
michael@0 | 95 | * |
michael@0 | 96 | * @param aX, aY The offset in the client area of the DOM window. |
michael@0 | 97 | * @param aWindow Optional (If null, current |window| will be used) |
michael@0 | 98 | * @return An nsIQueryContentEventResult object. If this failed, |
michael@0 | 99 | * the result might be null. |
michael@0 | 100 | */ |
michael@0 | 101 | function synthesizeCharAtPoint(aX, aY, aWindow) |
michael@0 | 102 | { |
michael@0 | 103 | var utils = _getDOMWindowUtils(aWindow); |
michael@0 | 104 | if (!utils) { |
michael@0 | 105 | return nullptr; |
michael@0 | 106 | } |
michael@0 | 107 | return utils.sendQueryContentEvent(utils.QUERY_CHARACTER_AT_POINT, |
michael@0 | 108 | 0, 0, aX, aY); |
michael@0 | 109 | } |
michael@0 | 110 | |
michael@0 | 111 | /** |
michael@0 | 112 | * Emulate a dragstart event. |
michael@0 | 113 | * element - element to fire the dragstart event on |
michael@0 | 114 | * expectedDragData - the data you expect the data transfer to contain afterwards |
michael@0 | 115 | * This data is in the format: |
michael@0 | 116 | * [ [ {type: value, data: value, test: function}, ... ], ... ] |
michael@0 | 117 | * can be null |
michael@0 | 118 | * aWindow - optional; defaults to the current window object. |
michael@0 | 119 | * x - optional; initial x coordinate |
michael@0 | 120 | * y - optional; initial y coordinate |
michael@0 | 121 | * Returns null if data matches. |
michael@0 | 122 | * Returns the event.dataTransfer if data does not match |
michael@0 | 123 | * |
michael@0 | 124 | * eqTest is an optional function if comparison can't be done with x == y; |
michael@0 | 125 | * function (actualData, expectedData) {return boolean} |
michael@0 | 126 | * @param actualData from dataTransfer |
michael@0 | 127 | * @param expectedData from expectedDragData |
michael@0 | 128 | * see bug 462172 for example of use |
michael@0 | 129 | * |
michael@0 | 130 | */ |
michael@0 | 131 | function synthesizeDragStart(element, expectedDragData, aWindow, x, y) |
michael@0 | 132 | { |
michael@0 | 133 | if (!aWindow) |
michael@0 | 134 | aWindow = window; |
michael@0 | 135 | x = x || 2; |
michael@0 | 136 | y = y || 2; |
michael@0 | 137 | const step = 9; |
michael@0 | 138 | |
michael@0 | 139 | var result = "trapDrag was not called"; |
michael@0 | 140 | var trapDrag = function(event) { |
michael@0 | 141 | try { |
michael@0 | 142 | var dataTransfer = event.dataTransfer; |
michael@0 | 143 | result = null; |
michael@0 | 144 | if (!dataTransfer) |
michael@0 | 145 | throw "no dataTransfer"; |
michael@0 | 146 | if (expectedDragData == null || |
michael@0 | 147 | dataTransfer.mozItemCount != expectedDragData.length) |
michael@0 | 148 | throw dataTransfer; |
michael@0 | 149 | for (var i = 0; i < dataTransfer.mozItemCount; i++) { |
michael@0 | 150 | var dtTypes = dataTransfer.mozTypesAt(i); |
michael@0 | 151 | if (dtTypes.length != expectedDragData[i].length) |
michael@0 | 152 | throw dataTransfer; |
michael@0 | 153 | for (var j = 0; j < dtTypes.length; j++) { |
michael@0 | 154 | if (dtTypes[j] != expectedDragData[i][j].type) |
michael@0 | 155 | throw dataTransfer; |
michael@0 | 156 | var dtData = dataTransfer.mozGetDataAt(dtTypes[j],i); |
michael@0 | 157 | if (expectedDragData[i][j].eqTest) { |
michael@0 | 158 | if (!expectedDragData[i][j].eqTest(dtData, expectedDragData[i][j].data)) |
michael@0 | 159 | throw dataTransfer; |
michael@0 | 160 | } |
michael@0 | 161 | else if (expectedDragData[i][j].data != dtData) |
michael@0 | 162 | throw dataTransfer; |
michael@0 | 163 | } |
michael@0 | 164 | } |
michael@0 | 165 | } catch(ex) { |
michael@0 | 166 | result = ex; |
michael@0 | 167 | } |
michael@0 | 168 | event.preventDefault(); |
michael@0 | 169 | event.stopPropagation(); |
michael@0 | 170 | } |
michael@0 | 171 | aWindow.addEventListener("dragstart", trapDrag, false); |
michael@0 | 172 | synthesizeMouse(element, x, y, { type: "mousedown" }, aWindow); |
michael@0 | 173 | x += step; y += step; |
michael@0 | 174 | synthesizeMouse(element, x, y, { type: "mousemove" }, aWindow); |
michael@0 | 175 | x += step; y += step; |
michael@0 | 176 | synthesizeMouse(element, x, y, { type: "mousemove" }, aWindow); |
michael@0 | 177 | aWindow.removeEventListener("dragstart", trapDrag, false); |
michael@0 | 178 | synthesizeMouse(element, x, y, { type: "mouseup" }, aWindow); |
michael@0 | 179 | return result; |
michael@0 | 180 | } |
michael@0 | 181 | |
michael@0 | 182 | /** |
michael@0 | 183 | * Emulate a drop by emulating a dragstart and firing events dragenter, dragover, and drop. |
michael@0 | 184 | * srcElement - the element to use to start the drag, usually the same as destElement |
michael@0 | 185 | * but if destElement isn't suitable to start a drag on pass a suitable |
michael@0 | 186 | * element for srcElement |
michael@0 | 187 | * destElement - the element to fire the dragover, dragleave and drop events |
michael@0 | 188 | * dragData - the data to supply for the data transfer |
michael@0 | 189 | * This data is in the format: |
michael@0 | 190 | * [ [ {type: value, data: value}, ...], ... ] |
michael@0 | 191 | * dropEffect - the drop effect to set during the dragstart event, or 'move' if null |
michael@0 | 192 | * aWindow - optional; defaults to the current window object. |
michael@0 | 193 | * eventUtils - optional; allows you to pass in a reference to EventUtils.js. |
michael@0 | 194 | * If the eventUtils parameter is not passed in, we assume EventUtils.js is |
michael@0 | 195 | * in the scope. Used by browser-chrome tests. |
michael@0 | 196 | * |
michael@0 | 197 | * Returns the drop effect that was desired. |
michael@0 | 198 | */ |
michael@0 | 199 | function synthesizeDrop(srcElement, destElement, dragData, dropEffect, aWindow, eventUtils) |
michael@0 | 200 | { |
michael@0 | 201 | if (!aWindow) |
michael@0 | 202 | aWindow = window; |
michael@0 | 203 | |
michael@0 | 204 | var synthesizeMouseAtCenter = (eventUtils || window).synthesizeMouseAtCenter; |
michael@0 | 205 | var synthesizeMouse = (eventUtils || window).synthesizeMouse; |
michael@0 | 206 | |
michael@0 | 207 | var gWindowUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor). |
michael@0 | 208 | getInterface(Components.interfaces.nsIDOMWindowUtils); |
michael@0 | 209 | var ds = Components.classes["@mozilla.org/widget/dragservice;1"]. |
michael@0 | 210 | getService(Components.interfaces.nsIDragService); |
michael@0 | 211 | |
michael@0 | 212 | var dataTransfer; |
michael@0 | 213 | var trapDrag = function(event) { |
michael@0 | 214 | dataTransfer = event.dataTransfer; |
michael@0 | 215 | for (var i = 0; i < dragData.length; i++) { |
michael@0 | 216 | var item = dragData[i]; |
michael@0 | 217 | for (var j = 0; j < item.length; j++) { |
michael@0 | 218 | dataTransfer.mozSetDataAt(item[j].type, item[j].data, i); |
michael@0 | 219 | } |
michael@0 | 220 | } |
michael@0 | 221 | dataTransfer.dropEffect = dropEffect || "move"; |
michael@0 | 222 | event.preventDefault(); |
michael@0 | 223 | event.stopPropagation(); |
michael@0 | 224 | } |
michael@0 | 225 | |
michael@0 | 226 | ds.startDragSession(); |
michael@0 | 227 | |
michael@0 | 228 | try { |
michael@0 | 229 | // need to use real mouse action |
michael@0 | 230 | aWindow.addEventListener("dragstart", trapDrag, true); |
michael@0 | 231 | synthesizeMouseAtCenter(srcElement, { type: "mousedown" }, aWindow); |
michael@0 | 232 | |
michael@0 | 233 | var rect = srcElement.getBoundingClientRect(); |
michael@0 | 234 | var x = rect.width / 2; |
michael@0 | 235 | var y = rect.height / 2; |
michael@0 | 236 | synthesizeMouse(srcElement, x, y, { type: "mousemove" }, aWindow); |
michael@0 | 237 | synthesizeMouse(srcElement, x+10, y+10, { type: "mousemove" }, aWindow); |
michael@0 | 238 | aWindow.removeEventListener("dragstart", trapDrag, true); |
michael@0 | 239 | |
michael@0 | 240 | event = aWindow.document.createEvent("DragEvents"); |
michael@0 | 241 | event.initDragEvent("dragenter", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer); |
michael@0 | 242 | gWindowUtils.dispatchDOMEventViaPresShell(destElement, event, true); |
michael@0 | 243 | |
michael@0 | 244 | var event = aWindow.document.createEvent("DragEvents"); |
michael@0 | 245 | event.initDragEvent("dragover", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer); |
michael@0 | 246 | if (gWindowUtils.dispatchDOMEventViaPresShell(destElement, event, true)) { |
michael@0 | 247 | synthesizeMouseAtCenter(destElement, { type: "mouseup" }, aWindow); |
michael@0 | 248 | return "none"; |
michael@0 | 249 | } |
michael@0 | 250 | |
michael@0 | 251 | if (dataTransfer.dropEffect != "none") { |
michael@0 | 252 | event = aWindow.document.createEvent("DragEvents"); |
michael@0 | 253 | event.initDragEvent("drop", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer); |
michael@0 | 254 | gWindowUtils.dispatchDOMEventViaPresShell(destElement, event, true); |
michael@0 | 255 | } |
michael@0 | 256 | |
michael@0 | 257 | synthesizeMouseAtCenter(destElement, { type: "mouseup" }, aWindow); |
michael@0 | 258 | |
michael@0 | 259 | return dataTransfer.dropEffect; |
michael@0 | 260 | } finally { |
michael@0 | 261 | ds.endDragSession(true); |
michael@0 | 262 | } |
michael@0 | 263 | }; |
michael@0 | 264 | |
michael@0 | 265 | var PluginUtils = |
michael@0 | 266 | { |
michael@0 | 267 | withTestPlugin : function(callback) |
michael@0 | 268 | { |
michael@0 | 269 | if (typeof Components == "undefined") |
michael@0 | 270 | { |
michael@0 | 271 | todo(false, "Not a Mozilla-based browser"); |
michael@0 | 272 | return false; |
michael@0 | 273 | } |
michael@0 | 274 | |
michael@0 | 275 | var ph = Components.classes["@mozilla.org/plugin/host;1"] |
michael@0 | 276 | .getService(Components.interfaces.nsIPluginHost); |
michael@0 | 277 | var tags = ph.getPluginTags(); |
michael@0 | 278 | |
michael@0 | 279 | // Find the test plugin |
michael@0 | 280 | for (var i = 0; i < tags.length; i++) |
michael@0 | 281 | { |
michael@0 | 282 | if (tags[i].name == "Test Plug-in") |
michael@0 | 283 | { |
michael@0 | 284 | callback(tags[i]); |
michael@0 | 285 | return true; |
michael@0 | 286 | } |
michael@0 | 287 | } |
michael@0 | 288 | todo(false, "Need a test plugin on this platform"); |
michael@0 | 289 | return false; |
michael@0 | 290 | } |
michael@0 | 291 | }; |