diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /devtools/client/performance-new/test/browser/helpers.js | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/performance-new/test/browser/helpers.js')
-rw-r--r-- | devtools/client/performance-new/test/browser/helpers.js | 836 |
1 files changed, 836 insertions, 0 deletions
diff --git a/devtools/client/performance-new/test/browser/helpers.js b/devtools/client/performance-new/test/browser/helpers.js new file mode 100644 index 0000000000..9e85bf5920 --- /dev/null +++ b/devtools/client/performance-new/test/browser/helpers.js @@ -0,0 +1,836 @@ +/* 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/. */ +"use strict"; + +/** + * Wait for a single requestAnimationFrame tick. + */ +function tick() { + return new Promise(resolve => requestAnimationFrame(resolve)); +} + +/** + * It can be confusing when waiting for something asynchronously. This function + * logs out a message periodically (every 1 second) in order to create helpful + * log messages. + * @param {string} message + * @returns {Function} + */ +function createPeriodicLogger() { + let startTime = Date.now(); + let lastCount = 0; + let lastMessage = null; + + return message => { + if (lastMessage === message) { + // The messages are the same, check if we should log them. + const now = Date.now(); + const count = Math.floor((now - startTime) / 1000); + if (count !== lastCount) { + info( + `${message} (After ${count} ${count === 1 ? "second" : "seconds"})` + ); + lastCount = count; + } + } else { + // The messages are different, log them now, and reset the waiting time. + info(message); + startTime = Date.now(); + lastCount = 0; + lastMessage = message; + } + }; +} + +/** + * Wait until a condition is fullfilled. + * @param {Function} condition + * @param {string?} logMessage + * @return The truthy result of the condition. + */ +async function waitUntil(condition, message) { + const logPeriodically = createPeriodicLogger(); + + // Loop through the condition. + while (true) { + if (message) { + logPeriodically(message); + } + const result = condition(); + if (result) { + return result; + } + + await tick(); + } +} + +/** + * This function looks inside of a container for some element that has a label. + * It runs in a loop every requestAnimationFrame until it finds the element. If + * it doesn't find the element it throws an error. + * + * @param {Element} container + * @param {string} label + * @returns {Promise<HTMLElement>} + */ +function getElementByLabel(container, label) { + return waitUntil( + () => container.querySelector(`[label="${label}"]`), + `Trying to find the button with the label "${label}".` + ); +} +/* exported getElementByLabel */ + +/** + * This function looks inside of a container for some element that has a tooltip. + * It runs in a loop every requestAnimationFrame until it finds the element. If + * it doesn't find the element it throws an error. + * + * @param {Element} container + * @param {string} tooltip + * @returns {Promise<HTMLElement>} + */ +function getElementByTooltip(container, tooltip) { + return waitUntil( + () => container.querySelector(`[tooltiptext="${tooltip}"]`), + `Trying to find the button with the tooltip "${tooltip}".` + ); +} +/* exported getElementByTooltip */ + +/** + * This function will select a node from the XPath. + * @returns {HTMLElement?} + */ +function getElementByXPath(document, path) { + return document.evaluate( + path, + document, + null, + XPathResult.FIRST_ORDERED_NODE_TYPE, + null + ).singleNodeValue; +} +/* exported getElementByXPath */ + +/** + * This function looks inside of a document for some element that contains + * the given text. It runs in a loop every requestAnimationFrame until it + * finds the element. If it doesn't find the element it throws an error. + * + * @param {HTMLDocument} document + * @param {string} text + * @returns {Promise<HTMLElement>} + */ +async function getElementFromDocumentByText(document, text) { + // Fallback on aria-label if there are no results for the text xpath. + const xpath = `//*[contains(text(), '${text}')] | //*[contains(@aria-label, '${text}')]`; + return waitUntil( + () => getElementByXPath(document, xpath), + `Trying to find the element with the text "${text}".` + ); +} +/* exported getElementFromDocumentByText */ + +/** + * This function is similar to getElementFromDocumentByText, but it immediately + * returns and does not wait for an element to exist. + * @param {HTMLDocument} document + * @param {string} text + * @returns {HTMLElement?} + */ +function maybeGetElementFromDocumentByText(document, text) { + info(`Immediately trying to find the element with the text "${text}".`); + const xpath = `//*[contains(text(), '${text}')]`; + return getElementByXPath(document, xpath); +} +/* exported maybeGetElementFromDocumentByText */ + +/** + * Make sure the profiler popup is enabled. + */ +async function makeSureProfilerPopupIsEnabled() { + info("Make sure the profiler popup is enabled."); + + info("> Load the profiler menu button."); + const { ProfilerMenuButton } = ChromeUtils.import( + "resource://devtools/client/performance-new/popup/menu-button.jsm.js" + ); + + if (!ProfilerMenuButton.isInNavbar()) { + // Make sure the feature flag is enabled. + SpecialPowers.pushPrefEnv({ + set: [["devtools.performance.popup.feature-flag", true]], + }); + + info("> The menu button is not in the nav bar, add it."); + ProfilerMenuButton.addToNavbar(document); + + await waitUntil( + () => gBrowser.ownerDocument.getElementById("profiler-button"), + "> Waiting until the profiler button is added to the browser." + ); + + await SimpleTest.promiseFocus(gBrowser.ownerGlobal); + + registerCleanupFunction(() => { + info( + "Clean up after the test by disabling the profiler popup menu button." + ); + if (!ProfilerMenuButton.isInNavbar()) { + throw new Error( + "Expected the profiler popup to still be in the navbar during the test cleanup." + ); + } + ProfilerMenuButton.remove(); + }); + } else { + info("> The menu button was already enabled."); + } +} +/* exported makeSureProfilerPopupIsEnabled */ + +/** + * XUL popups will fire the popupshown and popuphidden events. These will fire for + * any type of popup in the browser. This function waits for one of those events, and + * checks that the viewId of the popup is PanelUI-profiler + * + * @param {Window} window + * @param {"popupshown" | "popuphidden"} eventName + * @returns {Promise<void>} + */ +function waitForProfilerPopupEvent(window, eventName) { + return new Promise(resolve => { + function handleEvent(event) { + if (event.target.getAttribute("viewId") === "PanelUI-profiler") { + window.removeEventListener(eventName, handleEvent); + resolve(); + } + } + window.addEventListener(eventName, handleEvent); + }); +} +/* exported waitForProfilerPopupEvent */ + +/** + * Do not use this directly in a test. Prefer withPopupOpen and openPopupAndEnsureCloses. + * + * This function toggles the profiler menu button, and then uses user gestures + * to click it open. It waits a tick to make sure it has a chance to initialize. + * @param {Window} window + * @return {Promise<void>} + */ +async function _toggleOpenProfilerPopup(window) { + info("Toggle open the profiler popup."); + + info("> Find the profiler menu button."); + const profilerDropmarker = window.document.getElementById( + "profiler-button-dropmarker" + ); + if (!profilerDropmarker) { + throw new Error( + "Could not find the profiler button dropmarker in the toolbar." + ); + } + + const popupShown = waitForProfilerPopupEvent(window, "popupshown"); + + info("> Trigger a click on the profiler button dropmarker."); + await EventUtils.synthesizeMouseAtCenter(profilerDropmarker, {}, window); + + if (profilerDropmarker.getAttribute("open") !== "true") { + throw new Error( + "This test assumes that the button will have an open=true attribute after clicking it." + ); + } + + info("> Wait for the popup to be shown."); + await popupShown; + // Also wait a tick in case someone else is subscribing to the "popupshown" event + // and is doing synchronous work with it. + await tick(); +} + +/** + * Do not use this directly in a test. Prefer withPopupOpen. + * + * This function uses a keyboard shortcut to close the profiler popup. + * @param {Window} window + * @return {Promise<void>} + */ +async function _closePopup(window) { + const popupHiddenPromise = waitForProfilerPopupEvent(window, "popuphidden"); + info("> Trigger an escape key to hide the popup"); + EventUtils.synthesizeKey("KEY_Escape"); + + info("> Wait for the popup to be hidden."); + await popupHiddenPromise; + // Also wait a tick in case someone else is subscribing to the "popuphidden" event + // and is doing synchronous work with it. + await tick(); +} + +/** + * Perform some action on the popup, and close it afterwards. + * @param {Window} window + * @param {() => Promise<void>} callback + */ +async function withPopupOpen(window, callback) { + await _toggleOpenProfilerPopup(window); + await callback(); + await _closePopup(window); +} +/* exported withPopupOpen */ + +/** + * This function opens the profiler popup, but also ensures that something else closes + * it before the end of the test. This is useful for tests that trigger the profiler + * popup to close through an implicit action, like opening a tab. + * + * @param {Window} window + * @param {() => Promise<void>} callback + */ +async function openPopupAndEnsureCloses(window, callback) { + await _toggleOpenProfilerPopup(window); + // We want to ensure the popup gets closed by the test, during the callback. + const popupHiddenPromise = waitForProfilerPopupEvent(window, "popuphidden"); + await callback(); + info("> Verifying that the popup was closed by the test."); + await popupHiddenPromise; +} +/* exported openPopupAndEnsureCloses */ + +/** + * This function overwrites the default profiler.firefox.com URL for tests. This + * ensures that the tests do not attempt to access external URLs. + * The origin needs to be on the allowlist in validateProfilerWebChannelUrl, + * otherwise the WebChannel won't work. ("http://example.com" is on that list.) + * + * @param {string} origin - For example: http://example.com + * @param {string} pathname - For example: /my/testing/frontend.html + * @returns {Promise} + */ +function setProfilerFrontendUrl(origin, pathname) { + return SpecialPowers.pushPrefEnv({ + set: [ + // Make sure observer and testing function run in the same process + ["devtools.performance.recording.ui-base-url", origin], + ["devtools.performance.recording.ui-base-url-path", pathname], + ], + }); +} +/* exported setProfilerFrontendUrl */ + +/** + * This function checks the document title of a tab to see what the state is. + * This creates a simple messaging mechanism between the content page and the + * test harness. This function runs in a loop every requestAnimationFrame, and + * checks for a sucess title. In addition, an "initialTitle" and "errorTitle" + * can be specified for nicer test output. + * @param {object} + * { + * initialTitle: string, + * successTitle: string, + * errorTitle: string + * } + */ +async function checkTabLoadedProfile({ + initialTitle, + successTitle, + errorTitle, +}) { + const logPeriodically = createPeriodicLogger(); + + info("Attempting to see if the selected tab can receive a profile."); + + return waitUntil(() => { + switch (gBrowser.selectedTab.label) { + case initialTitle: + logPeriodically(`> Waiting for the profile to be received.`); + return false; + case successTitle: + ok(true, "The profile was successfully injected to the page"); + BrowserTestUtils.removeTab(gBrowser.selectedTab); + return true; + case errorTitle: + throw new Error( + "The fake frontend indicated that there was an error injecting the profile." + ); + default: + logPeriodically(`> Waiting for the fake frontend tab to be loaded.`); + return false; + } + }); +} +/* exported checkTabLoadedProfile */ + +/** + * This function checks the url of a tab so we can assert the frontend's url + * with our expected url. This function runs in a loop every + * requestAnimationFrame, and checks for a initialTitle. Asserts as soon as it + * finds that title. We don't have to look for success title or error title + * since we only care about the url. + * @param {{ + * initialTitle: string, + * successTitle: string, + * errorTitle: string, + * expectedUrl: string + * }} + */ +async function waitForTabUrl({ + initialTitle, + successTitle, + errorTitle, + expectedUrl, +}) { + const logPeriodically = createPeriodicLogger(); + + info(`Waiting for the selected tab to have the url "${expectedUrl}".`); + + return waitUntil(() => { + switch (gBrowser.selectedTab.label) { + case initialTitle: + case successTitle: + if (gBrowser.currentURI.spec === expectedUrl) { + ok(true, `The selected tab has the url ${expectedUrl}`); + BrowserTestUtils.removeTab(gBrowser.selectedTab); + return true; + } + throw new Error( + `Found a different url on the fake frontend: ${gBrowser.currentURI.spec} (expecting ${expectedUrl})` + ); + case errorTitle: + throw new Error( + "The fake frontend indicated that there was an error injecting the profile." + ); + default: + logPeriodically(`> Waiting for the fake frontend tab to be loaded.`); + return false; + } + }); +} +/* exported waitForTabUrl */ + +/** + * This function checks the document title of a tab as an easy way to pass + * messages from a content page to the mochitest. + * @param {string} title + */ +async function waitForTabTitle(title) { + const logPeriodically = createPeriodicLogger(); + + info(`Waiting for the selected tab to have the title "${title}".`); + + return waitUntil(() => { + if (gBrowser.selectedTab.label === title) { + ok(true, `The selected tab has the title ${title}`); + return true; + } + logPeriodically(`> Waiting for the tab title to change.`); + return false; + }); +} +/* exported waitForTabTitle */ + +/** + * Open about:profiling in a new tab, and output helpful log messages. + * + * @template T + * @param {(Document) => T} callback + * @returns {Promise<T>} + */ +function withAboutProfiling(callback) { + info("Begin to open about:profiling in a new tab."); + return BrowserTestUtils.withNewTab( + "about:profiling", + async contentBrowser => { + info("about:profiling is now open in a tab."); + await TestUtils.waitForCondition( + () => + contentBrowser.contentDocument.getElementById("root") + .firstElementChild, + "Document's root has been populated" + ); + return callback(contentBrowser.contentDocument); + } + ); +} +/* exported withAboutProfiling */ + +/** + * Open DevTools and view the performance-new tab. After running the callback, clean + * up the test. + * + * @param {string} [url="about:blank"] url for the new tab + * @param {(Document, Document) => unknown} callback: the first parameter is the + * devtools panel's document, the + * second parameter is the opened tab's + * document. + * @param {Window} [aWindow] The browser's window object we target + * @returns {Promise<void>} + */ +async function withDevToolsPanel(url, callback, aWindow = window) { + if (typeof url === "function") { + aWindow = callback ?? window; + callback = url; + url = "about:blank"; + } + + const { gBrowser } = aWindow; + + const { + gDevTools, + } = require("resource://devtools/client/framework/devtools.js"); + + info(`Create a new tab with url "${url}".`); + const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + + info("Begin to open the DevTools and the performance-new panel."); + const toolbox = await gDevTools.showToolboxForTab(tab, { + toolId: "performance", + }); + + const { document } = toolbox.getCurrentPanel().panelWin; + + info("The performance-new panel is now open and ready to use."); + await callback(document, tab.linkedBrowser.contentDocument); + + info("About to remove the about:blank tab"); + await toolbox.destroy(); + + // The previous asynchronous functions may resolve within a tick after opening a new tab. + // We shouldn't remove the newly opened tab in the same tick. + // Wait for the next tick here. + await TestUtils.waitForTick(); + + // Take care to register the TabClose event before we call removeTab, to avoid + // race issues. + const waitForClosingPromise = BrowserTestUtils.waitForTabClosing(tab); + BrowserTestUtils.removeTab(tab); + info("Requested closing the about:blank tab, waiting..."); + await waitForClosingPromise; + info("The about:blank tab is now removed."); +} +/* exported withDevToolsPanel */ + +/** + * Start and stop the profiler to get the current active configuration. This is + * done programmtically through the nsIProfiler interface, rather than through click + * interactions, since the about:profiling page does not include buttons to control + * the recording. + * + * @returns {Object} + */ +function getActiveConfiguration() { + const BackgroundJSM = ChromeUtils.import( + "resource://devtools/client/performance-new/shared/background.jsm.js" + ); + + const { startProfiler, stopProfiler } = BackgroundJSM; + + info("Start the profiler with the current about:profiling configuration."); + startProfiler("aboutprofiling"); + + // Immediately pause the sampling, to make sure the test runs fast. The profiler + // only needs to be started to initialize the configuration. + Services.profiler.Pause(); + + const { activeConfiguration } = Services.profiler; + if (!activeConfiguration) { + throw new Error( + "Expected to find an active configuration for the profile." + ); + } + + info("Stop the profiler after getting the active configuration."); + stopProfiler(); + + return activeConfiguration; +} +/* exported getActiveConfiguration */ + +/** + * Start the profiler programmatically and check that the active configuration has + * a feature enabled + * + * @param {string} feature + * @return {boolean} + */ +function activeConfigurationHasFeature(feature) { + const { features } = getActiveConfiguration(); + return features.includes(feature); +} +/* exported activeConfigurationHasFeature */ + +/** + * Start the profiler programmatically and check that the active configuration is + * tracking a thread. + * + * @param {string} thread + * @return {boolean} + */ +function activeConfigurationHasThread(thread) { + const { threads } = getActiveConfiguration(); + return threads.includes(thread); +} +/* exported activeConfigurationHasThread */ + +/** + * Use user driven events to start the profiler, and then get the active configuration + * of the profiler. This is similar to functions in the head.js file, but is specific + * for the DevTools situation. The UI complains if the profiler stops unexpectedly. + * + * @param {Document} document + * @param {string} feature + * @returns {boolean} + */ +async function devToolsActiveConfigurationHasFeature(document, feature) { + info("Get the active configuration of the profiler via user driven events."); + const start = await getActiveButtonFromText(document, "Start recording"); + info("Click the button to start recording."); + start.click(); + + // Get the cancel button first, so that way we know the profile has actually + // been recorded. + const cancel = await getActiveButtonFromText(document, "Cancel recording"); + + const { activeConfiguration } = Services.profiler; + if (!activeConfiguration) { + throw new Error( + "Expected to find an active configuration for the profile." + ); + } + + info("Click the cancel button to discard the profile.."); + cancel.click(); + + // Wait until the start button is back. + await getActiveButtonFromText(document, "Start recording"); + + return activeConfiguration.features.includes(feature); +} +/* exported devToolsActiveConfigurationHasFeature */ + +/** + * This adapts the expectation using the current build's available profiler + * features. + * @param {string} fixture It can be either already trimmed or untrimmed. + * @returns {string} + */ +function _adaptCustomPresetExpectationToCustomBuild(fixture) { + const supportedFeatures = Services.profiler.GetFeatures(); + info("Supported features are: " + supportedFeatures.join(", ")); + + // Some platforms do not support stack walking, we can adjust the passed + // fixture so that tests are passing in these platforms too. + // Most notably MacOS outside of Nightly and DevEdition. + if (!supportedFeatures.includes("stackwalk")) { + info( + "Supported features do not include stackwalk, let's remove the Native Stacks from the expected output." + ); + fixture = fixture.replace(/^.*Native Stacks.*\n/m, ""); + } + + return fixture; +} + +/** + * Get the content of the preset description. + * @param {Element} devtoolsDocument + * @returns {string} + */ +function getDevtoolsCustomPresetContent(devtoolsDocument) { + return devtoolsDocument.querySelector(".perf-presets-custom").innerText; +} +/* exported getDevtoolsCustomPresetContent */ + +/** + * This checks if the content of the preset description equals the fixture in + * string form. + * @param {Element} devtoolsDocument + * @param {string} fixture + */ +function checkDevtoolsCustomPresetContent(devtoolsDocument, fixture) { + // This removes all indentations and any start or end new line and other space characters. + fixture = fixture.replace(/^\s+/gm, "").trim(); + // This removes unavailable features from the fixture content. + fixture = _adaptCustomPresetExpectationToCustomBuild(fixture); + is(getDevtoolsCustomPresetContent(devtoolsDocument), fixture); +} +/* exported checkDevtoolsCustomPresetContent */ + +/** + * Selects an element with some given text, then it walks up the DOM until it finds + * an input or select element via a call to querySelector. + * + * @param {Document} document + * @param {string} text + * @param {HTMLInputElement} + */ +async function getNearestInputFromText(document, text) { + const textElement = await getElementFromDocumentByText(document, text); + if (textElement.control) { + // This is a label, just grab the input. + return textElement.control; + } + // A non-label node + let next = textElement; + while ((next = next.parentElement)) { + const input = next.querySelector("input, select"); + if (input) { + return input; + } + } + throw new Error("Could not find an input or select near the text element."); +} +/* exported getNearestInputFromText */ + +/** + * Grabs the closest button element from a given snippet of text, and make sure + * the button is not disabled. + * + * @param {Document} document + * @param {string} text + * @param {HTMLButtonElement} + */ +async function getActiveButtonFromText(document, text) { + // This could select a span inside the button, or the button itself. + let button = await getElementFromDocumentByText(document, text); + + while (button.tagName !== "button") { + // Walk up until a button element is found. + button = button.parentElement; + if (!button) { + throw new Error(`Unable to find a button from the text "${text}"`); + } + } + + await waitUntil( + () => !button.disabled, + "Waiting until the button is not disabled." + ); + + return button; +} +/* exported getActiveButtonFromText */ + +/** + * Wait until the profiler menu button is added. + * + * @returns Promise<void> + */ +async function waitForProfilerMenuButton() { + info("Checking if the profiler menu button is enabled."); + await waitUntil( + () => gBrowser.ownerDocument.getElementById("profiler-button"), + "> Waiting until the profiler button is added to the browser." + ); +} +/* exported waitForProfilerMenuButton */ + +/** + * Make sure the profiler popup is disabled for the test. + */ +async function makeSureProfilerPopupIsDisabled() { + info("Make sure the profiler popup is dsiabled."); + + info("> Load the profiler menu button module."); + const { ProfilerMenuButton } = ChromeUtils.import( + "resource://devtools/client/performance-new/popup/menu-button.jsm.js" + ); + + const isOriginallyInNavBar = ProfilerMenuButton.isInNavbar(); + + if (isOriginallyInNavBar) { + info("> The menu button is in the navbar, remove it for this test."); + ProfilerMenuButton.remove(); + } else { + info("> The menu button was not in the navbar yet."); + } + + registerCleanupFunction(() => { + info("Revert the profiler menu button to be back in its original place"); + if (isOriginallyInNavBar !== ProfilerMenuButton.isInNavbar()) { + ProfilerMenuButton.remove(); + } + }); +} +/* exported makeSureProfilerPopupIsDisabled */ + +/** + * Open the WebChannel test document, that will enable the profiler popup via + * WebChannel. + * @param {Function} callback + */ +function withWebChannelTestDocument(callback) { + return BrowserTestUtils.withNewTab( + { + gBrowser, + url: "http://example.com/browser/devtools/client/performance-new/test/browser/webchannel.html", + }, + callback + ); +} +/* exported withWebChannelTestDocument */ + +// This has been stolen from the great library dom-testing-library. +// See https://github.com/testing-library/dom-testing-library/blob/91b9dc3b6f5deea88028e97aab15b3b9f3289a2a/src/events.js#L104-L123 +// function written after some investigation here: +// https://github.com/facebook/react/issues/10135#issuecomment-401496776 +function setNativeValue(element, value) { + const { set: valueSetter } = + Object.getOwnPropertyDescriptor(element, "value") || {}; + const prototype = Object.getPrototypeOf(element); + const { set: prototypeValueSetter } = + Object.getOwnPropertyDescriptor(prototype, "value") || {}; + if (prototypeValueSetter && valueSetter !== prototypeValueSetter) { + prototypeValueSetter.call(element, value); + } else { + /* istanbul ignore if */ + // eslint-disable-next-line no-lonely-if -- Can't be ignored by istanbul otherwise + if (valueSetter) { + valueSetter.call(element, value); + } else { + throw new Error("The given element does not have a value setter"); + } + } +} +/* exported setNativeValue */ + +/** + * Set a React-friendly input value. Doing this the normal way doesn't work. + * This reuses the previous function setNativeValue stolen from + * dom-testing-library. + * + * See https://github.com/facebook/react/issues/10135 + * + * @param {HTMLInputElement} input + * @param {string} value + */ +function setReactFriendlyInputValue(input, value) { + setNativeValue(input, value); + + // 'change' instead of 'input', see https://github.com/facebook/react/issues/11488#issuecomment-381590324 + input.dispatchEvent(new Event("change", { bubbles: true })); +} +/* exported setReactFriendlyInputValue */ + +/** + * The recording state is the internal state machine that represents the async + * operations that are going on in the profiler. This function sets up a helper + * that will obtain the Redux store and query this internal state. This is useful + * for unit testing purposes. + * + * @param {Document} document + */ +function setupGetRecordingState(document) { + const selectors = require("resource://devtools/client/performance-new/store/selectors.js"); + const store = document.defaultView.gStore; + if (!store) { + throw new Error("Could not find the redux store on the window object."); + } + return () => selectors.getRecordingState(store.getState()); +} +/* exported setupGetRecordingState */ |