/* 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/. */ let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); let {TargetFactory, require} = devtools; let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {}); let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {}); const {DOMHelpers} = Cu.import("resource:///modules/devtools/DOMHelpers.jsm", {}); const {Hosts} = require("devtools/framework/toolbox-hosts"); const {defer} = require("sdk/core/promise"); const DevToolsUtils = require("devtools/toolkit/DevToolsUtils"); DevToolsUtils.testing = true; SimpleTest.registerCleanupFunction(() => { DevToolsUtils.testing = false; }); const TEST_URI_ROOT = "http://example.com/browser/browser/devtools/shared/test/"; const OPTIONS_VIEW_URL = TEST_URI_ROOT + "doc_options-view.xul"; /** * Open a new tab at a URL and call a callback on load */ function addTab(aURL, aCallback) { waitForExplicitFinish(); gBrowser.selectedTab = gBrowser.addTab(); content.location = aURL; let tab = gBrowser.selectedTab; let browser = gBrowser.getBrowserForTab(tab); function onTabLoad() { browser.removeEventListener("load", onTabLoad, true); aCallback(browser, tab, browser.contentDocument); } browser.addEventListener("load", onTabLoad, true); } function promiseTab(aURL) { return new Promise(resolve => addTab(aURL, resolve)); } registerCleanupFunction(function* tearDown() { let target = TargetFactory.forTab(gBrowser.selectedTab); yield gDevTools.closeToolbox(target); while (gBrowser.tabs.length > 1) { gBrowser.removeCurrentTab(); } console = undefined; }); function catchFail(func) { return function() { try { return func.apply(null, arguments); } catch (ex) { ok(false, ex); console.error(ex); finish(); throw ex; } }; } /** * Polls a given function waiting for the given value. * * @param object aOptions * Options object with the following properties: * - validator * A validator function that should return the expected value. This is * called every few milliseconds to check if the result is the expected * one. When the returned result is the expected one, then the |success| * function is called and polling stops. If |validator| never returns * the expected value, then polling timeouts after several tries and * a failure is recorded - the given |failure| function is invoked. * - success * A function called when the validator function returns the expected * value. * - failure * A function called if the validator function timeouts - fails to return * the expected value in the given time. * - name * Name of test. This is used to generate the success and failure * messages. * - timeout * Timeout for validator function, in milliseconds. Default is 5000 ms. * - value * The expected value. If this option is omitted then the |validator| * function must return a trueish value. * Each of the provided callback functions will receive two arguments: * the |aOptions| object and the last value returned by |validator|. */ function waitForValue(aOptions) { let start = Date.now(); let timeout = aOptions.timeout || 5000; let lastValue; function wait(validatorFn, successFn, failureFn) { if ((Date.now() - start) > timeout) { // Log the failure. ok(false, "Timed out while waiting for: " + aOptions.name); let expected = "value" in aOptions ? "'" + aOptions.value + "'" : "a trueish value"; info("timeout info :: got '" + lastValue + "', expected " + expected); failureFn(aOptions, lastValue); return; } lastValue = validatorFn(aOptions, lastValue); let successful = "value" in aOptions ? lastValue == aOptions.value : lastValue; if (successful) { ok(true, aOptions.name); successFn(aOptions, lastValue); } else { setTimeout(() => { wait(validatorFn, successFn, failureFn); }, 100); } } wait(aOptions.validator, aOptions.success, aOptions.failure); } function oneTimeObserve(name, callback) { var func = function() { Services.obs.removeObserver(func, name); callback(); }; Services.obs.addObserver(func, name, false); } let createHost = Task.async(function*(type = "bottom", src = "data:text/html;charset=utf-8,") { let host = new Hosts[type](gBrowser.selectedTab); let iframe = yield host.create(); yield new Promise(resolve => { let domHelper = new DOMHelpers(iframe.contentWindow); iframe.setAttribute("src", src); domHelper.onceDOMReady(resolve); }); return [host, iframe.contentWindow, iframe.contentDocument]; }); /** * Load the Telemetry utils, then stub Telemetry.prototype.log in order to * record everything that's logged in it. * Store all recordings on Telemetry.telemetryInfo. * @return {Telemetry} */ function loadTelemetryAndRecordLogs() { info("Mock the Telemetry log function to record logged information"); let Telemetry = require("devtools/shared/telemetry"); Telemetry.prototype.telemetryInfo = {}; Telemetry.prototype._oldlog = Telemetry.prototype.log; Telemetry.prototype.log = function(histogramId, value) { if (!this.telemetryInfo) { // Can be removed when Bug 992911 lands (see Bug 1011652 Comment 10) return; } if (histogramId) { if (!this.telemetryInfo[histogramId]) { this.telemetryInfo[histogramId] = []; } this.telemetryInfo[histogramId].push(value); } }; return Telemetry; } /** * Stop recording the Telemetry logs and put back the utils as it was before. */ function stopRecordingTelemetryLogs(Telemetry) { Telemetry.prototype.log = Telemetry.prototype._oldlog; delete Telemetry.prototype._oldlog; delete Telemetry.prototype.telemetryInfo; } /** * Check the correctness of the data recorded in Telemetry after * loadTelemetryAndRecordLogs was called. */ function checkTelemetryResults(Telemetry) { let result = Telemetry.prototype.telemetryInfo; for (let [histId, value] of Iterator(result)) { if (histId.endsWith("OPENED_PER_USER_FLAG")) { ok(value.length === 1 && value[0] === true, "Per user value " + histId + " has a single value of true"); } else if (histId.endsWith("OPENED_BOOLEAN")) { ok(value.length > 1, histId + " has more than one entry"); let okay = value.every(function(element) { return element === true; }); ok(okay, "All " + histId + " entries are === true"); } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) { ok(value.length > 1, histId + " has more than one entry"); let okay = value.every(function(element) { return element > 0; }); ok(okay, "All " + histId + " entries have time > 0"); } } } /** * Open and close the toolbox in the current browser tab, several times, waiting * some amount of time in between. * @param {Number} nbOfTimes * @param {Number} usageTime in milliseconds * @param {String} toolId */ function* openAndCloseToolbox(nbOfTimes, usageTime, toolId) { for (let i = 0; i < nbOfTimes; i ++) { info("Opening toolbox " + (i + 1)); let target = TargetFactory.forTab(gBrowser.selectedTab); yield gDevTools.showToolbox(target, toolId) // We use a timeout to check the toolbox's active time yield new Promise(resolve => setTimeout(resolve, usageTime)); info("Closing toolbox " + (i + 1)); yield gDevTools.closeToolbox(target); } } /** * Synthesize a profile for testing. */ function synthesizeProfileForTest(samples) { const RecordingUtils = devtools.require("devtools/performance/recording-utils"); samples.unshift({ time: 0, frames: [] }); let uniqueStacks = new RecordingUtils.UniqueStacks(); return RecordingUtils.deflateThread({ samples: samples, markers: [] }, uniqueStacks); } /** * Waits until a predicate returns true. * * @param function predicate * Invoked once in a while until it returns true. * @param number interval [optional] * How often the predicate is invoked, in milliseconds. */ function waitUntil(predicate, interval = 10) { if (predicate()) { return Promise.resolve(true); } return new Promise(resolve => { setTimeout(function() { waitUntil(predicate).then(() => resolve(true)); }, interval); }); } /** * Show the presets list sidebar in the cssfilter widget popup * @param {CSSFilterWidget} widget * @return {Promise} */ function showFilterPopupPresets(widget) { let onRender = widget.once("render"); widget._togglePresets(); return onRender; } /** * Show presets list and create a sample preset with the name and value provided * @param {CSSFilterWidget} widget * @param {string} name * @param {string} value * @return {Promise} */ let showFilterPopupPresetsAndCreatePreset = Task.async(function*(widget, name, value) { yield showFilterPopupPresets(widget); let onRender = widget.once("render"); widget.setCssValue(value); yield onRender; let footer = widget.el.querySelector(".presets-list .footer"); footer.querySelector("input").value = name; onRender = widget.once("render"); widget._savePreset({ preventDefault: () => {} }); yield onRender; });