Source code

Revision control

Copy as Markdown

Other Tools

/* Any copyright is dedicated to the Public Domain.
/* eslint no-unused-vars: [2, {"vars": "local"}] */
"use strict";
// Import the inspector's head.js first (which itself imports shared-head.js).
Services.scriptloader.loadSubScript(
this
);
registerCleanupFunction(() => {
Services.prefs.clearUserPref("devtools.defaultColorUnit");
});
/**
* Dispatch the copy event on the given element
*/
function fireCopyEvent(element) {
const evt = element.ownerDocument.createEvent("Event");
evt.initEvent("copy", true, true);
element.dispatchEvent(evt);
}
/**
* Return all the computed items in the computed view
*
* @param {CssComputedView} view
* The instance of the computed view panel
* @returns {Array<Element>}
*/
function getComputedViewProperties(view) {
return Array.from(
view.styleDocument.querySelectorAll(
"#computed-container .computed-property-view"
)
);
}
/**
* Get references to the name and value span nodes corresponding to a given
* property name in the computed-view
*
* @param {CssComputedView} view
* The instance of the computed view panel
* @param {String} name
* The name of the property to retrieve
* @return an object {nameSpan, valueSpan}
*/
function getComputedViewProperty(view, name) {
let prop;
for (const property of getComputedViewProperties(view)) {
const nameSpan = property.querySelector(".computed-property-name");
const valueSpan = property.querySelector(".computed-property-value");
if (nameSpan.firstChild.textContent === name) {
prop = { nameSpan, valueSpan };
break;
}
}
return prop;
}
/**
* Get an instance of PropertyView from the computed-view.
*
* @param {CssComputedView} view
* The instance of the computed view panel
* @param {String} name
* The name of the property to retrieve
* @return {PropertyView}
*/
function getComputedViewPropertyView(view, name) {
let propView;
for (const propertyView of view.propertyViews) {
if (propertyView.propertyInfo.name === name) {
propView = propertyView;
break;
}
}
return propView;
}
/**
* Get a reference to the matched rules element for a given property name in
* the computed-view.
* A matched rule element is inside the property element (<li>) itself
* and is only shown when the twisty icon is expanded on the property.
* It contains matched rules, with selectors, properties, values and stylesheet links.
*
* @param {CssComputedView} view
* The instance of the computed view panel
* @param {String} name
* The name of the property to retrieve
* @return {Promise} A promise that resolves to the property matched rules
* container
*/
var getComputedViewMatchedRules = async function (view, name) {
let expander;
let matchedRulesEl;
for (const property of view.styleDocument.querySelectorAll(
"#computed-container .computed-property-view"
)) {
const nameSpan = property.querySelector(".computed-property-name");
if (nameSpan.firstChild.textContent === name) {
expander = property.querySelector(".computed-expandable");
matchedRulesEl = property.querySelector(".matchedselectors");
break;
}
}
if (!expander.hasAttribute("open")) {
// Need to expand the property
const onExpand = view.inspector.once("computed-view-property-expanded");
expander.click();
await onExpand;
await waitFor(() => expander.hasAttribute("open"));
}
return matchedRulesEl;
};
/**
* Get the text value of the property corresponding to a given name in the
* computed-view
*
* @param {CssComputedView} view
* The instance of the computed view panel
* @param {String} name
* The name of the property to retrieve
* @return {String} The property value
*/
function getComputedViewPropertyValue(view, name) {
return getComputedViewProperty(view, name).valueSpan.textContent;
}
/**
* Expand a given property, given its index in the current property list of
* the computed view
*
* @param {CssComputedView} view
* The instance of the computed view panel
* @param {Number} index
* The index of the property to be expanded
* @return a promise that resolves when the property has been expanded, or
* rejects if the property was not found
*/
function expandComputedViewPropertyByIndex(view, index) {
info("Expanding property " + index + " in the computed view");
const expandos = view.styleDocument.querySelectorAll(".computed-expandable");
if (!expandos.length || !expandos[index]) {
return Promise.reject();
}
const onExpand = view.inspector.once("computed-view-property-expanded");
expandos[index].click();
return onExpand;
}
/**
* Get a rule-link from the computed-view given its index
*
* @param {CssComputedView} view
* The instance of the computed view panel
* @param {Number} index
* The index of the link to be retrieved
* @return {DOMNode} The link at the given index, if one exists, null otherwise
*/
function getComputedViewLinkByIndex(view, index) {
const links = view.styleDocument.querySelectorAll(
".rule-link .computed-link"
);
return links[index];
}
/**
* Trigger the select all action in the computed view.
*
* @param {CssComputedView} view
* The instance of the computed view panel
*/
function selectAllText(view) {
info("Selecting all the text");
view.contextMenu._onSelectAll();
}
/**
* Select all the text, copy it, and check the content in the clipboard.
*
* @param {CssComputedView} view
* The instance of the computed view panel
* @param {String} expectedPattern
* A regular expression used to check the content of the clipboard
*/
async function copyAllAndCheckClipboard(view, expectedPattern) {
selectAllText(view);
const contentDoc = view.styleDocument;
const prop = contentDoc.querySelector(
"#computed-container .computed-property-view"
);
try {
info("Trigger a copy event and wait for the clipboard content");
await waitForClipboardPromise(
() => fireCopyEvent(prop),
() => checkClipboard(expectedPattern)
);
} catch (e) {
failClipboardCheck(expectedPattern);
}
}
/**
* Select some text, copy it, and check the content in the clipboard.
*
* @param {CssComputedView} view
* The instance of the computed view panel
* @param {Object} positions
* The start and end positions of the text to be selected. This must be an object
* like this:
* { start: {prop: 1, offset: 0}, end: {prop: 3, offset: 5} }
* @param {String} expectedPattern
* A regular expression used to check the content of the clipboard
*/
async function copySomeTextAndCheckClipboard(view, positions, expectedPattern) {
info("Testing selection copy");
const contentDocument = view.styleDocument;
const props = contentDocument.querySelectorAll(
"#computed-container .computed-property-view"
);
info("Create the text selection range");
const range = contentDocument.createRange();
range.setStart(props[positions.start.prop], positions.start.offset);
range.setEnd(props[positions.end.prop], positions.end.offset);
contentDocument.defaultView.getSelection().addRange(range);
try {
info("Trigger a copy event and wait for the clipboard content");
await waitForClipboardPromise(
() => fireCopyEvent(props[0]),
() => checkClipboard(expectedPattern)
);
} catch (e) {
failClipboardCheck(expectedPattern);
}
}
function checkClipboard(expectedPattern) {
const actual = SpecialPowers.getClipboardData("text/plain");
const expectedRegExp = new RegExp(expectedPattern, "g");
return expectedRegExp.test(actual);
}
function failClipboardCheck(expectedPattern) {
// Format expected text for comparison
const terminator = Services.appinfo.OS == "WINNT" ? "\r\n" : "\n";
expectedPattern = expectedPattern.replace(/\[\\r\\n\][+*]/g, terminator);
expectedPattern = expectedPattern.replace(/\\\(/g, "(");
expectedPattern = expectedPattern.replace(/\\\)/g, ")");
let actual = SpecialPowers.getClipboardData("text/plain");
// Trim the right hand side of our strings. This is because expectedPattern
// accounts for windows sometimes adding a newline to our copied data.
expectedPattern = expectedPattern.trimRight();
actual = actual.trimRight();
dump(
"TEST-UNEXPECTED-FAIL | Clipboard text does not match expected ... " +
"results (escaped for accurate comparison):\n"
);
info("Actual: " + escape(actual));
info("Expected: " + escape(expectedPattern));
}