diff options
Diffstat (limited to 'devtools/client/inspector/test/shared-head.js')
-rw-r--r-- | devtools/client/inspector/test/shared-head.js | 1002 |
1 files changed, 1002 insertions, 0 deletions
diff --git a/devtools/client/inspector/test/shared-head.js b/devtools/client/inspector/test/shared-head.js new file mode 100644 index 0000000000..670208443e --- /dev/null +++ b/devtools/client/inspector/test/shared-head.js @@ -0,0 +1,1002 @@ +/* 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"; + +/* eslint no-unused-vars: [2, {"vars": "local"}] */ +/* globals getHighlighterTestFront, openToolboxForTab, gBrowser */ +/* import-globals-from ../../shared/test/shared-head.js */ + +var { + getInplaceEditorForSpan: inplaceEditor, +} = require("resource://devtools/client/shared/inplace-editor.js"); + +// This file contains functions related to the inspector that are also of interest to +// other test directores as well. + +/** + * Open the toolbox, with the inspector tool visible. + * @param {String} hostType Optional hostType, as defined in Toolbox.HostType + * @return {Promise} A promise that resolves when the inspector is ready.The promise + * resolves with an object containing the following properties: + * - toolbox + * - inspector + * - highlighterTestFront + */ +var openInspector = async function (hostType) { + info("Opening the inspector"); + + const toolbox = await openToolboxForTab( + gBrowser.selectedTab, + "inspector", + hostType + ); + const inspector = toolbox.getPanel("inspector"); + + const highlighterTestFront = await getHighlighterTestFront(toolbox); + + return { toolbox, inspector, highlighterTestFront }; +}; + +/** + * Open the toolbox, with the inspector tool visible, and the one of the sidebar + * tabs selected. + * + * @param {String} id + * The ID of the sidebar tab to be opened + * @return {Promise<Object>} A promise that resolves when the inspector is ready and the tab is + * visible and ready. The promise resolves with an object containing the + * following properties: + * - toolbox + * - inspector + * - highlighterTestFront + */ +var openInspectorSidebarTab = async function (id) { + const { toolbox, inspector, highlighterTestFront } = await openInspector(); + + info("Selecting the " + id + " sidebar"); + + const onSidebarSelect = inspector.sidebar.once("select"); + if (id === "layoutview") { + // The layout view should wait until the box-model and grid-panel are ready. + const onBoxModelViewReady = inspector.once("boxmodel-view-updated"); + const onGridPanelReady = inspector.once("grid-panel-updated"); + inspector.sidebar.select(id); + await onBoxModelViewReady; + await onGridPanelReady; + } else { + inspector.sidebar.select(id); + } + await onSidebarSelect; + + return { + toolbox, + inspector, + highlighterTestFront, + }; +}; + +/** + * Open the toolbox, with the inspector tool visible, and the rule-view + * sidebar tab selected. + * + * @return a promise that resolves when the inspector is ready and the rule view + * is visible and ready + */ +async function openRuleView() { + const { inspector, toolbox, highlighterTestFront } = await openInspector(); + + const ruleViewPanel = inspector.getPanel("ruleview"); + await ruleViewPanel.readyPromise; + const view = ruleViewPanel.view; + + // Replace the view to use a custom debounce function that can be triggered manually + // through an additional ".flush()" property. + view.debounce = manualDebounce(); + + return { + toolbox, + inspector, + highlighterTestFront, + view, + }; +} + +/** + * Open the toolbox, with the inspector tool visible, and the computed-view + * sidebar tab selected. + * + * @return a promise that resolves when the inspector is ready and the computed + * view is visible and ready + */ +function openComputedView() { + return openInspectorSidebarTab("computedview").then(data => { + const view = data.inspector.getPanel("computedview").computedView; + + return { + toolbox: data.toolbox, + inspector: data.inspector, + highlighterTestFront: data.highlighterTestFront, + view, + }; + }); +} + +/** + * Open the toolbox, with the inspector tool visible, and the changes view + * sidebar tab selected. + * + * @return a promise that resolves when the inspector is ready and the changes + * view is visible and ready + */ +function openChangesView() { + return openInspectorSidebarTab("changesview").then(data => { + return { + toolbox: data.toolbox, + inspector: data.inspector, + highlighterTestFront: data.highlighterTestFront, + view: data.inspector.getPanel("changesview"), + }; + }); +} + +/** + * Open the toolbox, with the inspector tool visible, and the layout view + * sidebar tab selected to display the box model view with properties. + * + * @return {Promise} a promise that resolves when the inspector is ready and the layout + * view is visible and ready. + */ +function openLayoutView() { + return openInspectorSidebarTab("layoutview").then(data => { + return { + toolbox: data.toolbox, + inspector: data.inspector, + boxmodel: data.inspector.getPanel("boxmodel"), + gridInspector: data.inspector.getPanel("layoutview").gridInspector, + flexboxInspector: data.inspector.getPanel("layoutview").flexboxInspector, + layoutView: data.inspector.getPanel("layoutview"), + highlighterTestFront: data.highlighterTestFront, + }; + }); +} + +/** + * Select the rule view sidebar tab on an already opened inspector panel. + * + * @param {InspectorPanel} inspector + * The opened inspector panel + * @return {CssRuleView} the rule view + */ +function selectRuleView(inspector) { + return inspector.getPanel("ruleview").view; +} + +/** + * Select the computed view sidebar tab on an already opened inspector panel. + * + * @param {InspectorPanel} inspector + * The opened inspector panel + * @return {CssComputedView} the computed view + */ +function selectComputedView(inspector) { + inspector.sidebar.select("computedview"); + return inspector.getPanel("computedview").computedView; +} + +/** + * Select the changes view sidebar tab on an already opened inspector panel. + * + * @param {InspectorPanel} inspector + * The opened inspector panel + * @return {ChangesView} the changes view + */ +function selectChangesView(inspector) { + inspector.sidebar.select("changesview"); + return inspector.getPanel("changesview"); +} + +/** + * Select the layout view sidebar tab on an already opened inspector panel. + * + * @param {InspectorPanel} inspector + * @return {BoxModel} the box model + */ +function selectLayoutView(inspector) { + inspector.sidebar.select("layoutview"); + return inspector.getPanel("boxmodel"); +} + +/** + * Get the NodeFront for a node that matches a given css selector, via the + * protocol. + * @param {String|NodeFront} selector + * @param {InspectorPanel} inspector The instance of InspectorPanel currently + * loaded in the toolbox + * @return {Promise} Resolves to the NodeFront instance + */ +function getNodeFront(selector, { walker }) { + if (selector._form) { + return selector; + } + return walker.querySelector(walker.rootNode, selector); +} + +/** + * Set the inspector's current selection to the first match of the given css + * selector + * + * @param {String|NodeFront} selector + * @param {InspectorPanel} inspector + * The instance of InspectorPanel currently loaded in the toolbox. + * @param {String} reason + * Defaults to "test" which instructs the inspector not to highlight the + * node upon selection. + * @param {Boolean} isSlotted + * Is the selection representing the slotted version the node. + * @return {Promise} Resolves when the inspector is updated with the new node + */ +var selectNode = async function ( + selector, + inspector, + reason = "test", + isSlotted +) { + info("Selecting the node for '" + selector + "'"); + const nodeFront = await getNodeFront(selector, inspector); + const updated = inspector.once("inspector-updated"); + + const { + ELEMENT_NODE, + } = require("resource://devtools/shared/dom-node-constants.js"); + const onSelectionCssSelectorsUpdated = + nodeFront?.nodeType == ELEMENT_NODE + ? inspector.once("selection-css-selectors-updated") + : null; + + inspector.selection.setNodeFront(nodeFront, { reason, isSlotted }); + await updated; + await onSelectionCssSelectorsUpdated; +}; + +/** + * Using the markupview's _waitForChildren function, wait for all queued + * children updates to be handled. + * @param {InspectorPanel} inspector The instance of InspectorPanel currently + * loaded in the toolbox + * @return a promise that resolves when all queued children updates have been + * handled + */ +function waitForChildrenUpdated({ markup }) { + info("Waiting for queued children updates to be handled"); + return new Promise(resolve => { + markup._waitForChildren().then(() => { + executeSoon(resolve); + }); + }); +} + +// The expand all operation of the markup-view calls itself recursively and +// there's not one event we can wait for to know when it's done, so use this +// helper function to wait until all recursive children updates are done. +async function waitForMultipleChildrenUpdates(inspector) { + // As long as child updates are queued up while we wait for an update already + // wait again + if ( + inspector.markup._queuedChildUpdates && + inspector.markup._queuedChildUpdates.size + ) { + await waitForChildrenUpdated(inspector); + return waitForMultipleChildrenUpdates(inspector); + } + return null; +} + +/** + * Expand the provided markup container programmatically and wait for all + * children to update. + */ +async function expandContainer(inspector, container) { + await inspector.markup.expandNode(container.node); + await waitForMultipleChildrenUpdates(inspector); +} + +/** + * Get the NodeFront for a node that matches a given css selector inside a + * given iframe. + * + * @param {Array} selectors + * Arrays of CSS selectors from the root document to the node. + * The last CSS selector of the array is for the node in its frame doc. + * The before-last CSS selector is for the frame in its parent frame, etc... + * Ex: ["frame.first-frame", ..., "frame.last-frame", ".target-node"] + * @param {InspectorPanel} inspector + * See `selectNode` + * @return {NodeFront} Resolves the corresponding node front. + */ +async function getNodeFrontInFrames(selectors, inspector) { + let walker = inspector.walker; + let rootNode = walker.rootNode; + + // clone the array since `selectors` could be used from callsite after. + selectors = [...selectors]; + // Extract the last selector from the provided array of selectors. + const nodeSelector = selectors.pop(); + + // Remaining selectors should all be frame selectors. Renaming for clarity. + const frameSelectors = selectors; + + info("Loop through all frame selectors"); + for (const frameSelector of frameSelectors) { + const url = walker.targetFront.url; + info(`Find the frame element for selector ${frameSelector} in ${url}`); + + const frameNodeFront = await walker.querySelector(rootNode, frameSelector); + + // If needed, connect to the corresponding frame target. + // Otherwise, reuse the current targetFront. + let frameTarget = frameNodeFront.targetFront; + if (frameNodeFront.useChildTargetToFetchChildren) { + info("Connect to frame and retrieve the targetFront"); + frameTarget = await frameNodeFront.connectToFrame(); + } + + walker = (await frameTarget.getFront("inspector")).walker; + + if (frameNodeFront.useChildTargetToFetchChildren) { + // For frames or browser elements, use the walker's rootNode. + rootNode = walker.rootNode; + } else { + // For same-process frames, select the document front as the root node. + // It is a different node from the walker's rootNode. + info("Retrieve the children of the frame to find the document node"); + const { nodes } = await walker.children(frameNodeFront); + rootNode = nodes.find(n => n.nodeType === Node.DOCUMENT_NODE); + } + } + + return walker.querySelector(rootNode, nodeSelector); +} + +/** + * Helper to select a node in the markup-view, in a nested tree of + * frames/browser elements. The iframes can either be remote or same-process. + * + * Note: "frame" will refer to either "frame" or "browser" in the documentation + * and method. + * + * @param {Array} selectors + * Arrays of CSS selectors from the root document to the node. + * The last CSS selector of the array is for the node in its frame doc. + * The before-last CSS selector is for the frame in its parent frame, etc... + * Ex: ["frame.first-frame", ..., "frame.last-frame", ".target-node"] + * @param {InspectorPanel} inspector + * See `selectNode` + * @param {String} reason + * See `selectNode` + * @param {Boolean} isSlotted + * See `selectNode` + * @return {NodeFront} The selected node front. + */ +async function selectNodeInFrames( + selectors, + inspector, + reason = "test", + isSlotted +) { + const nodeFront = await getNodeFrontInFrames(selectors, inspector); + await selectNode(nodeFront, inspector, reason, isSlotted); + return nodeFront; +} + +/** + * Create a throttling function that can be manually "flushed". This is to replace the + * use of the `debounce` function from `devtools/client/inspector/shared/utils.js`, which + * has a setTimeout that can cause intermittents. + * @return {Function} This function has the same function signature as debounce, but + * the property `.flush()` has been added for flushing out any + * debounced calls. + */ +function manualDebounce() { + let calls = []; + + function debounce(func, wait, scope) { + return function () { + const existingCall = calls.find(call => call.func === func); + if (existingCall) { + existingCall.args = arguments; + } else { + calls.push({ func, wait, scope, args: arguments }); + } + }; + } + + debounce.flush = function () { + calls.forEach(({ func, scope, args }) => func.apply(scope, args)); + calls = []; + }; + + return debounce; +} + +/** + * Get the requested rule style property from the current browser. + * + * @param {Number} styleSheetIndex + * @param {Number} ruleIndex + * @param {String} name + * @return {String} The value, if found, null otherwise + */ + +async function getRulePropertyValue(styleSheetIndex, ruleIndex, name) { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [styleSheetIndex, ruleIndex, name], + (styleSheetIndexChild, ruleIndexChild, nameChild) => { + let value = null; + + info( + "Getting the value for property name " + + nameChild + + " in sheet " + + styleSheetIndexChild + + " and rule " + + ruleIndexChild + ); + + const sheet = content.document.styleSheets[styleSheetIndexChild]; + if (sheet) { + const rule = sheet.cssRules[ruleIndexChild]; + if (rule) { + value = rule.style.getPropertyValue(nameChild); + } + } + + return value; + } + ); +} + +/** + * Get the requested computed style property from the current browser. + * + * @param {String} selector + * The selector used to obtain the element. + * @param {String} pseudo + * pseudo id to query, or null. + * @param {String} propName + * name of the property. + */ +async function getComputedStyleProperty(selector, pseudo, propName) { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [selector, pseudo, propName], + (selectorChild, pseudoChild, propNameChild) => { + const element = content.document.querySelector(selectorChild); + return content.document.defaultView + .getComputedStyle(element, pseudoChild) + .getPropertyValue(propNameChild); + } + ); +} + +/** + * Wait until the requested computed style property has the + * expected value in the the current browser. + * + * @param {String} selector + * The selector used to obtain the element. + * @param {String} pseudo + * pseudo id to query, or null. + * @param {String} propName + * name of the property. + * @param {String} expected + * expected value of property + */ +async function waitForComputedStyleProperty( + selector, + pseudo, + propName, + expected +) { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [selector, pseudo, propName, expected], + (selectorChild, pseudoChild, propNameChild, expectedChild) => { + const element = content.document.querySelector(selectorChild); + return ContentTaskUtils.waitForCondition(() => { + const value = content.document.defaultView + .getComputedStyle(element, pseudoChild) + .getPropertyValue(propNameChild); + return value === expectedChild; + }); + } + ); +} + +/** + * Given an inplace editable element, click to switch it to edit mode, wait for + * focus + * + * @return a promise that resolves to the inplace-editor element when ready + */ +var focusEditableField = async function ( + ruleView, + editable, + xOffset = 1, + yOffset = 1, + options = {} +) { + const onFocus = once(editable.parentNode, "focus", true); + info("Clicking on editable field to turn to edit mode"); + if (options.type === undefined) { + // "mousedown" and "mouseup" flushes any pending layout. Therefore, + // if the caller wants to click an element, e.g., closebrace to add new + // property, we need to guarantee that the element is clicked here even + // if it's moved by flushing the layout because whether the UI is useful + // or not when there is pending reflow is not scope of the tests. + options.type = "mousedown"; + EventUtils.synthesizeMouse( + editable, + xOffset, + yOffset, + options, + editable.ownerGlobal + ); + options.type = "mouseup"; + EventUtils.synthesizeMouse( + editable, + xOffset, + yOffset, + options, + editable.ownerGlobal + ); + } else { + EventUtils.synthesizeMouse( + editable, + xOffset, + yOffset, + options, + editable.ownerGlobal + ); + } + await onFocus; + + info("Editable field gained focus, returning the input field now"); + const onEdit = inplaceEditor(editable.ownerDocument.activeElement); + + return onEdit; +}; + +/** + * Get the DOMNode for a css rule in the rule-view that corresponds to the given + * selector. + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {String} selectorText + * The selector in the rule-view for which the rule + * object is wanted + * @param {Number} index + * If there are more than 1 rule with the same selector, you may pass a + * index to determine which of the rules you want. + * @return {DOMNode} + */ +function getRuleViewRule(view, selectorText, index = 0) { + let rule; + let pos = 0; + for (const r of view.styleDocument.querySelectorAll(".ruleview-rule")) { + const selector = r.querySelector( + ".ruleview-selectorcontainer, " + ".ruleview-selector-matched" + ); + if (selector && selector.textContent === selectorText) { + if (index == pos) { + rule = r; + break; + } + pos++; + } + } + + return rule; +} + +/** + * Get references to the name and value span nodes corresponding to a given + * selector and property name in the rule-view. + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {String} selectorText + * The selector in the rule-view to look for the property in + * @param {String} propertyName + * The name of the property + * @param {Object=} options + * @param {Boolean=} options.wait + * When true, returns a promise which waits until a valid rule view + * property can be retrieved for the provided selectorText & propertyName. + * Defaults to false. + * @return {Object} An object like {nameSpan: DOMNode, valueSpan: DOMNode} + */ +function getRuleViewProperty(view, selectorText, propertyName, options = {}) { + if (options.wait) { + return waitFor(() => + _syncGetRuleViewProperty(view, selectorText, propertyName) + ); + } + return _syncGetRuleViewProperty(view, selectorText, propertyName); +} + +function _syncGetRuleViewProperty(view, selectorText, propertyName) { + const rule = getRuleViewRule(view, selectorText); + if (!rule) { + return null; + } + + // Look for the propertyName in that rule element + for (const p of rule.querySelectorAll(".ruleview-property")) { + const nameSpan = p.querySelector(".ruleview-propertyname"); + const valueSpan = p.querySelector(".ruleview-propertyvalue"); + + if (nameSpan.textContent === propertyName) { + return { nameSpan, valueSpan }; + } + } + return null; +} + +/** + * Get the text value of the property corresponding to a given selector and name + * in the rule-view + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {String} selectorText + * The selector in the rule-view to look for the property in + * @param {String} propertyName + * The name of the property + * @return {String} The property value + */ +function getRuleViewPropertyValue(view, selectorText, propertyName) { + return getRuleViewProperty(view, selectorText, propertyName).valueSpan + .textContent; +} + +/** + * Get a reference to the selector DOM element corresponding to a given selector + * in the rule-view + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {String} selectorText + * The selector in the rule-view to look for + * @return {DOMNode} The selector DOM element + */ +function getRuleViewSelector(view, selectorText) { + const rule = getRuleViewRule(view, selectorText); + return rule.querySelector(".ruleview-selector, .ruleview-selector-matched"); +} + +/** + * Get a rule-link from the rule-view given its index + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {Number} index + * The index of the link to get + * @return {DOMNode} The link if any at this index + */ +function getRuleViewLinkByIndex(view, index) { + const links = view.styleDocument.querySelectorAll(".ruleview-rule-source"); + return links[index]; +} + +/** + * Get rule-link text from the rule-view given its index + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {Number} index + * The index of the link to get + * @return {String} The string at this index + */ +function getRuleViewLinkTextByIndex(view, index) { + const link = getRuleViewLinkByIndex(view, index); + return link.querySelector(".ruleview-rule-source-label").textContent; +} + +/** + * Click on a rule-view's close brace to focus a new property name editor + * + * @param {RuleEditor} ruleEditor + * An instance of RuleEditor that will receive the new property + * @return a promise that resolves to the newly created editor when ready and + * focused + */ +var focusNewRuleViewProperty = async function (ruleEditor) { + info("Clicking on a close ruleEditor brace to start editing a new property"); + + // Use bottom alignment to avoid scrolling out of the parent element area. + ruleEditor.closeBrace.scrollIntoView(false); + const editor = await focusEditableField( + ruleEditor.ruleView, + ruleEditor.closeBrace + ); + + is( + inplaceEditor(ruleEditor.newPropSpan), + editor, + "Focused editor is the new property editor." + ); + + return editor; +}; + +/** + * Create a new property name in the rule-view, focusing a new property editor + * by clicking on the close brace, and then entering the given text. + * Keep in mind that the rule-view knows how to handle strings with multiple + * properties, so the input text may be like: "p1:v1;p2:v2;p3:v3". + * + * @param {RuleEditor} ruleEditor + * The instance of RuleEditor that will receive the new property(ies) + * @param {String} inputValue + * The text to be entered in the new property name field + * @return a promise that resolves when the new property name has been entered + * and once the value field is focused + */ +var createNewRuleViewProperty = async function (ruleEditor, inputValue) { + info("Creating a new property editor"); + const editor = await focusNewRuleViewProperty(ruleEditor); + + info("Entering the value " + inputValue); + editor.input.value = inputValue; + + info("Submitting the new value and waiting for value field focus"); + const onFocus = once(ruleEditor.element, "focus", true); + EventUtils.synthesizeKey( + "VK_RETURN", + {}, + ruleEditor.element.ownerDocument.defaultView + ); + await onFocus; +}; + +/** + * Set the search value for the rule-view filter styles search box. + * + * @param {CssRuleView} view + * The instance of the rule-view panel + * @param {String} searchValue + * The filter search value + * @return a promise that resolves when the rule-view is filtered for the + * search term + */ +var setSearchFilter = async function (view, searchValue) { + info('Setting filter text to "' + searchValue + '"'); + + const searchField = view.searchField; + searchField.focus(); + + for (const key of searchValue.split("")) { + EventUtils.synthesizeKey(key, {}, view.styleWindow); + } + + await view.inspector.once("ruleview-filtered"); +}; + +/** + * Flatten all context menu items into a single array to make searching through + * it easier. + */ +function buildContextMenuItems(menu) { + const allItems = [].concat.apply( + [], + menu.items.map(function addItem(item) { + if (item.submenu) { + return addItem(item.submenu.items); + } + return item; + }) + ); + + return allItems; +} + +/** + * Open the style editor context menu and return all of it's items in a flat array + * @param {CssRuleView} view + * The instance of the rule-view panel + * @return An array of MenuItems + */ +function openStyleContextMenuAndGetAllItems(view, target) { + const menu = view.contextMenu._openMenu({ target }); + return buildContextMenuItems(menu); +} + +/** + * Open the inspector menu and return all of it's items in a flat array + * @param {InspectorPanel} inspector + * @param {Object} options to pass into openMenu + * @return An array of MenuItems + */ +function openContextMenuAndGetAllItems(inspector, options) { + const menu = inspector.markup.contextMenu._openMenu(options); + return buildContextMenuItems(menu); +} + +/** + * Wait until the elements the given selectors indicate come to have the visited state. + * + * @param {Tab} tab + * The tab where the elements on. + * @param {Array} selectors + * The selectors for the elements. + */ +async function waitUntilVisitedState(tab, selectors) { + await asyncWaitUntil(async () => { + const hasVisitedState = await ContentTask.spawn( + tab.linkedBrowser, + selectors, + args => { + const ELEMENT_STATE_VISITED = 1 << 19; + + for (const selector of args) { + const target = + content.wrappedJSObject.document.querySelector(selector); + if ( + !( + target && + InspectorUtils.getContentState(target) & ELEMENT_STATE_VISITED + ) + ) { + return false; + } + } + return true; + } + ); + return hasVisitedState; + }); +} + +/** + * Return wether or not the passed selector matches an element in the content page. + * + * @param {string} selector + * @returns Promise<Boolean> + */ +function hasMatchingElementInContentPage(selector) { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [selector], + function (innerSelector) { + return content.document.querySelector(innerSelector) !== null; + } + ); +} + +/** + * Return the number of elements matching the passed selector. + * + * @param {string} selector + * @returns Promise<Number> the number of matching elements + */ +function getNumberOfMatchingElementsInContentPage(selector) { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [selector], + function (innerSelector) { + return content.document.querySelectorAll(innerSelector).length; + } + ); +} + +/** + * Get the property of an element in the content page + * + * @param {string} selector: The selector to get the element we want the property of + * @param {string} propertyName: The name of the property we want the value of + * @returns {Promise} A promise that returns with the value of the property for the element + */ +function getContentPageElementProperty(selector, propertyName) { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [selector, propertyName], + function (innerSelector, innerPropertyName) { + return content.document.querySelector(innerSelector)[innerPropertyName]; + } + ); +} + +/** + * Set the property of an element in the content page + * + * @param {string} selector: The selector to get the element we want to set the property on + * @param {string} propertyName: The name of the property we want to set + * @param {string} propertyValue: The value that is going to be assigned to the property + * @returns {Promise} + */ +function setContentPageElementProperty(selector, propertyName, propertyValue) { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [selector, propertyName, propertyValue], + function (innerSelector, innerPropertyName, innerPropertyValue) { + content.document.querySelector(innerSelector)[innerPropertyName] = + innerPropertyValue; + } + ); +} + +/** + * Get all the attributes for a DOM Node living in the content page. + * + * @param {String} selector The node selector + * @returns {Array<Object>} An array of {name, value} objects. + */ +async function getContentPageElementAttributes(selector) { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [selector], + _selector => { + const node = content.document.querySelector(_selector); + return Array.from(node.attributes).map(({ name, value }) => ({ + name, + value, + })); + } + ); +} + +/** + * Get an attribute on a DOM Node living in the content page. + * + * @param {String} selector The node selector + * @param {String} attribute The attribute name + * @return {String} value The attribute value + */ +async function getContentPageElementAttribute(selector, attribute) { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [selector, attribute], + (_selector, _attribute) => { + return content.document.querySelector(_selector).getAttribute(_attribute); + } + ); +} + +/** + * Set an attribute on a DOM Node living in the content page. + * + * @param {String} selector The node selector + * @param {String} attribute The attribute name + * @param {String} value The attribute value + */ +async function setContentPageElementAttribute(selector, attribute, value) { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [selector, attribute, value], + (_selector, _attribute, _value) => { + content.document + .querySelector(_selector) + .setAttribute(_attribute, _value); + } + ); +} + +/** + * Remove an attribute from a DOM Node living in the content page. + * + * @param {String} selector The node selector + * @param {String} attribute The attribute name + */ +async function removeContentPageElementAttribute(selector, attribute) { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [selector, attribute], + (_selector, _attribute) => { + content.document.querySelector(_selector).removeAttribute(_attribute); + } + ); +} |