/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; // Load the shared test helpers into this compartment. /* import-globals-from ../../../shared/test/shared-head.js */ Services.scriptloader.loadSubScript( "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", this ); // Load the shared Redux helpers into this compartment. /* import-globals-from ../../../shared/test/shared-redux-head.js */ Services.scriptloader.loadSubScript( "chrome://mochitests/content/browser/devtools/client/shared/test/shared-redux-head.js", this ); var { censusDisplays, censusState, snapshotState: states, } = require("devtools/client/memory/constants"); var { L10N } = require("devtools/client/memory/utils"); Services.prefs.setBoolPref("devtools.memory.enabled", true); /** * Open the memory panel for the given tab. */ this.openMemoryPanel = async function(tab) { info("Opening memory panel."); const target = await TargetFactory.forTab(tab); const toolbox = await gDevTools.showToolbox(target, "memory"); info("Memory panel shown successfully."); const panel = toolbox.getCurrentPanel(); return { tab, panel }; }; /** * Close the memory panel for the given tab. */ this.closeMemoryPanel = async function(tab) { info("Closing memory panel."); const target = await TargetFactory.forTab(tab); const toolbox = gDevTools.getToolbox(target); await toolbox.destroy(); info("Closed memory panel successfully."); }; /** * Return a test function that adds a tab with the given url, opens the memory * panel, runs the given generator, closes the memory panel, removes the tab, * and finishes. * * Example usage: * * this.test = makeMemoryTest(TEST_URL, async function ({ tab, panel }) { * // Your tests go here... * }); */ function makeMemoryTest(url, generator) { return async function() { waitForExplicitFinish(); // It can take a long time to save a snapshot to disk, read the snapshots // back from disk, and finally perform analyses on them. requestLongerTimeout(2); const tab = await addTab(url); const results = await openMemoryPanel(tab); try { await generator(results); } catch (err) { ok(false, "Got an error: " + DevToolsUtils.safeErrorString(err)); } await closeMemoryPanel(tab); await removeTab(tab); finish(); }; } function dumpn(msg) { dump(`MEMORY-TEST: ${msg}\n`); } /** * Returns a promise that will resolve when the provided store matches * the expected array. expectedStates is an array of dominatorTree states. * Expectations : * - store.getState().snapshots.length == expected.length * - snapshots[i].dominatorTree.state == expected[i] * * @param {Store} store * @param {Array} expectedStates [description] * @return {Promise} */ function waitUntilDominatorTreeState(store, expected) { const predicate = () => { const snapshots = store.getState().snapshots; return ( snapshots.length === expected.length && expected.every((state, i) => { return ( snapshots[i].dominatorTree && snapshots[i].dominatorTree.state === state ); }) ); }; info(`Waiting for dominator trees to be of state: ${expected}`); return waitUntilState(store, predicate); } function takeSnapshot(window) { const { gStore, document } = window; const snapshotCount = gStore.getState().snapshots.length; info("Taking snapshot..."); document.querySelector(".devtools-toolbar .take-snapshot").click(); return waitUntilState( gStore, () => gStore.getState().snapshots.length === snapshotCount + 1 ); } function clearSnapshots(window) { const { gStore, document } = window; document.querySelector(".devtools-toolbar .clear-snapshots").click(); return waitUntilState(gStore, () => gStore .getState() .snapshots.every(snapshot => snapshot.state !== states.READ) ); } /** * Sets the current requested display and waits for the selected snapshot to use * it and complete the new census that entails. */ function setCensusDisplay(window, display) { info(`Setting census display to ${display}...`); const { gStore, gHeapAnalysesClient } = window; // XXX: Should handle this via clicking the DOM, but React doesn't // fire the onChange event, so just change it in the store. // window.document.querySelector(`.select-display`).value = type; gStore.dispatch( require("devtools/client/memory/actions/census-display").setCensusDisplayAndRefresh( gHeapAnalysesClient, display ) ); return waitUntilState(window.gStore, () => { const selected = window.gStore.getState().snapshots.find(s => s.selected); return ( selected.state === states.READ && selected.census && selected.census.state === censusState.SAVED && selected.census.display === display ); }); } /** * Get the snapshot tatus text currently displayed, or null if none is * displayed. * * @param {Document} document */ function getDisplayedSnapshotStatus(document) { const status = document.querySelector(".snapshot-status"); return status ? status.textContent.trim() : null; } /** * Get the index of the currently selected snapshot. * * @return {Number} */ function getSelectedSnapshotIndex(store) { const snapshots = store.getState().snapshots; const selectedSnapshot = snapshots.find(s => s.selected); return snapshots.indexOf(selectedSnapshot); } /** * Returns a promise that will resolve when the snapshot with provided index * becomes selected. * * @return {Promise} */ function waitUntilSnapshotSelected(store, snapshotIndex) { return waitUntilState( store, state => state.snapshots[snapshotIndex] && state.snapshots[snapshotIndex].selected === true ); } /** * Wait until the state has censuses in a certain state. * * @return {Promise} */ function waitUntilCensusState(store, getCensus, expected) { const predicate = () => { const snapshots = store.getState().snapshots; info( "Current census state:" + snapshots.map(x => (getCensus(x) ? getCensus(x).state : null)) ); return ( snapshots.length === expected.length && expected.every((state, i) => { const census = getCensus(snapshots[i]); return ( state === "*" || (!census && !state) || (census && census.state === state) ); }) ); }; info(`Waiting for snapshot censuses to be of state: ${expected}`); return waitUntilState(store, predicate); } /** * Mock out the requestAnimationFrame. * * @return {Object} * @function nextFrame * Call the last queued function * @function raf * The mocked raf function * @function timesCalled * How many times the RAF has been called */ function createRAFMock() { let queuedFns = []; const mock = { timesCalled: 0 }; mock.nextFrame = function() { const thisQueue = queuedFns; queuedFns = []; for (let i = 0; i < thisQueue.length; i++) { thisQueue[i](); } }; mock.raf = function(fn) { mock.timesCalled++; queuedFns.push(fn); }; return mock; } /** * Test to see if two floats are equivalent. * * @param {Float} a * @param {Float} b * @return {Boolean} */ function floatEquality(a, b) { const EPSILON = 0.00000000001; const equals = Math.abs(a - b) < EPSILON; if (!equals) { info(`${a} not equal to ${b}`); } return equals; }