1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/services/sync/tps/extensions/mozmill/resource/stdlib/utils.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,462 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +var EXPORTED_SYMBOLS = ["applicationName", "assert", "Copy", "getBrowserObject", 1.9 + "getChromeWindow", "getWindows", "getWindowByTitle", 1.10 + "getWindowByType", "getWindowId", "getMethodInWindows", 1.11 + "getPreference", "saveDataURL", "setPreference", 1.12 + "sleep", "startTimer", "stopTimer", "takeScreenshot", 1.13 + "unwrapNode", "waitFor" 1.14 + ]; 1.15 + 1.16 +const Cc = Components.classes; 1.17 +const Ci = Components.interfaces; 1.18 +const Cu = Components.utils; 1.19 + 1.20 + 1.21 +Cu.import("resource://gre/modules/NetUtil.jsm"); 1.22 +Cu.import("resource://gre/modules/Services.jsm"); 1.23 + 1.24 +const applicationIdMap = { 1.25 + '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}': 'Firefox', 1.26 + '{99bceaaa-e3c6-48c1-b981-ef9b46b67d60}': 'MetroFirefox' 1.27 +} 1.28 +const applicationName = applicationIdMap[Services.appinfo.ID] || Services.appinfo.name; 1.29 + 1.30 +var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions); 1.31 +var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker); 1.32 +var errors = {}; Cu.import('resource://mozmill/modules/errors.js', errors); 1.33 + 1.34 +var assert = new assertions.Assert(); 1.35 + 1.36 +var hwindow = Services.appShell.hiddenDOMWindow; 1.37 + 1.38 +var uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator); 1.39 + 1.40 +function Copy (obj) { 1.41 + for (var n in obj) { 1.42 + this[n] = obj[n]; 1.43 + } 1.44 +} 1.45 + 1.46 +/** 1.47 + * Returns the browser object of the specified window 1.48 + * 1.49 + * @param {Window} aWindow 1.50 + * Window to get the browser element from. 1.51 + * 1.52 + * @returns {Object} The browser element 1.53 + */ 1.54 +function getBrowserObject(aWindow) { 1.55 + switch(applicationName) { 1.56 + case "MetroFirefox": 1.57 + return aWindow.Browser; 1.58 + case "Firefox": 1.59 + default: 1.60 + return aWindow.gBrowser; 1.61 + } 1.62 +} 1.63 + 1.64 +function getChromeWindow(aWindow) { 1.65 + var chromeWin = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) 1.66 + .getInterface(Ci.nsIWebNavigation) 1.67 + .QueryInterface(Ci.nsIDocShellTreeItem) 1.68 + .rootTreeItem 1.69 + .QueryInterface(Ci.nsIInterfaceRequestor) 1.70 + .getInterface(Ci.nsIDOMWindow) 1.71 + .QueryInterface(Ci.nsIDOMChromeWindow); 1.72 + 1.73 + return chromeWin; 1.74 +} 1.75 + 1.76 +function getWindows(type) { 1.77 + if (type == undefined) { 1.78 + type = ""; 1.79 + } 1.80 + 1.81 + var windows = []; 1.82 + var enumerator = Services.wm.getEnumerator(type); 1.83 + 1.84 + while (enumerator.hasMoreElements()) { 1.85 + windows.push(enumerator.getNext()); 1.86 + } 1.87 + 1.88 + if (type == "") { 1.89 + windows.push(hwindow); 1.90 + } 1.91 + 1.92 + return windows; 1.93 +} 1.94 + 1.95 +function getMethodInWindows(methodName) { 1.96 + for each (var w in getWindows()) { 1.97 + if (w[methodName] != undefined) { 1.98 + return w[methodName]; 1.99 + } 1.100 + } 1.101 + 1.102 + throw new Error("Method with name: '" + methodName + "' is not in any open window."); 1.103 +} 1.104 + 1.105 +function getWindowByTitle(title) { 1.106 + for each (var w in getWindows()) { 1.107 + if (w.document.title && w.document.title == title) { 1.108 + return w; 1.109 + } 1.110 + } 1.111 + 1.112 + throw new Error("Window with title: '" + title + "' not found."); 1.113 +} 1.114 + 1.115 +function getWindowByType(type) { 1.116 + return Services.wm.getMostRecentWindow(type); 1.117 +} 1.118 + 1.119 +/** 1.120 + * Retrieve the outer window id for the given window. 1.121 + * 1.122 + * @param {Number} aWindow 1.123 + * Window to retrieve the id from. 1.124 + * @returns {Boolean} The outer window id 1.125 + **/ 1.126 +function getWindowId(aWindow) { 1.127 + try { 1.128 + // Normally we can retrieve the id via window utils 1.129 + return aWindow.QueryInterface(Ci.nsIInterfaceRequestor). 1.130 + getInterface(Ci.nsIDOMWindowUtils). 1.131 + outerWindowID; 1.132 + } catch (e) { 1.133 + // ... but for observer notifications we need another interface 1.134 + return aWindow.QueryInterface(Ci.nsISupportsPRUint64).data; 1.135 + } 1.136 +} 1.137 + 1.138 +var checkChrome = function () { 1.139 + var loc = window.document.location.href; 1.140 + try { 1.141 + loc = window.top.document.location.href; 1.142 + } catch (e) { 1.143 + } 1.144 + 1.145 + return /^chrome:\/\//.test(loc); 1.146 +} 1.147 + 1.148 +/** 1.149 + * Called to get the state of an individual preference. 1.150 + * 1.151 + * @param aPrefName string The preference to get the state of. 1.152 + * @param aDefaultValue any The default value if preference was not found. 1.153 + * 1.154 + * @returns any The value of the requested preference 1.155 + * 1.156 + * @see setPref 1.157 + * Code by Henrik Skupin: <hskupin@gmail.com> 1.158 + */ 1.159 +function getPreference(aPrefName, aDefaultValue) { 1.160 + try { 1.161 + var branch = Services.prefs; 1.162 + 1.163 + switch (typeof aDefaultValue) { 1.164 + case ('boolean'): 1.165 + return branch.getBoolPref(aPrefName); 1.166 + case ('string'): 1.167 + return branch.getCharPref(aPrefName); 1.168 + case ('number'): 1.169 + return branch.getIntPref(aPrefName); 1.170 + default: 1.171 + return branch.getComplexValue(aPrefName); 1.172 + } 1.173 + } catch (e) { 1.174 + return aDefaultValue; 1.175 + } 1.176 +} 1.177 + 1.178 +/** 1.179 + * Called to set the state of an individual preference. 1.180 + * 1.181 + * @param aPrefName string The preference to set the state of. 1.182 + * @param aValue any The value to set the preference to. 1.183 + * 1.184 + * @returns boolean Returns true if value was successfully set. 1.185 + * 1.186 + * @see getPref 1.187 + * Code by Henrik Skupin: <hskupin@gmail.com> 1.188 + */ 1.189 +function setPreference(aName, aValue) { 1.190 + try { 1.191 + var branch = Services.prefs; 1.192 + 1.193 + switch (typeof aValue) { 1.194 + case ('boolean'): 1.195 + branch.setBoolPref(aName, aValue); 1.196 + break; 1.197 + case ('string'): 1.198 + branch.setCharPref(aName, aValue); 1.199 + break; 1.200 + case ('number'): 1.201 + branch.setIntPref(aName, aValue); 1.202 + break; 1.203 + default: 1.204 + branch.setComplexValue(aName, aValue); 1.205 + } 1.206 + } catch (e) { 1.207 + return false; 1.208 + } 1.209 + 1.210 + return true; 1.211 +} 1.212 + 1.213 +/** 1.214 + * Sleep for the given amount of milliseconds 1.215 + * 1.216 + * @param {number} milliseconds 1.217 + * Sleeps the given number of milliseconds 1.218 + */ 1.219 +function sleep(milliseconds) { 1.220 + var timeup = false; 1.221 + 1.222 + hwindow.setTimeout(function () { timeup = true; }, milliseconds); 1.223 + var thread = Services.tm.currentThread; 1.224 + 1.225 + while (!timeup) { 1.226 + thread.processNextEvent(true); 1.227 + } 1.228 + 1.229 + broker.pass({'function':'utils.sleep()'}); 1.230 +} 1.231 + 1.232 +/** 1.233 + * Check if the callback function evaluates to true 1.234 + */ 1.235 +function assert(callback, message, thisObject) { 1.236 + var result = callback.call(thisObject); 1.237 + 1.238 + if (!result) { 1.239 + throw new Error(message || arguments.callee.name + ": Failed for '" + callback + "'"); 1.240 + } 1.241 + 1.242 + return true; 1.243 +} 1.244 + 1.245 +/** 1.246 + * Unwraps a node which is wrapped into a XPCNativeWrapper or XrayWrapper 1.247 + * 1.248 + * @param {DOMnode} Wrapped DOM node 1.249 + * @returns {DOMNode} Unwrapped DOM node 1.250 + */ 1.251 +function unwrapNode(aNode) { 1.252 + var node = aNode; 1.253 + if (node) { 1.254 + // unwrap is not available on older branches (3.5 and 3.6) - Bug 533596 1.255 + if ("unwrap" in XPCNativeWrapper) { 1.256 + node = XPCNativeWrapper.unwrap(node); 1.257 + } 1.258 + else if (node.wrappedJSObject != null) { 1.259 + node = node.wrappedJSObject; 1.260 + } 1.261 + } 1.262 + 1.263 + return node; 1.264 +} 1.265 + 1.266 +/** 1.267 + * Waits for the callback evaluates to true 1.268 + */ 1.269 +function waitFor(callback, message, timeout, interval, thisObject) { 1.270 + broker.log({'function': 'utils.waitFor() - DEPRECATED', 1.271 + 'message': 'utils.waitFor() is deprecated. Use assert.waitFor() instead'}); 1.272 + assert.waitFor(callback, message, timeout, interval, thisObject); 1.273 +} 1.274 + 1.275 +/** 1.276 + * Calculates the x and y chrome offset for an element 1.277 + * See https://developer.mozilla.org/en/DOM/window.innerHeight 1.278 + * 1.279 + * Note this function will not work if the user has custom toolbars (via extension) at the bottom or left/right of the screen 1.280 + */ 1.281 +function getChromeOffset(elem) { 1.282 + var win = elem.ownerDocument.defaultView; 1.283 + // Calculate x offset 1.284 + var chromeWidth = 0; 1.285 + 1.286 + if (win["name"] != "sidebar") { 1.287 + chromeWidth = win.outerWidth - win.innerWidth; 1.288 + } 1.289 + 1.290 + // Calculate y offset 1.291 + var chromeHeight = win.outerHeight - win.innerHeight; 1.292 + // chromeHeight == 0 means elem is already in the chrome and doesn't need the addonbar offset 1.293 + if (chromeHeight > 0) { 1.294 + // window.innerHeight doesn't include the addon or find bar, so account for these if present 1.295 + var addonbar = win.document.getElementById("addon-bar"); 1.296 + if (addonbar) { 1.297 + chromeHeight -= addonbar.scrollHeight; 1.298 + } 1.299 + 1.300 + var findbar = win.document.getElementById("FindToolbar"); 1.301 + if (findbar) { 1.302 + chromeHeight -= findbar.scrollHeight; 1.303 + } 1.304 + } 1.305 + 1.306 + return {'x':chromeWidth, 'y':chromeHeight}; 1.307 +} 1.308 + 1.309 +/** 1.310 + * Takes a screenshot of the specified DOM node 1.311 + */ 1.312 +function takeScreenshot(node, highlights) { 1.313 + var rect, win, width, height, left, top, needsOffset; 1.314 + // node can be either a window or an arbitrary DOM node 1.315 + try { 1.316 + // node is an arbitrary DOM node 1.317 + win = node.ownerDocument.defaultView; 1.318 + rect = node.getBoundingClientRect(); 1.319 + width = rect.width; 1.320 + height = rect.height; 1.321 + top = rect.top; 1.322 + left = rect.left; 1.323 + // offset for highlights not needed as they will be relative to this node 1.324 + needsOffset = false; 1.325 + } catch (e) { 1.326 + // node is a window 1.327 + win = node; 1.328 + width = win.innerWidth; 1.329 + height = win.innerHeight; 1.330 + top = 0; 1.331 + left = 0; 1.332 + // offset needed for highlights to take 'outerHeight' of window into account 1.333 + needsOffset = true; 1.334 + } 1.335 + 1.336 + var canvas = win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); 1.337 + canvas.width = width; 1.338 + canvas.height = height; 1.339 + 1.340 + var ctx = canvas.getContext("2d"); 1.341 + // Draws the DOM contents of the window to the canvas 1.342 + ctx.drawWindow(win, left, top, width, height, "rgb(255,255,255)"); 1.343 + 1.344 + // This section is for drawing a red rectangle around each element passed in via the highlights array 1.345 + if (highlights) { 1.346 + ctx.lineWidth = "2"; 1.347 + ctx.strokeStyle = "red"; 1.348 + ctx.save(); 1.349 + 1.350 + for (var i = 0; i < highlights.length; ++i) { 1.351 + var elem = highlights[i]; 1.352 + rect = elem.getBoundingClientRect(); 1.353 + 1.354 + var offsetY = 0, offsetX = 0; 1.355 + if (needsOffset) { 1.356 + var offset = getChromeOffset(elem); 1.357 + offsetX = offset.x; 1.358 + offsetY = offset.y; 1.359 + } else { 1.360 + // Don't need to offset the window chrome, just make relative to containing node 1.361 + offsetY = -top; 1.362 + offsetX = -left; 1.363 + } 1.364 + 1.365 + // Draw the rectangle 1.366 + ctx.strokeRect(rect.left + offsetX, rect.top + offsetY, rect.width, rect.height); 1.367 + } 1.368 + } 1.369 + 1.370 + return canvas.toDataURL("image/jpeg", 0.5); 1.371 +} 1.372 + 1.373 +/** 1.374 + * Save the dataURL content to the specified file. It will be stored in either the persisted screenshot or temporary folder. 1.375 + * 1.376 + * @param {String} aDataURL 1.377 + * The dataURL to save 1.378 + * @param {String} aFilename 1.379 + * Target file name without extension 1.380 + * 1.381 + * @returns {Object} The hash containing the path of saved file, and the failure bit 1.382 + */ 1.383 +function saveDataURL(aDataURL, aFilename) { 1.384 + var frame = {}; Cu.import('resource://mozmill/modules/frame.js', frame); 1.385 + const FILE_PERMISSIONS = parseInt("0644", 8); 1.386 + 1.387 + var file; 1.388 + file = Cc['@mozilla.org/file/local;1'] 1.389 + .createInstance(Ci.nsILocalFile); 1.390 + file.initWithPath(frame.persisted['screenshots']['path']); 1.391 + file.append(aFilename + ".jpg"); 1.392 + file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FILE_PERMISSIONS); 1.393 + 1.394 + // Create an output stream to write to file 1.395 + let foStream = Cc["@mozilla.org/network/file-output-stream;1"] 1.396 + .createInstance(Ci.nsIFileOutputStream); 1.397 + foStream.init(file, 0x02 | 0x08 | 0x10, FILE_PERMISSIONS, foStream.DEFER_OPEN); 1.398 + 1.399 + let dataURI = NetUtil.newURI(aDataURL, "UTF8", null); 1.400 + if (!dataURI.schemeIs("data")) { 1.401 + throw TypeError("aDataURL parameter has to have 'data'" + 1.402 + " scheme instead of '" + dataURI.scheme + "'"); 1.403 + } 1.404 + 1.405 + // Write asynchronously to buffer; 1.406 + // Input and output streams are closed after write 1.407 + 1.408 + let ready = false; 1.409 + let failure = false; 1.410 + 1.411 + function sync(aStatus) { 1.412 + if (!Components.isSuccessCode(aStatus)) { 1.413 + failure = true; 1.414 + } 1.415 + ready = true; 1.416 + } 1.417 + 1.418 + NetUtil.asyncFetch(dataURI, function (aInputStream, aAsyncFetchResult) { 1.419 + if (!Components.isSuccessCode(aAsyncFetchResult)) { 1.420 + // An error occurred! 1.421 + sync(aAsyncFetchResult); 1.422 + } else { 1.423 + // Consume the input stream. 1.424 + NetUtil.asyncCopy(aInputStream, foStream, function (aAsyncCopyResult) { 1.425 + sync(aAsyncCopyResult); 1.426 + }); 1.427 + } 1.428 + }); 1.429 + 1.430 + assert.waitFor(function () { 1.431 + return ready; 1.432 + }, "DataURL has been saved to '" + file.path + "'"); 1.433 + 1.434 + return {filename: file.path, failure: failure}; 1.435 +} 1.436 + 1.437 +/** 1.438 + * Some very brain-dead timer functions useful for performance optimizations 1.439 + * This is only enabled in debug mode 1.440 + * 1.441 + **/ 1.442 +var gutility_mzmltimer = 0; 1.443 +/** 1.444 + * Starts timer initializing with current EPOC time in milliseconds 1.445 + * 1.446 + * @returns none 1.447 + **/ 1.448 +function startTimer(){ 1.449 + dump("TIMERCHECK:: starting now: " + Date.now() + "\n"); 1.450 + gutility_mzmltimer = Date.now(); 1.451 +} 1.452 + 1.453 +/** 1.454 + * Checks the timer and outputs current elapsed time since start of timer. It 1.455 + * will print out a message you provide with its "time check" so you can 1.456 + * correlate in the log file and figure out elapsed time of specific functions. 1.457 + * 1.458 + * @param aMsg string The debug message to print with the timer check 1.459 + * 1.460 + * @returns none 1.461 + **/ 1.462 +function checkTimer(aMsg){ 1.463 + var end = Date.now(); 1.464 + dump("TIMERCHECK:: at " + aMsg + " is: " + (end - gutility_mzmltimer) + "\n"); 1.465 +}