michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: var EXPORTED_SYMBOLS = ["applicationName", "assert", "Copy", "getBrowserObject", michael@0: "getChromeWindow", "getWindows", "getWindowByTitle", michael@0: "getWindowByType", "getWindowId", "getMethodInWindows", michael@0: "getPreference", "saveDataURL", "setPreference", michael@0: "sleep", "startTimer", "stopTimer", "takeScreenshot", michael@0: "unwrapNode", "waitFor" michael@0: ]; michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: michael@0: michael@0: Cu.import("resource://gre/modules/NetUtil.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: const applicationIdMap = { michael@0: '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}': 'Firefox', michael@0: '{99bceaaa-e3c6-48c1-b981-ef9b46b67d60}': 'MetroFirefox' michael@0: } michael@0: const applicationName = applicationIdMap[Services.appinfo.ID] || Services.appinfo.name; michael@0: michael@0: var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions); michael@0: var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker); michael@0: var errors = {}; Cu.import('resource://mozmill/modules/errors.js', errors); michael@0: michael@0: var assert = new assertions.Assert(); michael@0: michael@0: var hwindow = Services.appShell.hiddenDOMWindow; michael@0: michael@0: var uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator); michael@0: michael@0: function Copy (obj) { michael@0: for (var n in obj) { michael@0: this[n] = obj[n]; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Returns the browser object of the specified window michael@0: * michael@0: * @param {Window} aWindow michael@0: * Window to get the browser element from. michael@0: * michael@0: * @returns {Object} The browser element michael@0: */ michael@0: function getBrowserObject(aWindow) { michael@0: switch(applicationName) { michael@0: case "MetroFirefox": michael@0: return aWindow.Browser; michael@0: case "Firefox": michael@0: default: michael@0: return aWindow.gBrowser; michael@0: } michael@0: } michael@0: michael@0: function getChromeWindow(aWindow) { michael@0: var chromeWin = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShellTreeItem) michael@0: .rootTreeItem michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindow) michael@0: .QueryInterface(Ci.nsIDOMChromeWindow); michael@0: michael@0: return chromeWin; michael@0: } michael@0: michael@0: function getWindows(type) { michael@0: if (type == undefined) { michael@0: type = ""; michael@0: } michael@0: michael@0: var windows = []; michael@0: var enumerator = Services.wm.getEnumerator(type); michael@0: michael@0: while (enumerator.hasMoreElements()) { michael@0: windows.push(enumerator.getNext()); michael@0: } michael@0: michael@0: if (type == "") { michael@0: windows.push(hwindow); michael@0: } michael@0: michael@0: return windows; michael@0: } michael@0: michael@0: function getMethodInWindows(methodName) { michael@0: for each (var w in getWindows()) { michael@0: if (w[methodName] != undefined) { michael@0: return w[methodName]; michael@0: } michael@0: } michael@0: michael@0: throw new Error("Method with name: '" + methodName + "' is not in any open window."); michael@0: } michael@0: michael@0: function getWindowByTitle(title) { michael@0: for each (var w in getWindows()) { michael@0: if (w.document.title && w.document.title == title) { michael@0: return w; michael@0: } michael@0: } michael@0: michael@0: throw new Error("Window with title: '" + title + "' not found."); michael@0: } michael@0: michael@0: function getWindowByType(type) { michael@0: return Services.wm.getMostRecentWindow(type); michael@0: } michael@0: michael@0: /** michael@0: * Retrieve the outer window id for the given window. michael@0: * michael@0: * @param {Number} aWindow michael@0: * Window to retrieve the id from. michael@0: * @returns {Boolean} The outer window id michael@0: **/ michael@0: function getWindowId(aWindow) { michael@0: try { michael@0: // Normally we can retrieve the id via window utils michael@0: return aWindow.QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIDOMWindowUtils). michael@0: outerWindowID; michael@0: } catch (e) { michael@0: // ... but for observer notifications we need another interface michael@0: return aWindow.QueryInterface(Ci.nsISupportsPRUint64).data; michael@0: } michael@0: } michael@0: michael@0: var checkChrome = function () { michael@0: var loc = window.document.location.href; michael@0: try { michael@0: loc = window.top.document.location.href; michael@0: } catch (e) { michael@0: } michael@0: michael@0: return /^chrome:\/\//.test(loc); michael@0: } michael@0: michael@0: /** michael@0: * Called to get the state of an individual preference. michael@0: * michael@0: * @param aPrefName string The preference to get the state of. michael@0: * @param aDefaultValue any The default value if preference was not found. michael@0: * michael@0: * @returns any The value of the requested preference michael@0: * michael@0: * @see setPref michael@0: * Code by Henrik Skupin: michael@0: */ michael@0: function getPreference(aPrefName, aDefaultValue) { michael@0: try { michael@0: var branch = Services.prefs; michael@0: michael@0: switch (typeof aDefaultValue) { michael@0: case ('boolean'): michael@0: return branch.getBoolPref(aPrefName); michael@0: case ('string'): michael@0: return branch.getCharPref(aPrefName); michael@0: case ('number'): michael@0: return branch.getIntPref(aPrefName); michael@0: default: michael@0: return branch.getComplexValue(aPrefName); michael@0: } michael@0: } catch (e) { michael@0: return aDefaultValue; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Called to set the state of an individual preference. michael@0: * michael@0: * @param aPrefName string The preference to set the state of. michael@0: * @param aValue any The value to set the preference to. michael@0: * michael@0: * @returns boolean Returns true if value was successfully set. michael@0: * michael@0: * @see getPref michael@0: * Code by Henrik Skupin: michael@0: */ michael@0: function setPreference(aName, aValue) { michael@0: try { michael@0: var branch = Services.prefs; michael@0: michael@0: switch (typeof aValue) { michael@0: case ('boolean'): michael@0: branch.setBoolPref(aName, aValue); michael@0: break; michael@0: case ('string'): michael@0: branch.setCharPref(aName, aValue); michael@0: break; michael@0: case ('number'): michael@0: branch.setIntPref(aName, aValue); michael@0: break; michael@0: default: michael@0: branch.setComplexValue(aName, aValue); michael@0: } michael@0: } catch (e) { michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Sleep for the given amount of milliseconds michael@0: * michael@0: * @param {number} milliseconds michael@0: * Sleeps the given number of milliseconds michael@0: */ michael@0: function sleep(milliseconds) { michael@0: var timeup = false; michael@0: michael@0: hwindow.setTimeout(function () { timeup = true; }, milliseconds); michael@0: var thread = Services.tm.currentThread; michael@0: michael@0: while (!timeup) { michael@0: thread.processNextEvent(true); michael@0: } michael@0: michael@0: broker.pass({'function':'utils.sleep()'}); michael@0: } michael@0: michael@0: /** michael@0: * Check if the callback function evaluates to true michael@0: */ michael@0: function assert(callback, message, thisObject) { michael@0: var result = callback.call(thisObject); michael@0: michael@0: if (!result) { michael@0: throw new Error(message || arguments.callee.name + ": Failed for '" + callback + "'"); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Unwraps a node which is wrapped into a XPCNativeWrapper or XrayWrapper michael@0: * michael@0: * @param {DOMnode} Wrapped DOM node michael@0: * @returns {DOMNode} Unwrapped DOM node michael@0: */ michael@0: function unwrapNode(aNode) { michael@0: var node = aNode; michael@0: if (node) { michael@0: // unwrap is not available on older branches (3.5 and 3.6) - Bug 533596 michael@0: if ("unwrap" in XPCNativeWrapper) { michael@0: node = XPCNativeWrapper.unwrap(node); michael@0: } michael@0: else if (node.wrappedJSObject != null) { michael@0: node = node.wrappedJSObject; michael@0: } michael@0: } michael@0: michael@0: return node; michael@0: } michael@0: michael@0: /** michael@0: * Waits for the callback evaluates to true michael@0: */ michael@0: function waitFor(callback, message, timeout, interval, thisObject) { michael@0: broker.log({'function': 'utils.waitFor() - DEPRECATED', michael@0: 'message': 'utils.waitFor() is deprecated. Use assert.waitFor() instead'}); michael@0: assert.waitFor(callback, message, timeout, interval, thisObject); michael@0: } michael@0: michael@0: /** michael@0: * Calculates the x and y chrome offset for an element michael@0: * See https://developer.mozilla.org/en/DOM/window.innerHeight michael@0: * michael@0: * Note this function will not work if the user has custom toolbars (via extension) at the bottom or left/right of the screen michael@0: */ michael@0: function getChromeOffset(elem) { michael@0: var win = elem.ownerDocument.defaultView; michael@0: // Calculate x offset michael@0: var chromeWidth = 0; michael@0: michael@0: if (win["name"] != "sidebar") { michael@0: chromeWidth = win.outerWidth - win.innerWidth; michael@0: } michael@0: michael@0: // Calculate y offset michael@0: var chromeHeight = win.outerHeight - win.innerHeight; michael@0: // chromeHeight == 0 means elem is already in the chrome and doesn't need the addonbar offset michael@0: if (chromeHeight > 0) { michael@0: // window.innerHeight doesn't include the addon or find bar, so account for these if present michael@0: var addonbar = win.document.getElementById("addon-bar"); michael@0: if (addonbar) { michael@0: chromeHeight -= addonbar.scrollHeight; michael@0: } michael@0: michael@0: var findbar = win.document.getElementById("FindToolbar"); michael@0: if (findbar) { michael@0: chromeHeight -= findbar.scrollHeight; michael@0: } michael@0: } michael@0: michael@0: return {'x':chromeWidth, 'y':chromeHeight}; michael@0: } michael@0: michael@0: /** michael@0: * Takes a screenshot of the specified DOM node michael@0: */ michael@0: function takeScreenshot(node, highlights) { michael@0: var rect, win, width, height, left, top, needsOffset; michael@0: // node can be either a window or an arbitrary DOM node michael@0: try { michael@0: // node is an arbitrary DOM node michael@0: win = node.ownerDocument.defaultView; michael@0: rect = node.getBoundingClientRect(); michael@0: width = rect.width; michael@0: height = rect.height; michael@0: top = rect.top; michael@0: left = rect.left; michael@0: // offset for highlights not needed as they will be relative to this node michael@0: needsOffset = false; michael@0: } catch (e) { michael@0: // node is a window michael@0: win = node; michael@0: width = win.innerWidth; michael@0: height = win.innerHeight; michael@0: top = 0; michael@0: left = 0; michael@0: // offset needed for highlights to take 'outerHeight' of window into account michael@0: needsOffset = true; michael@0: } michael@0: michael@0: var canvas = win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); michael@0: canvas.width = width; michael@0: canvas.height = height; michael@0: michael@0: var ctx = canvas.getContext("2d"); michael@0: // Draws the DOM contents of the window to the canvas michael@0: ctx.drawWindow(win, left, top, width, height, "rgb(255,255,255)"); michael@0: michael@0: // This section is for drawing a red rectangle around each element passed in via the highlights array michael@0: if (highlights) { michael@0: ctx.lineWidth = "2"; michael@0: ctx.strokeStyle = "red"; michael@0: ctx.save(); michael@0: michael@0: for (var i = 0; i < highlights.length; ++i) { michael@0: var elem = highlights[i]; michael@0: rect = elem.getBoundingClientRect(); michael@0: michael@0: var offsetY = 0, offsetX = 0; michael@0: if (needsOffset) { michael@0: var offset = getChromeOffset(elem); michael@0: offsetX = offset.x; michael@0: offsetY = offset.y; michael@0: } else { michael@0: // Don't need to offset the window chrome, just make relative to containing node michael@0: offsetY = -top; michael@0: offsetX = -left; michael@0: } michael@0: michael@0: // Draw the rectangle michael@0: ctx.strokeRect(rect.left + offsetX, rect.top + offsetY, rect.width, rect.height); michael@0: } michael@0: } michael@0: michael@0: return canvas.toDataURL("image/jpeg", 0.5); michael@0: } michael@0: michael@0: /** michael@0: * Save the dataURL content to the specified file. It will be stored in either the persisted screenshot or temporary folder. michael@0: * michael@0: * @param {String} aDataURL michael@0: * The dataURL to save michael@0: * @param {String} aFilename michael@0: * Target file name without extension michael@0: * michael@0: * @returns {Object} The hash containing the path of saved file, and the failure bit michael@0: */ michael@0: function saveDataURL(aDataURL, aFilename) { michael@0: var frame = {}; Cu.import('resource://mozmill/modules/frame.js', frame); michael@0: const FILE_PERMISSIONS = parseInt("0644", 8); michael@0: michael@0: var file; michael@0: file = Cc['@mozilla.org/file/local;1'] michael@0: .createInstance(Ci.nsILocalFile); michael@0: file.initWithPath(frame.persisted['screenshots']['path']); michael@0: file.append(aFilename + ".jpg"); michael@0: file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FILE_PERMISSIONS); michael@0: michael@0: // Create an output stream to write to file michael@0: let foStream = Cc["@mozilla.org/network/file-output-stream;1"] michael@0: .createInstance(Ci.nsIFileOutputStream); michael@0: foStream.init(file, 0x02 | 0x08 | 0x10, FILE_PERMISSIONS, foStream.DEFER_OPEN); michael@0: michael@0: let dataURI = NetUtil.newURI(aDataURL, "UTF8", null); michael@0: if (!dataURI.schemeIs("data")) { michael@0: throw TypeError("aDataURL parameter has to have 'data'" + michael@0: " scheme instead of '" + dataURI.scheme + "'"); michael@0: } michael@0: michael@0: // Write asynchronously to buffer; michael@0: // Input and output streams are closed after write michael@0: michael@0: let ready = false; michael@0: let failure = false; michael@0: michael@0: function sync(aStatus) { michael@0: if (!Components.isSuccessCode(aStatus)) { michael@0: failure = true; michael@0: } michael@0: ready = true; michael@0: } michael@0: michael@0: NetUtil.asyncFetch(dataURI, function (aInputStream, aAsyncFetchResult) { michael@0: if (!Components.isSuccessCode(aAsyncFetchResult)) { michael@0: // An error occurred! michael@0: sync(aAsyncFetchResult); michael@0: } else { michael@0: // Consume the input stream. michael@0: NetUtil.asyncCopy(aInputStream, foStream, function (aAsyncCopyResult) { michael@0: sync(aAsyncCopyResult); michael@0: }); michael@0: } michael@0: }); michael@0: michael@0: assert.waitFor(function () { michael@0: return ready; michael@0: }, "DataURL has been saved to '" + file.path + "'"); michael@0: michael@0: return {filename: file.path, failure: failure}; michael@0: } michael@0: michael@0: /** michael@0: * Some very brain-dead timer functions useful for performance optimizations michael@0: * This is only enabled in debug mode michael@0: * michael@0: **/ michael@0: var gutility_mzmltimer = 0; michael@0: /** michael@0: * Starts timer initializing with current EPOC time in milliseconds michael@0: * michael@0: * @returns none michael@0: **/ michael@0: function startTimer(){ michael@0: dump("TIMERCHECK:: starting now: " + Date.now() + "\n"); michael@0: gutility_mzmltimer = Date.now(); michael@0: } michael@0: michael@0: /** michael@0: * Checks the timer and outputs current elapsed time since start of timer. It michael@0: * will print out a message you provide with its "time check" so you can michael@0: * correlate in the log file and figure out elapsed time of specific functions. michael@0: * michael@0: * @param aMsg string The debug message to print with the timer check michael@0: * michael@0: * @returns none michael@0: **/ michael@0: function checkTimer(aMsg){ michael@0: var end = Date.now(); michael@0: dump("TIMERCHECK:: at " + aMsg + " is: " + (end - gutility_mzmltimer) + "\n"); michael@0: }